Java tutorial
/* * Copyright (C) 2012-2013 CloudJee, Inc. All rights reserved. * * 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.wavemaker.tools.deployment.cloudfoundry; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.zip.ZipFile; import javax.servlet.http.Cookie; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.client.lib.CloudApplication; import org.cloudfoundry.client.lib.CloudFoundryClient; import org.cloudfoundry.client.lib.CloudFoundryException; import org.cloudfoundry.client.lib.CloudService; import org.cloudfoundry.client.lib.archive.ApplicationArchive; import org.cloudfoundry.client.lib.archive.ZipApplicationArchive; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.util.WebUtils; import com.wavemaker.common.WMRuntimeException; import com.wavemaker.runtime.RuntimeAccess; import com.wavemaker.runtime.data.util.DataServiceConstants; import com.wavemaker.tools.cloudfoundry.CloudFoundryUtils; import com.wavemaker.tools.cloudfoundry.spinup.ProjectnameWithRandomApplicationNamingStrategy; import com.wavemaker.tools.cloudfoundry.spinup.authentication.AuthenticationToken; import com.wavemaker.tools.cloudfoundry.spinup.authentication.SharedSecret; import com.wavemaker.tools.cloudfoundry.spinup.authentication.SharedSecretPropagation; import com.wavemaker.tools.cloudfoundry.spinup.authentication.TransportToken; import com.wavemaker.tools.data.BaseDataModelSetup; import com.wavemaker.tools.data.DataModelConfiguration; import com.wavemaker.tools.data.DataModelManager; import com.wavemaker.tools.deployment.DeploymentDB; import com.wavemaker.tools.deployment.DeploymentInfo; import com.wavemaker.tools.deployment.DeploymentStatusException; import com.wavemaker.tools.deployment.DeploymentTarget; import com.wavemaker.tools.deployment.cloudfoundry.LoggingStatusCallback.Timer; import com.wavemaker.tools.deployment.cloudfoundry.archive.ContentModifier; import com.wavemaker.tools.deployment.cloudfoundry.archive.ModifiedContentApplicationArchive; import com.wavemaker.tools.deployment.cloudfoundry.archive.StringReplaceContentModifier; import com.wavemaker.tools.io.local.LocalFolder; import com.wavemaker.tools.project.CloudFoundryDeploymentManager; import com.wavemaker.tools.project.Project; public class CloudFoundryDeploymentTarget implements DeploymentTarget { public static final String SUCCESS_RESULT = "SUCCESS"; public static final String TOKEN_EXPIRED_RESULT = "ERROR: CloudFoundry login token expired"; public static final String VMC_USERNAME_PROPERTY = "username"; public static final String VMC_PASSWORD_PROPERTY = "password"; public static final String VMC_URL_PROPERTY = "url"; public static final Map<String, String> CONFIGURABLE_PROPERTIES; private static final String DEFAULT_URL = "https://api.cloudfoundry.com"; private static final String SERVICE_TYPE = "database"; public static final String MYSQL_SERVICE_VENDOR = "mysql"; public static final String MYSQL_SERVICE_VERSION = "5.1"; public static final String POSTGRES_SERVICE_VENDOR = "postgresql"; public static final String POSTGRES_SERVICE_VERSION = "9.0"; private static final String SERVICE_TIER = "free"; private static final String TESTRUN_APP_NAME = "TestRun"; private static final Log log = LogFactory.getLog(CloudFoundryDeploymentTarget.class); private DataModelManager dataModelManager; private WebAppAssembler webAppAssembler; private final SharedSecretPropagation propagation = new SharedSecretPropagation(); static { Map<String, String> props = new LinkedHashMap<String, String>(); props.put(VMC_USERNAME_PROPERTY, "username@mydomain.com"); props.put(VMC_PASSWORD_PROPERTY, "password"); props.put(VMC_URL_PROPERTY, DEFAULT_URL); CONFIGURABLE_PROPERTIES = Collections.unmodifiableMap(props); } public void setDataModelManager(DataModelManager dataModelManager) { this.dataModelManager = dataModelManager; } public void setWebAppAssembler(WebAppAssembler webAppAssembler) { this.webAppAssembler = webAppAssembler; } @Override public void validateDeployment(DeploymentInfo deploymentInfo) throws DeploymentStatusException { CloudFoundryClient client = getClient(deploymentInfo); createApplication(client, deploymentInfo, true); } @Deprecated String deploy(File webapp, DeploymentInfo deploymentInfo) throws DeploymentStatusException { try { validateWar(webapp); ZipFile zipFile = new ZipFile(webapp); ApplicationArchive applicationArchive = new ZipApplicationArchive(zipFile); return doDeploy(applicationArchive, deploymentInfo); } catch (IOException e) { throw new WMRuntimeException(e); } } @Override public String deploy(Project project, DeploymentInfo deploymentInfo, java.io.File tempWebAppRoot) throws DeploymentStatusException { ApplicationArchive applicationArchive; if (tempWebAppRoot == null) { applicationArchive = this.webAppAssembler.assemble(project); applicationArchive = modifyApplicationArchive(applicationArchive); } else { //try { // this.webAppAssembler.prepareForAssemble(new LocalFolder(tempWebAppRoot)); //} catch (IOException ex) { // throw new DeploymentStatusException(ex.getMessage()); //} applicationArchive = this.webAppAssembler.assemble(project.getProjectName(), new FileSystemResource(tempWebAppRoot)); } return doDeploy(applicationArchive, deploymentInfo); } /** * Initiate the test/run operation for the given project on the cloud foundry instance that is running studio. This * method is not part of the {@link DeploymentTarget} interface but is called directly from * {@link CloudFoundryDeploymentManager#testRunStart()} in order to reuse CloudFoundry deployment code. * * @param project * @return */ public String testRunStart(Project project) { try { ApplicationArchive applicationArchive = this.webAppAssembler.assemble(project); applicationArchive = modifyApplicationArchive(applicationArchive); return doDeploy(applicationArchive, getSelfDeploymentInfo(project), false); } catch (DeploymentStatusException e) { throw new WMRuntimeException(e.getMessage(), e); } } /** * undeploy the given project from the cloud foundry instance that is running studio. This method is not part of the * {@link DeploymentTarget} interface but is called directly from {@link CloudFoundryDeploymentManager} in order to * reuse CloudFoundry deployment code. * * @param project */ public void undeploy(Project project) { try { undeploy(getSelfDeploymentInfo(project), false); } catch (DeploymentStatusException e) { throw new WMRuntimeException(e.getMessage(), e); } } private DeploymentInfo getSelfDeploymentInfo(Project project) { try { String cloudControllerUrl = CloudFoundryUtils.getControllerUrl(); AuthenticationToken token = getAuthenticationToken(); if (token == null) { // FIXME remove hard coded details String email = "wavemaker@vmware.com"; String password = "password"; CloudFoundryClient cloudFoundryClient = new CloudFoundryClient(email, password, cloudControllerUrl); token = new AuthenticationToken(cloudFoundryClient.login()); } DeploymentInfo deploymentInfo = new DeploymentInfo(); deploymentInfo.setToken(token.toString()); deploymentInfo.setApplicationName(project.getProjectName()); deploymentInfo.setTarget(cloudControllerUrl); deploymentInfo.setApplicationName(TESTRUN_APP_NAME); return deploymentInfo; } catch (Exception e) { throw new WMRuntimeException(e); } } private AuthenticationToken getAuthenticationToken() { RuntimeAccess runtimeAccess = RuntimeAccess.getInstance(); if (runtimeAccess == null) { return null; } Cookie cookie = WebUtils.getCookie(runtimeAccess.getRequest(), "wavemaker_authentication_token"); if (cookie == null || !StringUtils.hasLength(cookie.getValue())) { return null; } SharedSecret sharedSecret = this.propagation.getForSelf(false); if (sharedSecret == null) { return null; } AuthenticationToken authenticationToken = sharedSecret.decrypt(TransportToken.decode(cookie.getValue())); return authenticationToken; } private ApplicationArchive modifyApplicationArchive(ApplicationArchive applicationArchive) { ContentModifier modifier = new StringReplaceContentModifier() .forEntryName("index.html", "config.js", "login.html").replaceAll("\\/wavemaker\\/", "/"); return new ModifiedContentApplicationArchive(applicationArchive, modifier); } private void uploadAppliation(CloudFoundryClient client, String appName, ApplicationArchive applicationArchive) throws DeploymentStatusException { Timer timer = new Timer(); try { client.uploadApplication(appName, applicationArchive, new LoggingStatusCallback(timer)); log.info("Application upload completed in " + timer.stop() + "ms"); } catch (HttpServerErrorException e) { throw new DeploymentStatusException("ERROR in upload application: " + e.getLocalizedMessage(), e); } catch (IOException ex) { throw new WMRuntimeException("Error ocurred while trying to upload WAR file.", ex); } } private String doDeploy(ApplicationArchive applicationArchive, DeploymentInfo deploymentInfo) throws DeploymentStatusException { return doDeploy(applicationArchive, deploymentInfo, true); } private String doDeploy(ApplicationArchive applicationArchive, DeploymentInfo deploymentInfo, boolean checkExist) throws DeploymentStatusException { try { CloudFoundryClient client = getClient(deploymentInfo); String url = createApplication(client, deploymentInfo, checkExist); log.info("Preparing: " + url); setupServices(client, deploymentInfo); uploadAppliation(client, deploymentInfo.getApplicationName(), applicationArchive); try { CloudApplication application = client.getApplication(deploymentInfo.getApplicationName()); if (application.getState().equals(CloudApplication.AppState.STARTED)) { doRestart(deploymentInfo, client); } else { doStart(deploymentInfo, client); } } catch (CloudFoundryException ex) { throw new DeploymentStatusException("ERROR: Could not start application. " + ex.getDescription(), ex); } if (!url.startsWith("http://")) { url = "http://" + url; } return url; } catch (HttpServerErrorException e) { throw new DeploymentStatusException("ERROR: Clould not deploy application due to remote exception\n" + e.getMessage() + "\n\n" + e.getStatusText()); } } private Boolean appNameInUse(CloudFoundryClient client, String appName) { try { if (client.getApplication(appName) != null) { log.info("ApplicationName: " + appName + " is already in use"); return true; } else { log.info("ApplicatonName with name: " + appName + "was NOT found"); return false; } } catch (HttpClientErrorException e) { log.info("Failed to find aplicatonName with name: " + appName + ". Response was: " + e.getLocalizedMessage()); if (e.getStatusCode() != HttpStatus.NOT_FOUND) { return true; } else { return false; } } } private String createApplication(CloudFoundryClient client, DeploymentInfo deploymentInfo, boolean checkExist) throws DeploymentStatusException { String appName = deploymentInfo.getApplicationName(); if (appName == TESTRUN_APP_NAME) { if (appNameInUse(client, appName)) { deploymentInfo.setDeploymentUrl(client.getApplication(appName).getUris().get(0)); } else { deploymentInfo.setDeploymentUrl(getUrl(deploymentInfo)); } } if (deploymentInfo.getDeploymentUrl() == null || deploymentInfo.getDeploymentUrl().isEmpty()) { deploymentInfo.setDeploymentUrl(getUrl(deploymentInfo)); } String url = deploymentInfo.getDeploymentUrl(); if (checkExist && appNameInUse(client, appName)) { try { client.deleteApplication(appName); } catch (Exception e) { e.printStackTrace(); throw new DeploymentStatusException( "ERROR: Unable to delete existing application by same name. Choose another name"); } } try { client.createApplication(appName, CloudApplication.SPRING, client.getDefaultApplicationMemory(CloudApplication.SPRING), Collections.singletonList(url), null, true); return url; } catch (CloudFoundryException e) { throw new DeploymentStatusException("ERROR in createApplication: " + e.getDescription(), e); } } /** * @param deploymentInfo * @return String generated URL */ @Override public String getUrl(DeploymentInfo deploymentInfo) { String url = deploymentInfo.getTarget(); if (!StringUtils.hasText(url)) { url = DEFAULT_URL; } ProjectnameWithRandomApplicationNamingStrategy pNameStrat = new ProjectnameWithRandomApplicationNamingStrategy( deploymentInfo.getApplicationName()); return url.replace("api", pNameStrat.generateName()); } /** * @param deploymentInfo */ private void setupServices(CloudFoundryClient client, DeploymentInfo deploymentInfo) { if (CollectionUtils.isEmpty(deploymentInfo.getDatabases())) { return; } CloudApplication app = client.getApplication(deploymentInfo.getApplicationName()); for (DeploymentDB db : deploymentInfo.getDatabases()) { if (app.getServices().contains(db.getDbName()) && !db.isUpdateSchema()) { // service binding already exists continue; } if (db.isUpdateSchema()) { try { client.deleteService(db.getDbName()); } catch (Exception ex) { } } String dbType = "NONE"; DataModelConfiguration config = this.dataModelManager.getDataModel(db.getDataModelId()); String url = config.readConnectionProperties().getProperty(DataServiceConstants.DB_URL_KEY, ""); if (StringUtils.hasText(url)) { dbType = BaseDataModelSetup.getDBTypeFromURL(url); } boolean serviceToBind = false; try { CloudService service = client.getService(db.getDbName()); if (dbType.equals(MYSQL_SERVICE_VENDOR)) { Assert.state(MYSQL_SERVICE_VENDOR.equals(service.getVendor()), "There is already a service provisioned with the name '" + db.getDbName() + "' but it is not a MySQL service."); } else if (dbType.equals(POSTGRES_SERVICE_VENDOR)) { Assert.state(POSTGRES_SERVICE_VENDOR.equals(service.getVendor()), "There is already a service provisioned with the name '" + db.getDbName() + "' but it is not a PostgreSQL service."); } serviceToBind = true; } catch (CloudFoundryException ex) { if (ex.getStatusCode() != HttpStatus.NOT_FOUND) { throw ex; } if (dbType.equals(MYSQL_SERVICE_VENDOR)) { client.createService(createMySqlService(db)); serviceToBind = true; } else if (dbType.equals(POSTGRES_SERVICE_VENDOR)) { client.createService(createPostgresqlService(db)); serviceToBind = true; } } if (serviceToBind) { client.bindService(deploymentInfo.getApplicationName(), db.getDbName()); } } } /** * @param db * @return */ private CloudService createMySqlService(DeploymentDB db) { return createMySqlService(db.getDbName()); } /** * @param dbName * @return */ public static CloudService createMySqlService(String dbName) { CloudService mysql = new CloudService(); mysql.setType(SERVICE_TYPE); mysql.setVendor(MYSQL_SERVICE_VENDOR); mysql.setTier(SERVICE_TIER); mysql.setVersion(MYSQL_SERVICE_VERSION); mysql.setName(dbName); return mysql; } /** * @param db * @return */ public static CloudService createPostgresqlService(DeploymentDB db) { return createPostgresqlService(db.getDbName()); } /** * @param dbName * @return */ public static CloudService createPostgresqlService(String dbName) { CloudService postgresql = new CloudService(); postgresql.setType(SERVICE_TYPE); postgresql.setVendor(POSTGRES_SERVICE_VENDOR); postgresql.setTier(SERVICE_TIER); postgresql.setVersion(POSTGRES_SERVICE_VERSION); postgresql.setName(dbName); return postgresql; } @Override public void undeploy(DeploymentInfo deploymentInfo, boolean deleteServices) throws DeploymentStatusException { CloudFoundryClient client = getClient(deploymentInfo); log.info("Deleting application " + deploymentInfo.getApplicationName()); Timer timer = new Timer(); timer.start(); try { if (deleteServices) { CloudApplication app = client.getApplication(deploymentInfo.getApplicationName()); for (String service : app.getServices()) { client.deleteService(service); } } client.deleteApplication(deploymentInfo.getApplicationName()); log.info("Application " + deploymentInfo.getApplicationName() + " deleted successfully in " + timer.stop() + "ms"); } catch (CloudFoundryException ex) { if (HttpStatus.FORBIDDEN == ex.getStatusCode()) { throw new DeploymentStatusException(TOKEN_EXPIRED_RESULT, ex); } else { throw ex; } } } private void doRestart(DeploymentInfo deploymentInfo, CloudFoundryClient client) { log.info("Restarting application " + deploymentInfo.getApplicationName() + " as " + deploymentInfo.getDeploymentUrl()); Timer timer = new Timer(); timer.start(); CloudFoundryUtils.restartApplicationAndWaitUntilRunning(client, deploymentInfo.getApplicationName()); log.info("Application " + deploymentInfo.getApplicationName() + " restarted successfully in " + timer.stop() + "ms"); } private void doStart(DeploymentInfo deploymentInfo, CloudFoundryClient client) { log.info("Starting application " + deploymentInfo.getApplicationName() + " as " + deploymentInfo.getDeploymentUrl()); Timer timer = new Timer(); timer.start(); CloudFoundryUtils.startApplicationAndWaitUntilRunning(client, deploymentInfo.getApplicationName()); log.info("Application " + deploymentInfo.getApplicationName() + " started successfully in " + timer.stop() + "ms"); } private CloudFoundryClient getClient(DeploymentInfo deploymentInfo) { Assert.hasText(deploymentInfo.getToken(), "CloudFoundry login token not supplied."); String url = deploymentInfo.getTarget(); if (!StringUtils.hasText(url)) { url = DEFAULT_URL; } try { CloudFoundryClient client = new CloudFoundryClient(deploymentInfo.getToken(), url); return client; } catch (MalformedURLException e) { throw new WMRuntimeException("CloudFoundry target URL is invalid. Attempted: " + url, e); } } private void validateWar(File war) { Assert.notNull(war, "war cannot be null"); Assert.isTrue(war.exists(), "war does not exist"); Assert.isTrue(!war.isDirectory(), "war cannot be a directory"); } }