Merge branch 'master' of github.com:spring-projects/sts4
This commit is contained in:
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user