/* * � Copyright IBM Corp. 2015 * * 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.ibm.xsp.extlib.designer.bluemix.job; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import lotus.domino.Database; import lotus.domino.NotesException; import lotus.domino.Session; import org.cloudfoundry.client.lib.CloudCredentials; import org.cloudfoundry.client.lib.CloudFoundryClient; import org.cloudfoundry.client.lib.domain.CloudApplication; import org.cloudfoundry.client.lib.domain.CloudDomain; import org.cloudfoundry.client.lib.domain.InstanceInfo; import org.cloudfoundry.client.lib.domain.InstanceState; import org.cloudfoundry.client.lib.domain.InstancesInfo; import org.cloudfoundry.client.lib.domain.Staging; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.IJobManager; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.statushandlers.IStatusAdapterConstants; import org.eclipse.ui.statushandlers.StatusAdapter; import org.eclipse.ui.statushandlers.StatusManager; import com.ibm.commons.util.DateTime; import com.ibm.commons.util.StringUtil; import com.ibm.designer.domino.ide.resources.extensions.NotesPlatform; import com.ibm.designer.domino.ide.resources.project.IDominoDesignerProject; import com.ibm.designer.domino.preferences.DominoPreferenceManager; import com.ibm.designer.domino.xsp.internal.builder.XFacesBuilder; import com.ibm.xsp.extlib.designer.bluemix.BluemixLogger; import com.ibm.xsp.extlib.designer.bluemix.BluemixPlugin; import com.ibm.xsp.extlib.designer.bluemix.config.BluemixConfig; import com.ibm.xsp.extlib.designer.bluemix.config.ConfigManager; import com.ibm.xsp.extlib.designer.bluemix.manifest.BluemixManifest; import com.ibm.xsp.extlib.designer.bluemix.manifest.ManifestUtil; import com.ibm.xsp.extlib.designer.bluemix.preference.PreferenceKeys; import com.ibm.xsp.extlib.designer.bluemix.preference.PreferencePage; import com.ibm.xsp.extlib.designer.bluemix.util.BluemixUtil; import com.ibm.xsp.extlib.designer.bluemix.util.BluemixZipUtil; import static com.ibm.xsp.extlib.designer.bluemix.preference.PreferenceKeys.*; /** * @author Gary Marjoram * */ public class DeployJob extends Job { private static MutexRule _jobRule = new MutexRule(); private final BluemixConfig _config; private final IDominoDesignerProject _project; private Throwable _copyException; private String firstAppName = null; private String dbName = null; public DeployJob(BluemixConfig config, IDominoDesignerProject project) { super(BluemixUtil.productizeString("Deploy to %BM_PRODUCT%")); // $NLX-DeployJob.DeploytoIBMBluemix-1$ _config = config; _project = project; } @Override protected IStatus run(final IProgressMonitor monitor) { CloudFoundryClient client = null; dbName = _project.getDatabaseName(); String msg = BluemixUtil.productizeString(StringUtil.format("Deploying \"{0}\" to %BM_PRODUCT%", dbName)); // $NLX-DeployJob.Deploying0toIBMBluemix-1$ monitor.beginTask(msg, IProgressMonitor.UNKNOWN); IJobManager jobManager = Job.getJobManager(); try { // Wait on all builders to complete jobManager.join(ResourcesPlugin.FAMILY_MANUAL_BUILD, new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN)); jobManager.join(ResourcesPlugin.FAMILY_AUTO_BUILD, new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN)); jobManager.join(XFacesBuilder.DeferFullBuildJobFamily, new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN)); } catch (Exception e) { if (BluemixLogger.BLUEMIX_LOGGER.isErrorEnabled()) { BluemixLogger.BLUEMIX_LOGGER.errorp(this, "run", e, "Error waiting on builders to complete"); // $NON-NLS-1$ $NLE-DeployJob.Errorwaitingonbuilderstocomplete-2$ } } try { // Copy the DB to the deployment directory _copyException = null; NotesPlatform.getInstance().syncExec(new Runnable() { public void run() { ISchedulingRule rule = null; Database db = null; Database tmpDb = null; try { // While we're copying protect the NSF from a build starting rule = ResourcesPlugin.getWorkspace().getRuleFactory().buildRule(); Job.getJobManager().beginRule(rule, new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN)); monitor.subTask("Creating database copy..."); // $NLX-DeployJob.Creatingdatabasecopy-1$ // Open the source db Session sess = NotesPlatform.getInstance().getSession(); db = sess.getDatabase(_project.getServerName(), _project.getDatabaseName()); // Construct the file paths String targetDbName = _config.directory + "\\" + db.getFileName(); String tmpFilePath; // Create temporary Db name String tmpDbName = DateTime.formatDateTime(new Date(), "yyyyMMddHHmmssSSS"); // $NON-NLS-1$ if (StringUtil.equalsIgnoreCase(_config.copyMethod, "replica")) { // $NON-NLS-1$ // Create the temporary local replica tmpFilePath = BluemixUtil.createLocalDatabaseReplica(db, tmpDbName); } else { // Create the temporary local copy tmpFilePath = BluemixUtil.createLocalDatabaseCopy(db, tmpDbName); } // Copy the temporary replica/copy to the target location monitor.subTask("Copying to deployment directory..."); // $NLX-DeployJob.Copyingtodeploymentdirectory-1$ BluemixUtil.copyFile(new File(tmpFilePath), new File(targetDbName)); // Delete the temporary copy/replica tmpDb = sess.getDatabase(null, tmpDbName); tmpDb.remove(); } catch (Throwable e) { // Record the Exception _copyException = e; } finally { if (rule != null) { Job.getJobManager().endRule(rule); } if (tmpDb != null) { try { tmpDb.recycle(); } catch (NotesException e) { if (BluemixLogger.BLUEMIX_LOGGER.isErrorEnabled()) { BluemixLogger.BLUEMIX_LOGGER.errorp(this, "run", e, "Failed to recycle tmpDb"); // $NON-NLS-1$ $NLE-DeployJob.FailedtorecycletmpDb-2$ } } } if (db != null) { try { db.recycle(); } catch (NotesException e) { if (BluemixLogger.BLUEMIX_LOGGER.isErrorEnabled()) { BluemixLogger.BLUEMIX_LOGGER.errorp(this, "run", e, "Failed to recycle db"); // $NON-NLS-1$ $NLE-DeployJob.Failedtorecycledb-2$ } } } } } }); // Did the NSF copy complete successfully? if (_copyException != null) { // No throw exception throw new Exception("Error copying database to deployment directory", _copyException); // $NLX-DeployJob.ErrorcopyingDatabasetodeploymentd-1$ } if (monitor.isCanceled()) return Status.CANCEL_STATUS; // Deploy to bluemix - load the manifest BluemixManifest manifest = new BluemixManifest(ManifestUtil.getManifestFile(_config)); Set<String> applications = manifest.getAppNames(); if (applications.size() > 0) { String user = PreferencePage.getSecurePreference(KEY_BLUEMIX_SERVER_USERNAME, ""); String password = PreferencePage.getSecurePreference(KEY_BLUEMIX_SERVER_PASSWORD, ""); CloudCredentials credentials = new CloudCredentials(user, password); if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask("Connecting to Cloud Space..."); // $NLX-DeployJob.ConnectingtoCloudSpace-1$ try { String target = PreferencePage.getSecurePreference(KEY_BLUEMIX_SERVER_URL, ""); client = new CloudFoundryClient(credentials, URI.create(target).toURL(), _config.org, _config.space); client.login(); } catch (Exception e) { throw new Exception("Error connecting to Cloud Space", e); // $NLX-DeployJob.ErrorconnectingtoCloudSpace-1$ } List<CloudApplication> existingApps; boolean couldNotGetAppList = false; if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask("Retrieving applications..."); // $NLX-DeployJob.RetrievingApplications-1$ try { existingApps = client.getApplications(); } catch (Exception e) { if (BluemixUtil.isDefect187654Exception(e)) { // Probably Defect187654 - retrieving non string env vars // Allow deploy to continue, try to create each app and if // this fails try to update an existing one existingApps = new ArrayList<CloudApplication>(); couldNotGetAppList = true; if (BluemixLogger.BLUEMIX_LOGGER.isWarnEnabled()) { BluemixLogger.BLUEMIX_LOGGER.warnp(null, "run", e, "Failed to retrieve application list from Cloud Space"); // $NON-NLS-1$ $NLW-DeployJob.Failedtoretrieveapplicationlistfr-2$ } } else { throw new Exception("Error retrieving applications from Cloud Space", e); // $NLX-DeployJob.ErrorretrievingApplicationsfromCloud-1$ } } for (String appName : applications) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; // Store the first app name for display later if (firstAppName == null) { firstAppName = appName; } // Check if the app is already on Bluemix // Assume it is not to begin with boolean createNewApp = true; if (couldNotGetAppList) { // No app list - Defect187654 try { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Retrieving application details : {0}", appName)); // $NLX-DeployJob.RetrievingApplicationDetails0-1$ // Try to get each individual app client.getApplication(appName); // Success - app already exists createNewApp = false; } catch (Exception e) { if (BluemixUtil.isDefect187654Exception(e)) { // App already exists - but env needs to be cleared (Defect187654) // They'll be re-written later on in the deploy process createNewApp = false; try { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Clearing environment variables : {0}", appName)); // $NLX-DeployJob.ClearingEnvironmentVariables0-1$ // Erase the env vars client.updateApplicationEnv(appName, new LinkedHashMap<String, String>()); } catch (Exception ex) { if (BluemixLogger.BLUEMIX_LOGGER.isWarnEnabled()) { BluemixLogger.BLUEMIX_LOGGER.warnp(null, "run", e, "Failed to clear {} env vars", appName); // $NON-NLS-1$ $NLW-DeployJob.Failedtoclearenvvars-2$ } } } } } else { // Have the app list - search for the app for (CloudApplication cloudApp : existingApps) { if (StringUtil.equalsIgnoreCase(appName, cloudApp.getName())) { createNewApp = false; break; } } } // Staging String buildPack = manifest.getBuildPack(appName); String command = manifest.getCommand(appName); Integer timeout = manifest.getTimeout(appName); String stack = manifest.getStack(appName); Staging staging = new Staging(command, buildPack, stack, timeout); // Memory Integer memory = manifest.getMemory(appName); if (createNewApp) { // New Application if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Creating new application : {0}", appName)); // $NLX-DeployJob.CreatingnewApplication0-1$ try { // Create the Application with staging and memory client.createApplication(appName, staging, memory, null, null); } catch (Exception e) { throw new Exception("Error creating application", e); // $NLX-DeployJob.ErrorcreatingApplication-1$ } } else { // Existing Application - Stop it if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Stopping application...")); // $NLX-DeployJob.StoppingApplication-1$ try { client.stopApplication(appName); } catch (Exception e) { throw new Exception("Error stopping application", e); // $NLX-DeployJob.ErrorstoppingApplication-1$ } // Update the staging if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating staging...")); // $NLX-DeployJob.UpdatingStaging-1$ try { client.updateApplicationStaging(appName, staging); } catch (Exception e) { throw new Exception("Error updating application staging", e); // $NLX-DeployJob.ErrorupdatingApplicationst-1$ } // Update the memory if (memory != null) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating memory...")); // $NLX-DeployJob.UpdatingMemory-1$ try { client.updateApplicationMemory(appName, memory); } catch (Exception e) { throw new Exception("Error updating application memory", e); // $NLX-DeployJob.ErrorupdatingApplicationme-1$ } } } // Env Map<String, Object> env = manifest.getEnv(appName); if (env != null) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating environment variables...")); // $NLX-DeployJob.UpdatingEnv-1$ try { client.updateApplicationEnv(appName, ManifestUtil.convertToStringMap(env)); } catch (Exception e) { throw new Exception("Error updating application environment variables", e); // $NLX-DeployJob.ErrorupdatingApplicationen-1$ } } // Instances Integer instances = manifest.getInstances(appName); if (instances != null) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating instances...")); // $NLX-DeployJob.UpdatingInstances-1$ try { client.updateApplicationInstances(appName, instances); } catch (Exception e) { throw new Exception("Error updating application instances", e); // $NLX-DeployJob.ErrorupdatingApplicationin-1$ } } // Disk Quota Integer disk = manifest.getDiskQuota(appName); if (disk != null) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating disk quota...")); // $NLX-DeployJob.UpdatingDiskQuota-1$ try { client.updateApplicationDiskQuota(appName, disk); } catch (Exception e) { throw new Exception("Error updating application disk quota", e); // $NLX-DeployJob.ErrorupdatingApplicationdi-1$ } } // URIs List<CloudDomain> domains = client.getSharedDomains(); String defaultDomain = domains.isEmpty() ? "" : domains.get(0).getName(); List<String> uris = manifest.getRoutes(appName, defaultDomain); if (uris != null) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating URIs...")); // $NLX-DeployJob.UpdatingURIs-1$ try { client.updateApplicationUris(appName, uris); } catch (Exception e) { throw new Exception("Error updating application URI", e); // $NLX-DeployJob.ErrorupdatingApplicationUR-1$ } // Write the URI to the properties file if (uris.size() > 0) { _config.uri = "http://" + uris.get(0) + "/" + BluemixUtil.getNsfName(dbName); // $NON-NLS-1$ } else { _config.uri = ""; } ConfigManager.getInstance().setConfig(_project, _config, false, null); } // Services List<String> services = manifest.getServices(appName); if (services != null) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Updating services...")); // $NLX-DeployJob.UpdatingServices-1$ try { client.updateApplicationServices(appName, services); } catch (Exception e) { throw new Exception("Error updating application services", e); // $NLX-DeployJob.ErrorupdatingApplicationse-1$ } } // Upload the files if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Uploading files...")); // $NLX-DeployJob.Uploadingfiles-1$ try { String path = manifest.getPath(appName); if (path != null) { path = _config.directory + "\\" + path; File file = new File(path); if (file.exists()) { uploadApplication(client, appName, file); } } else { uploadApplication(client, appName, new File(_config.directory)); } } catch (Exception e) { throw new Exception("Error uploading application files", e); // $NLX-DeployJob.ErroruploadingApplicationf-1$ } // Start the Application if (monitor.isCanceled()) return Status.CANCEL_STATUS; monitor.subTask(StringUtil.format("Starting application...")); // $NLX-DeployJob.StartingApplication-1$ try { client.startApplication(appName); } catch (Exception e) { throw new Exception("Error starting application", e); // $NLX-DeployJob.ErrorstartingApplication-1$ } } if (monitor.isCanceled()) return Status.CANCEL_STATUS; // Wait for the apps to be started DominoPreferenceManager mgr = DominoPreferenceManager.getInstance(); if (mgr.getBooleanValue(PreferenceKeys.KEY_BLUEMIX_DEPLOY_WAIT, false)) { monitor.subTask(StringUtil.format("Waiting for application to start...")); // $NLX-DeployJob.WaitingforApplicationtostart-1$ boolean complete; Long timeout = mgr.getLongValue(PreferenceKeys.KEY_BLUEMIX_DEPLOY_WAIT_TIMEOUT, false); long startTime = System.currentTimeMillis(); do { // Sleep for 5 secs Thread.sleep(5000); if (monitor.isCanceled()) return Status.CANCEL_STATUS; // Assume success complete = true; for (String appName : applications) { if(!isApplicationRunning(client, appName)) { // At least one app not running complete = false; break; } } if (monitor.isCanceled()) return Status.CANCEL_STATUS; } while ((!complete) && (System.currentTimeMillis() - startTime < (timeout*1000))); // Did all instances start successfully if (!complete) { msg = StringUtil.format("All instances of \"{0}\" did not start within the timeout period: {1} seconds", firstAppName, timeout); // $NLX-DeployJob.Allinstancesof0didnotstartwithint-1$ throw new Exception(msg); } else { if (mgr.getBooleanValue(PreferenceKeys.KEY_BLUEMIX_DEPLOY_WAIT_SHOW_SUCCESS, false)) { PlatformUI.getWorkbench().getDisplay().syncExec (new Runnable () { public void run () { String msg = StringUtil.format("All instances of \"{0}({1})\" started successfully", firstAppName, dbName); // $NLX-DeployJob.Allinstancesof01startedsuccessful-1$ MessageDialog.openInformation(null, "Deployment Success", msg); // $NLX-DeployJob.DeploymentSuccess-1$ } }); } } } } } catch (Throwable e) { msg = BluemixUtil.getErrorText(e); StatusAdapter status = new StatusAdapter(new Status(IStatus.ERROR, BluemixPlugin.PLUGIN_ID, 0, msg, BluemixUtil.getRootCause(e))); status.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, e.getMessage()); StatusManager.getManager().handle(status, StatusManager.BLOCK); if (BluemixLogger.BLUEMIX_LOGGER.isErrorEnabled()) { BluemixLogger.BLUEMIX_LOGGER.errorp(this, "run", e, BluemixUtil.productizeString("Error deploying Application to %BM_PRODUCT%")); // $NON-NLS-1$ $NLE-DeployJob.ErrordeployingApplicationtoIBMBluemi-2$ } } finally { // Logout if (client != null) { client.logout(); } } return Status.OK_STATUS; } public void start() { setPriority(Job.BUILD); setUser(true); setRule(_jobRule); schedule(); } private static void uploadApplication(CloudFoundryClient client, String appName, File file) throws Exception { if (file.isDirectory()) { File zipFile = File.createTempFile("bluemix", ".zip"); // $NON-NLS-1$ $NON-NLS-2$ BluemixZipUtil.zipDirectory(file.getPath(), zipFile.getPath()); client.uploadApplication(appName, zipFile); zipFile.delete(); } else if (file.isFile()) { client.uploadApplication(appName, file); } } public static class MutexRule implements ISchedulingRule { public boolean isConflicting(ISchedulingRule rule) { return rule == this; } public boolean contains(ISchedulingRule rule) { return rule == this; } } public static boolean isApplicationRunning(CloudFoundryClient client, String appName) { InstancesInfo infos = client.getApplicationInstances(appName); if (infos != null) { for (InstanceInfo info :infos.getInstances()) { if (info.getState() != InstanceState.RUNNING) { return false; } } // All instances are running return true; } // No instance info - app not running return false; } }