/**
* Copyright (C) 2015 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.francetelecom.clara.cloud.activation.plugin.cf.infrastructure;
import com.francetelecom.clara.cloud.activation.plugin.cf.domain.ServiceActivationStatus;
import com.francetelecom.clara.cloud.archive.ManageArchive;
import com.francetelecom.clara.cloud.commons.MavenReference;
import com.francetelecom.clara.cloud.commons.TechnicalException;
import com.francetelecom.clara.cloud.techmodel.cf.*;
import com.francetelecom.clara.cloud.techmodel.cf.services.managed.ManagedService;
import com.google.common.net.InternetDomainName;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.cloudfoundry.client.lib.*;
import org.cloudfoundry.client.lib.domain.*;
import org.cloudfoundry.client.lib.domain.CloudApplication.AppState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.HttpServerErrorException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
/**
*
*/
public class CfAdapterImpl implements CfAdapter {
public static final Logger logger = LoggerFactory.getLogger(CfAdapterImpl.class);
private static final int MAX_RETRY = 5;
private FileFetcherUtil fileFetcherUtil;
protected URL target;
protected boolean isUsingHttpProxy;
protected String httpProxyHost;
protected int httpProxyPort;
protected String email;
protected String password;
protected boolean trustSelfSignedCerts;
protected String org;
protected String space;
protected InternetDomainName normalizedDomain;
protected String domain;
@Autowired
private ManageArchive archiver;
/**
* @param httpProxyHost
* @param httpProxyPort
* @param target
* @param email
* @param password
* @param org CloudFoundry space to use
* @param space CloudFoundry space to use. Expected to be already created.
* @param domain optional DNS domain for app (e.g.
* jee-probe.qa.cfapps.elpaaso.net or jee-probe.qa.cfapps.io)
* @param trustSelfSignedCerts TODO
*/
public CfAdapterImpl(String httpProxyHost, int httpProxyPort, URL target, String email, String password, String org, String space, String domain, Boolean trustSelfSignedCerts) {
this.httpProxyHost = httpProxyHost;
this.httpProxyPort = httpProxyPort;
if (target == null) {
throw new TechnicalException("mandatory target is null");
}
this.target = target;
if (email == null) {
throw new TechnicalException("mandatory email is null");
}
this.email = email;
if (password == null) {
throw new TechnicalException("mandatory password is null");
}
this.password = password;
if (org == null) {
throw new TechnicalException("mandatory org is null");
}
this.org = org;
this.space = space;
if (domain == null || domain.isEmpty()) {
throw new TechnicalException("mandatory domain is null or empty");
}
if (trustSelfSignedCerts == null) {
throw new TechnicalException("mandatory trustSelfSignedCerts is null or empty");
}
this.trustSelfSignedCerts = trustSelfSignedCerts;
normalizedDomain = InternetDomainName.from(domain); // as optimization,
// cache normalized
// value to ease
// safe normalized
// comparisons
this.domain = normalizedDomain.name();
}
protected CloudFoundryOperations login() {
logger.info("Running on " + target + " on behalf of " + email);
logger.info("Using space " + space + " of organization " + org + " with domain " + domain);
CloudCredentials cloudCredentials = new CloudCredentials(email, password);
HttpProxyConfiguration httpProxyConfiguration = httpProxyConfiguration();
CloudFoundryClient clientWithinTargetSpace = new CloudFoundryClient(cloudCredentials, target, org, space, httpProxyConfiguration, trustSelfSignedCerts);
clientWithinTargetSpace.login();
// createDomain(domain);
return clientWithinTargetSpace;
}
protected CloudFoundryOperations login(String spaceName) {
logger.info("Running on " + target + " on behalf of " + email);
logger.info("Using space " + spaceName + " of organization " + org + " with domain " + domain);
CloudCredentials cloudCredentials = new CloudCredentials(email, password);
HttpProxyConfiguration httpProxyConfiguration = httpProxyConfiguration();
CloudFoundryClient clientWithinTargetSpace = new CloudFoundryClient(cloudCredentials, target, org, spaceName, httpProxyConfiguration, trustSelfSignedCerts);
clientWithinTargetSpace.login();
// createDomain(domain);
return clientWithinTargetSpace;
}
public HttpProxyConfiguration httpProxyConfiguration() {
logger.info("Connection settings: isUsingHttpProxy=" + isUsingHttpProxy + " httpProxyHost=" + httpProxyHost + " httpProxyPort=" + httpProxyPort);
HttpProxyConfiguration httpProxyConfiguration;
if (isUsingHttpProxy && (getHttpProxyHost() != null)) {
logger.debug("proxy is active");
httpProxyConfiguration = new HttpProxyConfiguration(httpProxyHost, httpProxyPort);
} else {
logger.debug("proxy is NOT active");
httpProxyConfiguration = null;
}
return httpProxyConfiguration;
}
public String getDomain() {
return domain;
}
@Override
public String getHttpProxyHost() {
return httpProxyHost;
}
@Override
public int getHttpProxyPort() {
return httpProxyPort;
}
public String getEmail() {
return email;
}
public String getOrg() {
return org;
}
public String getPassword() {
return password;
}
public String getSpace() {
return space;
}
public URL getTarget() {
return target;
}
@Override
public UUID createApp(final App app, String spaceName) {
final CloudFoundryOperations cfClient = login(spaceName);
try {
createApplicationWithRoutes(app, cfClient);
adjustApplicationInstance(app, cfClient);
addEnvironmentVariableToApplication(app, cfClient);
uploadApplicationBinaries(app, cfClient);
CloudApplication application = cfClient.getApplication(app.getAppName());
return application.getMeta().getGuid();
} finally {
cfClient.logout();
}
}
private void adjustApplicationInstance(App app, CloudFoundryOperations cfClient) {
int instanceCount = app.getInstanceCount();
if (instanceCount > 1) {
logger.debug("Multiple instance requested for {}. Updating instance count to {}",app.getAppName(),instanceCount);
cfClient.updateApplicationInstances(app.getAppName(), instanceCount);
}
}
private void uploadApplicationBinaries(final App app, final CloudFoundryOperations cfClient) {
FileFetcherUtil.FileProcessor fileProcessor = getUploader(app, cfClient);
MavenReference appBinaries = app.getAppBinaries();
if (appBinaries.getAccessUrl() == null && app.isOptionalApplicationBinaries()) {
generateApplicationBinariesAndApplyProcessing(appBinaries, fileProcessor);
} else {
logger.debug("upload application binaries from Maven for {}",app.getAppName());
fileFetcherUtil.fetchMavenReferenceAndApplyProcessing(appBinaries, fileProcessor);
}
}
private void generateApplicationBinariesAndApplyProcessing(MavenReference appBinariesRef, FileFetcherUtil.FileProcessor fileProcessor) {
logger.info("Generating default application for {}", appBinariesRef);
File appBinaries;
switch (appBinariesRef.getExtension()) {
case "ear":
appBinaries = archiver.generateMinimalEar(appBinariesRef, "/");
break;
case "jar":
logger.warn("Jar type detected for {}, but generating default war!", appBinariesRef);
case "war":
appBinaries = archiver.generateMinimalWar(appBinariesRef, "/");
break;
default:
throw new TechnicalException("Cannot generate default application for " + appBinariesRef + ". Unsupported extension: " + appBinariesRef.getExtension());
}
logger.info("Trying to upload {} for {}", appBinaries, appBinariesRef);
fileFetcherUtil.readFileAndApplyProcessing(appBinaries, fileProcessor);
FileUtils.deleteQuietly(appBinaries.getParentFile());
}
private FileFetcherUtil.FileProcessor getUploader(final App app, final CloudFoundryOperations cfClient) {
return new FileFetcherUtil.FileProcessor() {
@Override
public void process(String filename, String filetype, File file) {
String canonicalPath;
try {
canonicalPath = file.getCanonicalPath();
} catch (IOException e) {
throw new TechnicalException("unable to display file path:" + file, e);
}
if (!file.exists()) {
throw new TechnicalException("Expected valid app file at: " + canonicalPath);
}
try {
cfClient.uploadApplication(app.getAppName(), canonicalPath);
} catch (IOException e) {
throw new TechnicalException("Unable to upload file at: " + canonicalPath, e);
}
}
};
}
private void addEnvironmentVariableToApplication(App app, CloudFoundryOperations cfClient) {
Map<String, String> optionsMap1 = new HashMap<String, String>();
// java-buildpack debugging. Prints out the buildpack git command and additional traces.
logger.debug("prepare debug env variable");
optionsMap1.put("JBP_LOG_LEVEL", "DEBUG");
// optionsMap.put("DEBUG_TOGIST_CMD",
// "echo customized;date;vmstat;ps -AF --cols=2000;vmstat -s");
Map<String, String> optionsMap = optionsMap1;
//FIXME : should variables like JBP_LOG_LEVEL set @projection level ?
logger.debug("prepare env variable wi");
for (Map.Entry<EnvVariableKey, EnvVariableValue> var : app.listEnvVariables().entrySet()) {
optionsMap.put(var.getKey().getKey(), var.getValue().getValue());
}
logger.debug("set declared env variable for {}",app.getAppName());
cfClient.updateApplicationEnv(app.getAppName(), optionsMap);
}
private void createApplicationWithRoutes(App app, CloudFoundryOperations cfClient) {
String buildPackUrl = app.getBuildPackUrl();
String stack = app.getStack();
int healthCheckTimeOut = 180; // TODO: externalize this ? needed by
// low startup app (like elpaaso)
Staging staging = new Staging(null, buildPackUrl, stack, healthCheckTimeOut);
MavenReference appBinaries = app.getAppBinaries();
Set<Route> routes = app.getRoutes();
List<String> uris = new ArrayList<String>();
for (Route route : routes) {
uris.add(route.getUri());
}
logger.info("Provisionning app: {}" + app);
cfClient.createApplication(app.getAppName(), staging, app.getDiskSizeMb(),app.getRamMb(), uris, app.getServiceNames());
}
@Override
public int peekAppStartStatus(int instanceCount, String appName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
boolean pass = false;
try {
logger.info("checking if all " + instanceCount + " instance(s) of " + appName + " have started...");
InstancesInfo instances = getInstancesWithTimeout(cfClient, appName);
if (instances == null) {
logger.info("Null InstanceInfo returned, staging is in progress or has failed. Will retry");
return 0;
}
List<InstanceInfo> infos = instances.getInstances();
int currentNbInstances = 0;
if (instances != null) {
currentNbInstances = instances.getInstances().size();
}
if (currentNbInstances == 0) {
logger.warn("No instances returned, staging is in progress or has failed. Will retry");
return 0;
}
if (currentNbInstances != instanceCount) {
logger.error("expected " + instanceCount + " instances , but only got:" + currentNbInstances);
}
int passCount = 0;
int instanceIndex = 0;
for (InstanceInfo info : infos) {
InstanceState state1 = info.getState();
if (InstanceState.RUNNING.equals(state1)) {
passCount++;
logger.info("app " + appName + " instance#" + instanceIndex + " is now in desired state:" + state1);
} else {
logger.info("app " + appName + " instance#" + instanceIndex + " is still in undesired state:" + state1);
}
instanceIndex++;
}
return passCount;
} catch (StagingErrorException e) {
// No need to wait more, the staging failed.
String msg = "Unable to start app, caught unrecoverable exception:" + e;
throw new TechnicalException(msg, e);
} catch (CloudFoundryException ex) {
// ignore (we may get this when staging is still ongoing)
if (ex instanceof NotFinishedStagingException) {
logger.debug("Start status of " + appName + " not yet ready: " + ex);
} else {
logger.info("Issue checking start status of " + appName + " caught: " + ex, ex);
}
return 0;
}
} finally {
cfClient.logout();
}
}
@Override
public void startApp(App cfApp, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
CloudApplication.AppState state;
logger.info("Starting app with name=" + cfApp.getAppName());
try {
StartingInfo info = cfClient.startApplication(cfApp.getAppName());
CloudApplication app;
app = cfClient.getApplication(cfApp.getAppName());
state = app.getState();
logger.info("app is in state " + state);
logger.info("app staging logs are in:" + info.getStagingFile());
try {
String logs = cfClient.getStagingLogs(info, 0);
logger.info("app staging logs:" + logs);
} catch (Exception e) {
logger.info("unable to get app staging logs:" + e);
}
logger.info("app uris are:" + app.getUris());
int count;
count = cfApp.getInstanceCount();
if (count > 1) {
cfClient.updateApplicationInstances(cfApp.getAppName(), count);
app = cfClient.getApplication(cfApp.getAppName());
if (count != app.getInstances()) {
logger.error("expected instances to be updated to:" + count + ", got:" + app.getInstances());
}
}
} catch (Exception e) {
throw new TechnicalException("unable to start app:" + cfApp.getAppName(), e);
}
if (!CloudApplication.AppState.STARTED.equals(state)) {
throw new TechnicalException("Unexpected state after start:" + state);
}
} finally {
cfClient.logout();
}
}
@Override
public void logAppDiagnostics(String appName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
try {
List<ApplicationLog> crashLogs = cfClient.getRecentLogs(appName);
logger.info("Crashlogs for " + appName + " are:\n" + crashLogs);
} catch (Exception e) {
logger.info("Unable to log diagnostic details (crashlogs) for app=" + appName + ", caught:" + e, e);
}
try {
String jonasLogDirPath = "app/.jonas_base/logs/";
String logsDirContent = cfClient.getFile(appName, 0, jonasLogDirPath);
logger.info("logs dir content: \n{}", logsDirContent);
String jonasLogFileName = getJonasLogFileName(new Date());
if (logsDirContent != null && logsDirContent.contains(jonasLogFileName)) {
String jonasLogsContent = cfClient.getFile(appName, 0, jonasLogDirPath + jonasLogFileName);
logger.info("jonasLogs Content: \n{}", jonasLogsContent);
}
} catch (Exception e) {
logger.info("Unable to log diagnostic details (jonasLogs) for app=" + appName + ", caught:" + e);
}
try {
String buildpackDiagnosticLogsPath = "app/.buildpack-diagnostics/buildpack.log";
String buildpackDiagnosticLogs = cfClient.getFile(appName, 0, buildpackDiagnosticLogsPath);
logger.info("buildpackDiagnosticLogs content: \n{}", buildpackDiagnosticLogs);
} catch (Exception e) {
logger.info("Unable to log diagnostic details (buildpack diagnostic logs) for app=" + appName + ", caught:" + e);
}
try {
ApplicationStats applicationStats = cfClient.getApplicationStats(appName);
List<InstanceStats> records = applicationStats.getRecords();
int i = 0;
for (InstanceStats record : records) {
logger.info("Stats for instance #" + i + " of App " + appName + " are: " + ReflectionToStringBuilder.toString(record));
i++;
}
} catch (Exception e) {
logger.info("Unable to log diagnostic details (app stats) for app=" + appName + ", caught:" + e, e);
}
} finally {
cfClient.logout();
}
}
public String getJonasLogFileName(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String formattedDate = simpleDateFormat.format(date);
return "singleServerName-" + formattedDate + ".3.log";
}
@Override
public void stopApp(App cfApp, String spaceName) {
CloudApplication app;
String appName = cfApp.getAppName();
logger.info("Stopping app with name=" + appName);
CloudFoundryOperations cfClient = login(spaceName);
try {
try {
cfClient.stopApplication(appName);
} catch (Exception e) {
throw new TechnicalException("unable to stop app:" + appName, e);
}
app = cfClient.getApplication(appName);
CloudApplication.AppState state = app.getState();
logger.info("app is in state " + state);
if (!CloudApplication.AppState.STOPPED.equals(state)) {
throw new TechnicalException("Unexpected state after start:" + state);
}
} finally {
cfClient.logout();
}
}
@Override
public void deleteApp(App app, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
String appName = app.getAppName();
deleteRoutesAndSubdomain(app, spaceName);
logger.info("Deleting app with name=" + appName);
try {
cfClient.deleteApplication(appName);
} catch (Exception e) {
logger.warn("unable to delete app:" + appName, e);
}
} finally {
cfClient.logout();
}
}
protected void deleteRoutesAndSubdomain(App app, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
Set<Route> routes = app.getRoutes();
Set<String> subdomainsToDelete = new HashSet<String>();
for (Route route : routes) {
logger.info("Deleting route with uri=" + route.getUri());
if (!InternetDomainName.from(route.getDomain()).equals(normalizedDomain)) {
subdomainsToDelete.add(route.getDomain());
}
try {
cfClient.deleteRoute(route.getHost(), route.getDomain());
} catch (Exception e) {
logger.warn("unable to delete route host=" + route.getHost() + " domain=" + route.getDomain() + " caught:" + e, e);
// proceed
}
}
for (String subdomain : subdomainsToDelete) {
try {
logger.info("deleting subdomain=" + subdomain);
cfClient.deleteDomain(subdomain);
} catch (Exception e) {
logger.info("unable to delete domain=" + subdomain + " Might be in use by another app? Caught:" + e, e);
// proceed
}
}
} finally {
cfClient.logout();
}
}
private InstancesInfo getInstancesWithTimeout(CloudFoundryOperations client, String appName) {
int timeoutMs = 1 * 60 * 1000;
long start = System.currentTimeMillis();
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
// ignore
}
try {
return client.getApplicationInstances(appName);
} catch (HttpServerErrorException e) {
// error 500, keep waiting
}
long startWaitTime = System.currentTimeMillis() - start;
if (startWaitTime > timeoutMs) {
String msg = "Timed out waiting for app startup:" + startWaitTime + " ms (max is:" + timeoutMs + ")";
logger.error(msg);
throw new TechnicalException(msg);
}
}
}
public boolean isUsingHttpProxy() {
return isUsingHttpProxy;
}
public void setUsingHttpProxy(boolean usingHttpProxy) {
isUsingHttpProxy = usingHttpProxy;
}
public void setFileFetcherUtil(FileFetcherUtil fileFetcherUtil) {
this.fileFetcherUtil = fileFetcherUtil;
}
@Override
public boolean domainExists(String domainNameToCreate, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
List<CloudDomain> existingDomains = cfClient.getDomainsForOrg();
for (CloudDomain existingDomain : existingDomains) {
if (domainNameToCreate.equals(existingDomain.getName())) {
return true;
}
}
logger.info("Did not find {} within list of domains present on cf: {}", domainNameToCreate, existingDomains);
return false;
} finally {
cfClient.logout();
}
}
@Override
public void createService(UserProvidedService service, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
CloudService cloudService = new CloudService(null, service.getServiceName());
String syslogDrainUrl = service.getLogUrl();
cfClient.createUserProvidedService(cloudService, service.getCredentials(),syslogDrainUrl);
} finally {
cfClient.logout();
}
}
@Override
public boolean serviceExists(String serviceName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
return cfClient.getService(serviceName) != null;
} finally {
cfClient.logout();
}
}
@Override
public void deleteService(String serviceName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
// delete cloud service
// will unbind service from all bound application if exists
cfClient.deleteService(serviceName);
} finally {
cfClient.logout();
}
}
@Override
public void deleteAllServices(String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
cfClient.deleteAllServices();
} finally {
cfClient.logout();
}
}
@Override
public void bindService(final String appName, final String serviceName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
cfClient.bindService(appName, serviceName);
} finally {
cfClient.logout();
}
}
@Override
public void unbindService(final String appName, final String serviceName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
cfClient.unbindService(appName, serviceName);
} finally {
cfClient.logout();
}
}
@Override
public boolean isServiceBound(final String appName, final String serviceName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
CloudApplication cloudApplication = cfClient.getApplication(appName);
if (cloudApplication == null)
throw new TechnicalException("application <" + appName + "> not found");
List<String> services = cloudApplication.getServices();
if (services == null)
return false;
return services.contains(serviceName);
} finally {
cfClient.logout();
}
}
public void addDomain(String newNormalizedDomain, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
boolean targetDomainExists = false;
List<CloudDomain> domainsForOrg = cfClient.getDomainsForOrg();
for (CloudDomain browsedDomain : domainsForOrg) {
String browsedDomainName = browsedDomain.getName();
String normalizedBrowsedDomainName = InternetDomainName.from(browsedDomainName).name();
if (newNormalizedDomain.equals(normalizedBrowsedDomainName)) {
targetDomainExists = true;
logger.info("Found domain {} bound to org {}, no need to register it", browsedDomain, getOrg());
break;
}
}
if (!targetDomainExists) {
logger.info("Did not find domain {} bound to org {} among {}, registering one", new Object[]{newNormalizedDomain, domainsForOrg, getOrg()});
try {
cfClient.addDomain(newNormalizedDomain);
} catch (Exception e) {
throw new TechnicalException("Unable to register domain: " + newNormalizedDomain + " for this paas instance, caught:" + e
+ " Please check another Paas instance/test has not reserved the same domain on the same CF instance.", e);
}
}
} finally {
cfClient.logout();
}
}
@Override
public boolean appExists(String appName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
return (cfClient.getApplication(appName) != null);
} catch (CloudFoundryException e) {
if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
return false;
} else {
throw e;
}
} finally {
cfClient.logout();
}
}
@Override
public boolean isAppStarted(String appName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
return (AppState.STARTED.equals(cfClient.getApplication(appName).getState()));
} finally {
cfClient.logout();
}
}
@Override
public boolean isAppStopped(String appName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
return (AppState.STOPPED.equals(cfClient.getApplication(appName).getState()));
} finally {
cfClient.logout();
}
}
@Override
public void createService(ManagedService service, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
CloudService cloudService = new CloudService(null, service.getServiceInstance());
cloudService.setLabel(service.getService());
cloudService.setPlan(service.getPlan());
cfClient.createService(cloudService);
} finally {
cfClient.logout();
}
}
@Override
public void createSpace(SpaceName spaceName) {
CloudFoundryOperations cfClient = login();
try {
logger.info("creating cloud foundry space <" + spaceName + ">");
cfClient.createSpace(spaceName.getValue());
logger.info("cloud foundry space <" + spaceName + "> has been created.");
} finally {
cfClient.logout();
}
}
@Override
public void deleteSpace(SpaceName spaceName) {
CloudFoundryOperations cfClient = login();
try {
logger.info("deleting cloud foundry space <" + spaceName + ">");
cfClient.deleteSpace(spaceName.getValue());
logger.info("cloud foundry space <" + spaceName + "> has been deleted.");
} finally {
cfClient.logout();
}
}
@Override
public boolean spaceExists(SpaceName spaceName) {
CloudFoundryOperations cfClient = login();
try {
return (cfClient.getSpace(spaceName.getValue()) != null);
} finally {
cfClient.logout();
}
}
@Override
public void associateManagerWithSpace(SpaceName spaceName) {
CloudFoundryOperations cfClient = login(spaceName.getValue());
try {
logger.info("associating manager role to cloud foundry space <" + spaceName + "> ...");
cfClient.associateManagerWithSpace(spaceName.getValue());
} finally {
cfClient.logout();
}
}
@Override
public String getCurrentOrganizationName() {
return getOrg();
}
@Override
public ServiceActivationStatus getServiceInstanceState(String serviceName, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
logger.info("getting activation status for service <" + serviceName + "> in space <" + spaceName + "> ...");
final CloudServiceInstance serviceInstance = cfClient.getServiceInstance(serviceName);
return getServiceActivationStatus(serviceName, spaceName, serviceInstance);
} finally {
cfClient.logout();
}
}
protected ServiceActivationStatus getServiceActivationStatus(String serviceName, String spaceName, CloudServiceInstance serviceInstance) {
if (serviceInstance == null) {
logger.warn("Cannot get status for service <{}> in space <{}>. Will suppose service is deleted and thus last operation state is SUCCEEDED>", serviceName, spaceName);
return ServiceActivationStatus.ofService(serviceName, spaceName).hasSucceeded();
}
if (OperationState.IN_PROGRESS.equals(serviceInstance.getLastOperation().getState()))
return ServiceActivationStatus.ofService(serviceName, spaceName).isPending(serviceInstance.getLastOperation().getType() + " is still in progress...");
if (OperationState.SUCCEEDED.equals(serviceInstance.getLastOperation().getState()))
return ServiceActivationStatus.ofService(serviceName, spaceName).hasSucceeded();
if (OperationState.FAILED.equals(serviceInstance.getLastOperation().getState()))
return ServiceActivationStatus.ofService(serviceName, spaceName).hasFailed(serviceInstance.getLastOperation().getType() + " has failed");
throw new TechnicalException("unable to extract service activation status. service instance state <" + serviceInstance.getLastOperation().getState() + "> is unknown");
}
@Override
public void associateDeveloperWithSpace(SpaceName spaceName) {
CloudFoundryOperations cfClient = login(spaceName.getValue());
try {
logger.info("associating developer role to cloud foundry space <" + spaceName + "> ...");
cfClient.associateDeveloperWithSpace(spaceName.getValue());
} finally {
cfClient.logout();
}
}
@Override
public void associateAuditorWithSpace(SpaceName spaceName) {
CloudFoundryOperations cfClient = login(spaceName.getValue());
try {
logger.info("associating auditor role to cloud foundry space <" + spaceName + "> ...");
cfClient.associateAuditorWithSpace(spaceName.getValue());
} finally {
cfClient.logout();
}
}
@Override
public SpaceName getValidSpaceName(String nameSuffix) {
CloudFoundryOperations cfClient = login();
try {
int retry = 0;
while (retry < MAX_RETRY) {
SpaceName randomSpaceName = SpaceName.randomSpaceNameWithSuffix(nameSuffix);
if (cfClient.getSpace(randomSpaceName.getValue()) == null)
return randomSpaceName;
retry++;
}
throw new TechnicalException("Fail to get a valid space name after " + MAX_RETRY + " attempts.");
} finally {
cfClient.logout();
}
}
@Override
public boolean routeExists(Route route, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
try {
List<CloudRoute> routes = cfClient.getRoutes(route.getDomain());
logger.info("found routes " + routes + " for domain <" + route.getDomain() + ">");
if (routes == null || routes.size() == 0)
return false;
for (CloudRoute existingRoute : routes) {
if (existingRoute.getHost().equals(route.getHost()))
return true;
}
} catch (IllegalArgumentException e) {
// raised when domain not found
return false;
}
return false;
} finally {
cfClient.logout();
}
}
@Override
public void deleteRoute(Route route, String spaceName) {
CloudFoundryOperations cfClient = login(spaceName);
try {
cfClient.deleteRoute(route.getHost(), route.getDomain());
} finally {
cfClient.logout();
}
}
@Override
public RouteUri createRoute(final Route route, String spaceName) {
final CloudFoundryOperations cfClient = login(spaceName);
try {
return getRetryTemplate(MAX_RETRY).execute(new RetryCallback<RouteUri, CloudFoundryException>() {
@Override
public RouteUri doWithRetry(RetryContext context) throws CloudFoundryException {
if (context.getRetryCount() == 0) {
logger.info("creating cloud foundry route with uri <" + route.getUri() + ">");
cfClient.addRoute(route.getHost(), route.getDomain());
return new RouteUri(route.getUri());
} else {
RouteUri candidateRouteUri = route.candidateRouteUri();
logger.info("creating cloud foundry route with uri <" + candidateRouteUri + ">");
cfClient.addRoute(candidateRouteUri.getHost(), candidateRouteUri.getDomain());
return candidateRouteUri;
}
}
});
} finally {
cfClient.logout();
}
}
/**
* get a spring customized retry template
*
* @param retryAttempts
* @return a customized retry template
*/
private static RetryTemplate getRetryTemplate(int retryAttempts) {
// we set the retry time out
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(retryAttempts);
// our retry service
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}