Merge branch 'master' of github.com:spring-projects/sts4

This commit is contained in:
Kris De Volder
2017-01-30 16:24:43 -08:00
9 changed files with 380 additions and 471 deletions

View File

@@ -11,7 +11,7 @@
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
@@ -39,19 +39,20 @@ public class CFTarget {
*/
private final LoadingCache<String, List<CFBuildpack>> buildpacksCache;
private final LoadingCache<String, List<CFServiceInstance>> servicesCache;
private Throwable lastCFFailure;
public CFTarget(String targetName, CFClientParams params, ClientRequests requests) {
this.params = params;
this.requests = requests;
this.targetName = targetName;
CacheLoader<String, List<CFServiceInstance>> servicesLoader = new CacheLoader<String, List<CFServiceInstance>>() {
@Override
public List<CFServiceInstance> load(String key) throws Exception {
/*
* Ignore the key. Not used.
/* Cache of services does not use keys, as the whole cache
* gets wiped clean on any new call to CF.
*/
return requests.getServices();
return runAndCheckForFailure(() -> requests.getServices());
}
};
this.servicesCache = CacheBuilder.newBuilder()
@@ -61,26 +62,57 @@ public class CFTarget {
@Override
public List<CFBuildpack> load(String key) throws Exception {
/*
* Ignore the key. Not used.
/* Cache does not use keys, as the whole cache
* gets wiped clean on any new call to CF.
*/
return requests.getBuildpacks();
return runAndCheckForFailure(() -> requests.getBuildpacks());
}
};
this.buildpacksCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION, TimeUnit.HOURS).build(buildpacksLoader);
}
protected <T> T runAndCheckForFailure(Callable<T> callable) throws Exception {
this.lastCFFailure = null;
try {
return callable.call();
} catch (Exception e) {
if (isAcceptableCFFailure(e)) {
this.lastCFFailure = e;
}
throw e;
}
}
public boolean hasCFFailure() {
return this.lastCFFailure != null;
}
protected boolean isAcceptableCFFailure(Throwable e) {
// TODO: this is too broad. Be more specific: e.g. look for IOException, etc..
return e != null;
}
public CFClientParams getParams() {
return params;
}
public List<CFBuildpack> getBuildpacks() throws Exception {
return this.buildpacksCache.get(getName());
// Use the target name as the "key" , since Guava cache doesn't allow null keys
// However, the key is not really used when fetching buildpacks, as we are not caching
// buildpacks per target here. This class only represents ONE target, so it will only
// ever have one key
String key = getName();
return this.buildpacksCache.get(key);
}
public List<CFServiceInstance> getServices() throws Exception {
return this.servicesCache.get(getName());
/* services don't use keys, as they get wiped clean on each refresh
* . That said, the cache doesn't allow a null key, so use the target name as the "key"
*
*/
String key = getName();
return this.servicesCache.get(key);
}
public ClientRequests getClientRequests() {
@@ -95,5 +127,4 @@ public class CFTarget {
public String toString() {
return "CFClientTarget [params=" + params + ", targetName=" + targetName + "]";
}
}

View File

@@ -27,7 +27,7 @@ public class CFTargetCache {
private final ClientParamsProvider paramsProvider;
private final CloudFoundryClientFactory clientFactory;
private final ClientTimeouts timeouts;
private final LoadingCache<CFClientParams, CFTarget> cache;
private final LoadingCache<ClientParamsCacheKey, CFTarget> cache;
public static final long SERVICES_EXPIRATION = 10;
public static final long TARGET_EXPIRATION = 1;
@@ -39,11 +39,11 @@ public class CFTargetCache {
this.paramsProvider = paramsProvider;
this.clientFactory = clientFactory;
this.timeouts = timeouts;
CacheLoader<CFClientParams, CFTarget> loader = new CacheLoader<CFClientParams, CFTarget>() {
CacheLoader<ClientParamsCacheKey, CFTarget> loader = new CacheLoader<ClientParamsCacheKey, CFTarget>() {
@Override
public CFTarget load(CFClientParams params) throws Exception {
return create(params);
public CFTarget load(ClientParamsCacheKey params) throws Exception {
return create(params.fullParams);
}
};
@@ -63,8 +63,14 @@ public class CFTargetCache {
List<CFTarget> targets = new ArrayList<>();
if (allParams != null) {
for (CFClientParams params : allParams) {
CFTarget target = cache.get(params);
ClientParamsCacheKey key = ClientParamsCacheKey.from(params);
CFTarget target = cache.get(key);
if (target != null) {
// If any CF errors occurred in the target, refresh once
if (target.hasCFFailure()) {
cache.refresh(key);
target = cache.get(key);
}
targets.add(target);
}
}

View File

@@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
/**
*
* A key for CF targets that uses {@link CFClientParams} values, EXCEPT for
* credentials, as the key value.
*
*/
public class ClientParamsCacheKey {
public final CFClientParams fullParams;
/**
* Use static API to create: {@link #from(CFClientParams)}
* @param fullParams
*/
private ClientParamsCacheKey(CFClientParams fullParams) {
this.fullParams = fullParams;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fullParams.getApiUrl() == null) ? 0 : fullParams.getApiUrl().hashCode());
result = prime * result + ((fullParams.getOrgName() == null) ? 0 : fullParams.getOrgName().hashCode());
result = prime * result + (fullParams.skipSslValidation() ? 1231 : 1237);
result = prime * result + ((fullParams.getSpaceName() == null) ? 0 : fullParams.getSpaceName().hashCode());
result = prime * result + ((fullParams.getUsername() == null) ? 0 : fullParams.getUsername().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClientParamsCacheKey other = (ClientParamsCacheKey) obj;
if (fullParams.getApiUrl() == null) {
if (other.fullParams.getApiUrl() != null)
return false;
} else if (!fullParams.getApiUrl().equals(other.fullParams.getApiUrl()))
return false;
if (fullParams.getOrgName() == null) {
if (other.fullParams.getOrgName() != null)
return false;
} else if (!fullParams.getOrgName().equals(other.fullParams.getOrgName()))
return false;
if (fullParams.skipSslValidation() != other.fullParams.skipSslValidation())
return false;
if (fullParams.getSpaceName() == null) {
if (other.fullParams.getSpaceName() != null)
return false;
} else if (!fullParams.getSpaceName().equals(other.fullParams.getSpaceName()))
return false;
if (fullParams.getUsername() == null) {
if (other.fullParams.getUsername() != null)
return false;
} else if (!fullParams.getUsername().equals(other.fullParams.getUsername()))
return false;
return true;
}
public static ClientParamsCacheKey from(CFClientParams params) {
return new ClientParamsCacheKey(params);
}
}

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.cloudfoundry.client.v2;
import java.time.Duration;
import java.util.Optional;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.reactor.ConnectionContext;
import org.cloudfoundry.reactor.DefaultConnectionContext;
import org.cloudfoundry.reactor.ProxyConfiguration;
import org.cloudfoundry.reactor.TokenProvider;
import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;
import org.cloudfoundry.reactor.doppler.ReactorDopplerClient;
import org.cloudfoundry.reactor.tokenprovider.OneTimePasscodeTokenProvider;
import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;
import org.cloudfoundry.reactor.tokenprovider.RefreshTokenGrantTokenProvider;
import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials;
public class CFClientProvider {
final ConnectionContext connection;
final TokenProvider tokenProvider;
//Note the three client objects below are 'stateless' wrappers and it would be
// fine to recreate as needed instead of store them
final CloudFoundryClient client;
final ReactorUaaClient uaaClient;
final ReactorDopplerClient doppler;
private ProxyConfiguration getProxy(String host) {
// TODO: enable proxy support for vscode. The code below retrieves proxy via Eclipse proxy service thus
// not applicable when this is used outside of Eclipse
// try {
// if (StringUtils.hasText(host)) {
// URL url = new URL("https://"+host);
// // In certain cases, the activator would have stopped and the plugin may
// // no longer be available. Usually onl happens on shutdown.
// BootDashActivator plugin = BootDashActivator.getDefault();
// if (plugin != null) {
// IProxyService proxyService = plugin.getProxyService();
// if (proxyService != null) {
// IProxyData[] selectedProxies = proxyService.select(url.toURI());
//
// // No proxy configured or not found
// if (selectedProxies == null || selectedProxies.length == 0) {
// return null;
// }
//
// IProxyData data = selectedProxies[0];
// int proxyPort = data.getPort();
// String proxyHost = data.getHost();
// String user = data.getUserId();
// String password = data.getPassword();
// if (proxyHost!=null) {
// return ProxyConfiguration.builder()
// .host(proxyHost)
// .port(proxyPort==-1?Optional.empty():Optional.of(proxyPort))
// .username(Optional.ofNullable(user))
// .password(Optional.ofNullable(password))
// .build();
//// return proxyHost != null ? new HttpProxyConfiguration(proxyHost, proxyPort,
//// data.isRequiresAuthentication(), user, password) : null;
// }
// }
// }
// }
// } catch (Exception e) {
// Log.log(e);
// }
return null;
}
public CFClientProvider(CFClientParams params) {
long sslTimeout = Long.getLong("sts.bootdash.cf.client.ssl.handshake.timeout", 60); //TODO: make a preference for this?
Optional<Boolean> keepAlive = getBooleanSystemProp("http.keepAlive");
CloudFoundryClientCache.debug("cf client keepAlive = "+keepAlive);
connection = DefaultConnectionContext.builder()
.proxyConfiguration(Optional.ofNullable(getProxy(params.getHost())))
.apiHost(params.getHost())
.sslHandshakeTimeout(Duration.ofSeconds(sslTimeout))
.keepAlive(keepAlive)
.skipSslValidation(params.skipSslValidation())
.build();
tokenProvider = createTokenProvider(params);
client = ReactorCloudFoundryClient.builder()
.connectionContext(connection)
.tokenProvider(tokenProvider)
.build();
uaaClient = ReactorUaaClient.builder()
.connectionContext(connection)
.tokenProvider(tokenProvider)
.build();
doppler = ReactorDopplerClient.builder()
.connectionContext(connection)
.tokenProvider(tokenProvider)
.build();
}
private TokenProvider createTokenProvider(CFClientParams params) {
CFCredentials creds = params.getCredentials();
switch (creds.getType()) {
case PASSWORD:
return PasswordGrantTokenProvider.builder()
.username(params.getUsername())
.password(creds.getSecret())
.build();
case REFRESH_TOKEN:
return RefreshTokenGrantTokenProvider.builder()
.token(creds.getSecret())
.build();
case TEMPORARY_CODE:
return OneTimePasscodeTokenProvider.builder()
.passcode(creds.getSecret())
.build();
default:
throw new IllegalStateException("BUG! Missing switch case?");
}
}
private Optional<Boolean> getBooleanSystemProp(String name) {
String str = System.getProperty(name);
if (str!=null) {
return Optional.of(Boolean.valueOf(str));
}
return Optional.empty();
}
}

View File

@@ -10,183 +10,66 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.cloudfoundry.client.v2;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.reactor.ConnectionContext;
import org.cloudfoundry.reactor.DefaultConnectionContext;
import org.cloudfoundry.reactor.ProxyConfiguration;
import org.cloudfoundry.reactor.TokenProvider;
import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;
import org.cloudfoundry.reactor.doppler.ReactorDopplerClient;
import org.cloudfoundry.reactor.tokenprovider.OneTimePasscodeTokenProvider;
import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;
import org.cloudfoundry.reactor.tokenprovider.RefreshTokenGrantTokenProvider;
import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsCacheKey;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
* TODO: Remove this class when the 'thread leak bug' in V2 client is fixed.
*
* At the moment each time {@link SpringCloudFoundryClient} is create a threadpool
* is created by the client and it is never cleaned up. The only way we have
* to mitigate this leak is to try and create as few clients as possible.
* At the moment each time {@link SpringCloudFoundryClient} is create a
* threadpool is created by the client and it is never cleaned up. The only way
* we have to mitigate this leak is to try and create as few clients as
* possible.
* <p>
* So we have a permanent cache of clients here that is reused.
* <p>
* When the bug is fixed then this should no longer be necessary and we can removed this cache
* and just create the client as needed.
* When the bug is fixed then this should no longer be necessary and we can
* removed this cache and just create the client as needed.
*
* @author Kris De Volder
*/
public class CloudFoundryClientCache {
public class CFClientProvider {
final ConnectionContext connection;
final TokenProvider tokenProvider;
//Note the three client objects below are 'stateless' wrappers and it would be
// fine to recreate as needed instead of store them
final CloudFoundryClient client;
final ReactorUaaClient uaaClient;
final ReactorDopplerClient doppler;
private ProxyConfiguration getProxy(String host) {
// TODO: enable proxy support for vscode. The code below retrieves proxy via Eclipse proxy service thus
// not applicable when this is used outside of Eclipse
// try {
// if (StringUtils.hasText(host)) {
// URL url = new URL("https://"+host);
// // In certain cases, the activator would have stopped and the plugin may
// // no longer be available. Usually onl happens on shutdown.
// BootDashActivator plugin = BootDashActivator.getDefault();
// if (plugin != null) {
// IProxyService proxyService = plugin.getProxyService();
// if (proxyService != null) {
// IProxyData[] selectedProxies = proxyService.select(url.toURI());
//
// // No proxy configured or not found
// if (selectedProxies == null || selectedProxies.length == 0) {
// return null;
// }
//
// IProxyData data = selectedProxies[0];
// int proxyPort = data.getPort();
// String proxyHost = data.getHost();
// String user = data.getUserId();
// String password = data.getPassword();
// if (proxyHost!=null) {
// return ProxyConfiguration.builder()
// .host(proxyHost)
// .port(proxyPort==-1?Optional.empty():Optional.of(proxyPort))
// .username(Optional.ofNullable(user))
// .password(Optional.ofNullable(password))
// .build();
//// return proxyHost != null ? new HttpProxyConfiguration(proxyHost, proxyPort,
//// data.isRequiresAuthentication(), user, password) : null;
// }
// }
// }
// }
// } catch (Exception e) {
// Log.log(e);
// }
return null;
}
public CFClientProvider(CFClientParams params) {
long sslTimeout = Long.getLong("sts.bootdash.cf.client.ssl.handshake.timeout", 60); //TODO: make a preference for this?
Optional<Boolean> keepAlive = getBooleanSystemProp("http.keepAlive");
debug("cf client keepAlive = "+keepAlive);
connection = DefaultConnectionContext.builder()
.proxyConfiguration(Optional.ofNullable(getProxy(params.getHost())))
.apiHost(params.getHost())
.sslHandshakeTimeout(Duration.ofSeconds(sslTimeout))
.keepAlive(keepAlive)
.skipSslValidation(params.skipSslValidation())
.build();
tokenProvider = createTokenProvider(params);
client = ReactorCloudFoundryClient.builder()
.connectionContext(connection)
.tokenProvider(tokenProvider)
.build();
uaaClient = ReactorUaaClient.builder()
.connectionContext(connection)
.tokenProvider(tokenProvider)
.build();
doppler = ReactorDopplerClient.builder()
.connectionContext(connection)
.tokenProvider(tokenProvider)
.build();
}
private TokenProvider createTokenProvider(CFClientParams params) {
CFCredentials creds = params.getCredentials();
switch (creds.getType()) {
case PASSWORD:
return PasswordGrantTokenProvider.builder()
.username(params.getUsername())
.password(creds.getSecret())
.build();
case REFRESH_TOKEN:
return RefreshTokenGrantTokenProvider.builder()
.token(creds.getSecret())
.build();
case TEMPORARY_CODE:
return OneTimePasscodeTokenProvider.builder()
.passcode(creds.getSecret())
.build();
default:
throw new IllegalStateException("BUG! Missing switch case?");
}
}
private Optional<Boolean> getBooleanSystemProp(String name) {
String str = System.getProperty(name);
if (str!=null) {
return Optional.of(Boolean.valueOf(str));
}
return Optional.empty();
}
}
private static final boolean DEBUG = false;
public static final long EXPIRATION = 1;
private static void debug(String string) {
static void debug(String string) {
if (DEBUG) {
System.out.println(string);
}
}
private Map<CFClientParams, CFClientProvider> cache = new HashMap<>();
private final LoadingCache<ClientParamsCacheKey, CFClientProvider> cache;
private int clientCount = 0;
public synchronized CFClientProvider getOrCreate(CFClientParams params) {
CFClientProvider client = cache.get(params);
if (client==null) {
clientCount++;
debug("Creating client ["+clientCount+"]: "+params);
cache.put(params, client = create(params));
} else {
debug("Reusing client ["+clientCount+"]: "+params);
}
return client;
public CloudFoundryClientCache() {
CacheLoader<ClientParamsCacheKey, CFClientProvider> loader = new CacheLoader<ClientParamsCacheKey, CFClientProvider>() {
@Override
public CFClientProvider load(ClientParamsCacheKey params) throws Exception {
clientCount++;
debug("Creating client [" + clientCount + "]: " + params);
return create(params.fullParams);
}
};
cache = CacheBuilder.newBuilder().initialCapacity(1).expireAfterAccess(EXPIRATION, TimeUnit.HOURS)
.build(loader);
}
public synchronized CFClientProvider getOrCreate(CFClientParams params) throws Exception {
return cache.get(ClientParamsCacheKey.from(params));
}
protected CFClientProvider create(CFClientParams params) {
return new CFClientProvider(params);
}
}

View File

@@ -22,7 +22,6 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInsta
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientCache.CFClientProvider;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import com.google.common.collect.ImmutableList;
@@ -45,7 +44,7 @@ public class DefaultClientRequestsV2 implements ClientRequests {
private final ClientTimeouts timeouts;
public DefaultClientRequestsV2(CloudFoundryClientCache clients, CFClientParams params, ClientTimeouts timeouts) {
CFClientProvider provider = clients.getOrCreate(params);
CFClientProvider provider = getFromCache(clients, params);
this._client = provider.client;
this._operations = DefaultCloudFoundryOperations.builder()
@@ -61,6 +60,16 @@ public class DefaultClientRequestsV2 implements ClientRequests {
this.timeouts = timeouts != null ? timeouts : ClientTimeouts.DEFAULT_TIMEOUTS;
}
private CFClientProvider getFromCache(CloudFoundryClientCache clients, CFClientParams params) {
CFClientProvider provider = null;
try {
provider = clients.getOrCreate(params);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to create a v2 CF Java client using params: " + params, e);
}
return provider;
}
@Override
public List<CFServiceInstance> getServices() throws Exception {
return ReactorUtils.get(timeouts.getServicesTimeout(), CancelationTokens.NULL,

View File

@@ -1,166 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.manifest.yaml;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFEntities;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import com.google.common.collect.ImmutableList;
/**
* An alternative to using mockito for mocking the CF client, as the mockito
* version of the CF client seems to fail when run on OpenJDK (which is used for
* the sts4 concourse ci build).
* <p/>
* This also allows additional testing of the general manifest-yaml vscode
* framework, as the framework should be able to take any client factory,
* including the basic one below, and still produce correct results for content
* assist as well as reconcile
*
*/
public class BasicCfClientHarness {
private BasicCFClientFactory clientFactory = new BasicCFClientFactory();
private ClientParamsProvider paramsProvider = new BasicClientParamsProvider(ImmutableList.of(DEFAULT_PARAMS));
public static CFClientParams DEFAULT_PARAMS = new CFClientParams("test.io", "testuser",
CFCredentials.fromRefreshToken("refreshtoken"), false);
public BasicCFClientFactory getBasicClientFactory() {
return clientFactory;
}
public ClientParamsProvider getParamsProvider() {
return paramsProvider;
}
public void addServiceInstances(String... serviceInstances) {
List<CFServiceInstance> services = null;
if (serviceInstances != null) {
services = new ArrayList<CFServiceInstance>();
for (String name : serviceInstances) {
services.add(createServiceInstance(name));
}
}
getBasicClientFactory().getExistingClientInHarness().setServices(services);
}
public void addBuildpacks(String... buildpacks) {
// Allow testing of null condition when vscode asks for buildpacks from
// the client and
// client returns null instead of empty list
List<CFBuildpack> asList = null;
if (buildpacks != null) {
asList = new ArrayList<CFBuildpack>();
for (String name : buildpacks) {
asList.add(CFEntities.createBuildpack(name));
}
}
getBasicClientFactory().getExistingClientInHarness().setBuildPacks(asList);
}
protected CFServiceInstance createServiceInstance(String name) {
return CFEntities.createServiceInstance(name, null, null, null, null, null);
}
////////////////////////////////////////////////
// "Mocked" CF client types
public static final class BasicCFClientFactory implements CloudFoundryClientFactory {
/*
* Create the client "ahead of time" so that it can be configured before
* the language server is tested
*/
private BasicClientRequests preexistingClient = new BasicClientRequests(DEFAULT_PARAMS, new ClientTimeouts());
@Override
public ClientRequests getClient(CFClientParams params, ClientTimeouts timeouts) throws Exception {
return this.preexistingClient;
}
/**
* Convenient non-framework method to fetch the existing client so that
* values can be set to simulate values from CF.
*
* @return
*/
public BasicClientRequests getExistingClientInHarness() {
return this.preexistingClient;
}
}
static class BasicClientParamsProvider implements ClientParamsProvider {
private List<CFClientParams> params;
public BasicClientParamsProvider(List<CFClientParams> defaultParams) {
this.params = defaultParams;
}
public void setParams(List<CFClientParams> params) {
this.params = params;
}
@Override
public List<CFClientParams> getParams() throws NoTargetsException, ExecutionException {
return this.params;
}
}
static class BasicClientRequests implements ClientRequests {
private List<CFBuildpack> buildpacks;
private List<CFServiceInstance> serviceInstances;
private CFClientParams params;
private ClientTimeouts timeouts;
public BasicClientRequests(CFClientParams params, ClientTimeouts timeouts) {
this.params = params;
this.timeouts = timeouts;
}
@Override
public List<CFBuildpack> getBuildpacks() throws Exception {
return this.buildpacks;
}
@Override
public List<CFServiceInstance> getServices() throws Exception {
return this.serviceInstances;
}
public void setBuildPacks(List<CFBuildpack> buildpacks) {
this.buildpacks = buildpacks;
}
public void setServices(List<CFServiceInstance> serviceInstances) {
this.serviceInstances = serviceInstances;
}
}
}

View File

@@ -1,128 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.manifest.yaml;
import static org.junit.Assert.assertEquals;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
/**
* Basic CF tests for services and buildpacks, using a basic CF client that
* requires no actual CF connection.
*
* <p/>
* This is an alternative to using mockito, and also tests that the
* vscode-manifest framework can take any clients, including the basic one used
* in this test, and still function as expected for CF content assist and
* reconcile
*
*/
public class ManifestYamlEditorCFBasicTest {
LanguageServerHarness harness;
BasicCfClientHarness basicCfClientHarness = new BasicCfClientHarness();
@Before
public void setup() throws Exception {
harness = new LanguageServerHarness(() -> new ManifestYamlLanguageServer(
basicCfClientHarness.getBasicClientFactory(), basicCfClientHarness.getParamsProvider()));
harness.intialize(null);
}
@Test
public void contentAssistBuildpack() throws Exception {
basicCfClientHarness.addBuildpacks("java_buildpack");
assertContainsCompletions("buildpack: <*>", "buildpack: java_buildpack<*>");
}
@Test
public void contentAssistDoesNotContainBuildpack() throws Exception {
basicCfClientHarness.addBuildpacks("java_buildpack");
assertDoesNotContainCompletions("buildpack: <*>", "buildpack: wrong_buildpack<*>");
}
@Test
public void contentAssistServices() throws Exception {
basicCfClientHarness.addServiceInstances("mysql");
assertContainsCompletions("services:\n" + " - <*>", "mysql");
}
@Test
public void contentAssistDoesNotContainServices() throws Exception {
basicCfClientHarness.addServiceInstances("mysql");
assertDoesNotContainCompletions("services:\n" + " - <*>", "wrongsql");
}
@Test
public void contentAssistDoesNotContainServicesEmptyServices() throws Exception {
basicCfClientHarness.addServiceInstances(/*no services*/);
assertDoesNotContainCompletions("services:\n" + " - <*>", "mysql");
}
@Test
public void reconcileCFService() throws Exception {
basicCfClientHarness.addServiceInstances("myservice");
Editor editor = harness.newEditor("applications:\n" //
+ "- name: foo\n" //
+ " services:\n" //
+ " - myservice\n" //
);
// Should have no problems
editor.assertProblems(/* none */);
}
@Test
public void reconcileShowsWarningOnUnknownService() throws Exception {
basicCfClientHarness.addServiceInstances("myservice");
Editor editor = harness.newEditor("applications:\n" //
+ "- name: foo\n" //
+ " services:\n" //
+ " - bad-service\n" //
);
editor.assertProblems("bad-service|There is no service instance called");
Diagnostic problem = editor.assertProblem("bad-service");
assertEquals(DiagnosticSeverity.Warning, problem.getSeverity());
}
@Test
public void reconcileShowsWarningOnEmptyServices() throws Exception {
// Add empty list of services
basicCfClientHarness.addServiceInstances();
Editor editor = harness.newEditor("applications:\n" //
+ "- name: foo\n" //
+ " services:\n" //
+ " - bad-service\n");//
editor.assertProblems("bad-service|There is no service instance called");
Diagnostic problem = editor.assertProblem("bad-service");
assertEquals(DiagnosticSeverity.Warning, problem.getSeverity());
}
//////////////////////////////////////////////////////////////////////////////
private void assertContainsCompletions(String textBefore, String... textAfter) throws Exception {
Editor editor = harness.newEditor(textBefore);
editor.assertContainsCompletions(textAfter);
}
private void assertDoesNotContainCompletions(String textBefore, String... notToBeFound) throws Exception {
Editor editor = harness.newEditor(textBefore);
editor.assertDoesNotContainCompletions(notToBeFound);
}
}

View File

@@ -12,30 +12,25 @@ package org.springframework.ide.vscode.manifest.yaml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.List;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.*;
import com.google.common.collect.ImmutableList;
public class ManifestYamlEditorTest {
@@ -896,6 +891,51 @@ public class ManifestYamlEditorTest {
//query string should match the 'filter text' otherwise vscode will filter the item and it will be gone!
assertEquals("something", completion.getFilterText());
}
@Test
public void serviceContentAssistEmptyServices() throws Exception {
ClientRequests cfClient = cloudfoundry.client;
when(cfClient.getServices()).thenReturn(ImmutableList.of());
assertDoesNotContainCompletions("services:\n" + " - <*>", "mysql");
}
@Test
public void serviceContentAssistDoesNotContainServices() throws Exception {
ClientRequests cfClient = cloudfoundry.client;
CFServiceInstance service = Mockito.mock(CFServiceInstance.class);
when(service.getName()).thenReturn("mysql");
when(cfClient.getServices()).thenReturn(ImmutableList.of(service));
assertDoesNotContainCompletions("services:\n" + " - <*>", "wrongsql");
}
@Test
public void serviceContentAssist() throws Exception {
ClientRequests cfClient = cloudfoundry.client;
CFServiceInstance service = Mockito.mock(CFServiceInstance.class);
when(service.getName()).thenReturn("mysql");
when(cfClient.getServices()).thenReturn(ImmutableList.of(service));
assertContainsCompletions("services:\n" + " - <*>", "mysql");
}
@Test
public void buildpackContentAssist() throws Exception {
ClientRequests cfClient = cloudfoundry.client;
CFBuildpack buildPack = Mockito.mock(CFBuildpack.class);
when(buildPack.getName()).thenReturn("java_buildpack");
when(cfClient.getBuildpacks()).thenReturn(ImmutableList.of(buildPack));
assertContainsCompletions("buildpack: <*>", "buildpack: java_buildpack<*>");
}
@Test
public void buildpackContentAssistDoesNotContainCompletion() throws Exception {
ClientRequests cfClient = cloudfoundry.client;
CFBuildpack buildPack = Mockito.mock(CFBuildpack.class);
when(buildPack.getName()).thenReturn("java_buildpack");
when(cfClient.getBuildpacks()).thenReturn(ImmutableList.of(buildPack));
assertDoesNotContainCompletions("buildpack: <*>", "buildpack: wrong_buildpack<*>");
}
//////////////////////////////////////////////////////////////////////////////
@@ -903,5 +943,15 @@ public class ManifestYamlEditorTest {
Editor editor = harness.newEditor(textBefore);
editor.assertCompletions(textAfter);
}
private void assertDoesNotContainCompletions(String textBefore, String... notToBeFound) throws Exception {
Editor editor = harness.newEditor(textBefore);
editor.assertDoesNotContainCompletions(notToBeFound);
}
private void assertContainsCompletions(String textBefore, String... textAfter) throws Exception {
Editor editor = harness.newEditor(textBefore);
editor.assertContainsCompletions(textAfter);
}
}