/** * Copyright (c) Microsoft Corporation * <p/> * All rights reserved. * <p/> * MIT License * <p/> * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and * to permit persons to whom the Software is furnished to do so, subject to the following conditions: * <p/> * The above copyright notice and this permission notice shall be included in all copies or substantial portions of * the Software. * <p/> * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.microsoft.intellij.deploy; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindowManager; import com.interopbridges.tools.windowsazure.*; import com.microsoft.intellij.AzurePlugin; import com.microsoft.intellij.AzureSettings; import com.microsoft.intellij.activitylog.ActivityLogToolWindowFactory; import com.microsoft.intellij.util.AppInsightsCustomEvent; import com.microsoft.intellij.util.PluginUtil; import com.microsoft.intellij.wizards.WizardCacheManager; import com.microsoft.wacommon.utils.WACommonException; import com.microsoft.windowsazure.Configuration; import com.microsoft.windowsazure.core.OperationStatus; import com.microsoft.windowsazure.core.OperationStatusResponse; import com.microsoft.windowsazure.management.compute.models.*; import com.microsoft.windowsazure.management.compute.models.DeploymentSlot; import com.microsoft.windowsazure.management.compute.models.HostedServiceListResponse.HostedService; import com.microsoft.windowsazure.management.storage.models.StorageAccountCreateParameters; import com.microsoftopentechnologies.azurecommons.deploy.DeploymentEventArgs; import com.microsoftopentechnologies.azurecommons.deploy.DeploymentManagerUtilMethods; import com.microsoftopentechnologies.azurecommons.deploy.model.CertificateUpload; import com.microsoftopentechnologies.azurecommons.deploy.model.DeployDescriptor; import com.microsoftopentechnologies.azurecommons.exception.DeploymentException; import com.microsoftopentechnologies.azurecommons.exception.RestAPIException; import com.microsoftopentechnologies.azurecommons.storageregistry.StorageAccount; import com.microsoftopentechnologies.azurecommons.storageregistry.StorageAccountRegistry; import com.microsoftopentechnologies.azuremanagementutil.model.InstanceStatus; import com.microsoftopentechnologies.azuremanagementutil.model.Notifier; import com.microsoftopentechnologies.azuremanagementutil.model.StorageService; import com.microsoftopentechnologies.azuremanagementutil.rest.WindowsAzureRestUtils; import com.microsoftopentechnologies.azuremanagementutil.rest.WindowsAzureServiceManagement; import com.microsoftopentechnologies.azuremanagementutil.rest.WindowsAzureStorageServices; import java.io.File; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import static com.microsoft.intellij.AzurePlugin.log; import static com.microsoft.intellij.ui.messages.AzureBundle.message; public final class DeploymentManager { private final HashMap<String, DeployDescriptor> deployments = new HashMap<String, DeployDescriptor>(); private static final DeploymentManager DEFAULT_MANAGER = new DeploymentManager(); public static DeploymentManager getInstance() { return DEFAULT_MANAGER; } private DeploymentManager() { } public void addDeployment(String name, DeployDescriptor deployment) { deployments.put(name, deployment); } public void removeDeployment(String name) { deployments.remove(name); } public HashMap<String, DeployDescriptor> getDeployments() { return deployments; } public void deploy(Module selectedModule) throws InterruptedException, DeploymentException { DeployDescriptor deploymentDesc = WizardCacheManager.collectConfiguration(); String deployState = deploymentDesc.getDeployState(); Date startDate = new Date(); try { int conditionalProgress = 20; HostedService hostedService = deploymentDesc.getHostedService(); addDeployment(hostedService.getServiceName(), deploymentDesc); StorageService storageAccount = deploymentDesc.getStorageAccount(); WindowsAzureServiceManagement service = WizardCacheManager.createServiceManagementHelper(); openWindowsAzureActivityLogView(deploymentDesc, selectedModule.getProject()); if (deploymentDesc.getDeployMode() == WindowsAzurePackageType.LOCAL) { deployToLocalEmulator(selectedModule); notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 100, OperationStatus.Succeeded, message("deplCompleted")); return; } // Publish start event AppInsightsCustomEvent.create(message("startEvent"), ""); // need to improve this check (maybe hostedSerivce.isExisting())? if (hostedService.getUri() == null || hostedService.getUri().toString().isEmpty()) { // the hosted service was not yet created. notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 5, OperationStatus.InProgress, String.format("%s - %s", message("createHostedService"), hostedService.getServiceName())); createHostedService(hostedService.getServiceName(), hostedService.getServiceName(), hostedService.getProperties().getLocation(), hostedService.getProperties().getDescription()); conditionalProgress -= 5; } // same goes here if (storageAccount.getUrl() == null || storageAccount.getUrl().isEmpty()) { // the storage account was not yet created notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 10, OperationStatus.InProgress, String.format("%s - %s", message("createStorageAccount"), storageAccount.getServiceName())); createStorageAccount(storageAccount.getServiceName(), storageAccount.getServiceName(), storageAccount.getStorageAccountProperties().getLocation(), storageAccount.getStorageAccountProperties().getDescription()); conditionalProgress -= 10; } checkContainerExistance(); // upload certificates if (deploymentDesc.getCertList() != null) { List<CertificateUpload> certList = deploymentDesc.getCertList().getList(); if (certList != null && certList.size() > 0) { for (int i = 0; i < certList.size(); i++) { CertificateUpload cert = certList.get(i); DeploymentManagerUtilMethods.uploadCertificateIfNeededGeneric(service, deploymentDesc, cert.getPfxPath(), cert.getPfxPwd()); notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 0, OperationStatus.InProgress, String.format("%s%s", message("deplUploadCert"), cert.getName())); } } } if (deploymentDesc.getRemoteDesktopDescriptor().isEnabled()) { notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, conditionalProgress, OperationStatus.InProgress, message("deplConfigRdp")); DeploymentManagerUtilMethods.configureRemoteDesktop(deploymentDesc, WizardCacheManager.getCurrentDeployConfigFile(), String.format("%s%s%s", PathManager.getPluginsPath(), File.separator, AzurePlugin.PLUGIN_ID)); } else { notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, conditionalProgress, OperationStatus.InProgress, message("deplConfigRdp")); } Notifier notifier = new NotifierImp(); String targetCspckgName = createCspckTargetName(deploymentDesc); notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 20, OperationStatus.InProgress, message("uploadingServicePackage")); DeploymentManagerUtilMethods.uploadPackageService( WizardCacheManager.createStorageServiceHelper(), deploymentDesc.getCspkgFile(), targetCspckgName, message("eclipseDeployContainer").toLowerCase(), deploymentDesc, notifier); notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 20, OperationStatus.InProgress, message("creatingDeployment")); String storageAccountURL = deploymentDesc.getStorageAccount(). getStorageAccountProperties().getEndpoints().get(0).toString(); String cspkgUrl = String.format("%s%s/%s", storageAccountURL, message("eclipseDeployContainer").toLowerCase(), targetCspckgName); /* * To make deployment name unique attach time stamp * to the deployment name. */ DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String deploymentName = String.format("%s%s%s", hostedService.getServiceName(), deployState, dateFormat.format(new Date())); String requestId = DeploymentManagerUtilMethods.createDeployment(deploymentDesc, service, cspkgUrl, deploymentName); OperationStatus status = waitForStatus(deploymentDesc.getConfiguration(), service, requestId); DeploymentManagerUtilMethods.deletePackage(WizardCacheManager.createStorageServiceHelper(), message("eclipseDeployContainer").toLowerCase(), targetCspckgName, notifier); notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 0, OperationStatus.InProgress, message("deletePackage")); notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 20, OperationStatus.InProgress, message("waitingForDeployment")); DeploymentGetResponse deployment = waitForDeployment( deploymentDesc.getConfiguration(), hostedService.getServiceName(), deployState); boolean displayHttpsLink = deploymentDesc.getDisplayHttpsLink(); WindowsAzureProjectManager waProjManager = WindowsAzureProjectManager.load(new File(PluginUtil.getModulePath(selectedModule))); String serverAppName = null; for (WindowsAzureRole role : waProjManager.getRoles()) { if (role.getJDKSourcePath() != null && role.getServerCloudName() != null) { List<WindowsAzureRoleComponent> serverAppComponents = role.getServerApplications(); // Get first server app component if (serverAppComponents != null && serverAppComponents.size() > 0) { String deployName = serverAppComponents.get(0).getDeployName(); serverAppName = deployName.substring(0, deployName.lastIndexOf(".")); break; } } } String deploymentURL = displayHttpsLink ? deployment.getUri().toString().replaceAll("http://", "https://") : deployment.getUri().toString(); if (serverAppName != null) { if (!deploymentURL.endsWith("/")) { deploymentURL += "/"; } deploymentURL += serverAppName + "/"; } notifyProgress(deploymentDesc.getDeploymentId(), startDate, deploymentURL, 20, status, deployment.getStatus().toString()); // publish success event AppInsightsCustomEvent.create(message("successEvent"), ""); // RDP prompt will come only on windows if (deploymentDesc.isStartRdpOnDeploy() && AzurePlugin.IS_WINDOWS) { String pluginFolder = String.format("%s%s%s", PathManager.getPluginsPath(), File.separator, AzurePlugin.PLUGIN_ID); WindowsAzureRestUtils.getInstance().launchRDP(deployment, deploymentDesc.getRemoteDesktopDescriptor().getUserName(), pluginFolder); } } catch (Throwable t) { if (t instanceof ProcessCanceledException) { PluginUtil.displayWarningDialogInAWT(message("interrupt"), message("deploymentInterrupted")); } else { // Publish failure event AppInsightsCustomEvent.create(message("failureEvent"), ""); String msg = (t.getMessage() != null ? t.getMessage() : ""); if (!msg.startsWith(OperationStatus.Failed.toString())) { msg = OperationStatus.Failed.toString() + " : " + msg; } notifyProgress(deploymentDesc.getDeploymentId(), startDate, null, 100, OperationStatus.Failed, msg, deploymentDesc.getDeploymentId(), deployState); if (t instanceof DeploymentException) { throw (DeploymentException) t; } throw new DeploymentException(msg, t); } } } private void createStorageAccount(final String storageServiceName, final String label, final String location, final String description) throws Exception { StorageAccountCreateParameters accountParameters = new StorageAccountCreateParameters(); accountParameters.setName(storageServiceName); accountParameters.setLabel(label); accountParameters.setLocation(location); accountParameters.setDescription(description); StorageService storageService = WizardCacheManager.createStorageAccount(accountParameters); /* * Add newly created storage account * in centralized storage account registry. */ StorageAccount storageAccount = new StorageAccount(storageService.getServiceName(), storageService.getPrimaryKey(), storageService. getStorageAccountProperties().getEndpoints().get(0).toString()); StorageAccountRegistry.addAccount(storageAccount); AzureSettings.getSafeInstance(PluginUtil.getSelectedProject()).saveStorage(); } private void createHostedService(final String hostedServiceName, final String label, final String location, final String description) throws Exception { HostedServiceCreateParameters createHostedService = new HostedServiceCreateParameters(); createHostedService.setServiceName(hostedServiceName); createHostedService.setLabel(label); createHostedService.setLocation(location); createHostedService.setDescription(description); WizardCacheManager.createHostedService(createHostedService); } private void checkContainerExistance() throws Exception { WindowsAzureStorageServices storageServices = WizardCacheManager.createStorageServiceHelper(); storageServices.createContainer(message("eclipseDeployContainer").toLowerCase()); } private DeploymentGetResponse waitForDeployment(Configuration configuration, String serviceName, String deployState) throws Exception { DeploymentGetResponse deployment = null; String status = null; DeploymentSlot deploymentSlot; if (DeploymentSlot.Staging.toString().equalsIgnoreCase(deployState)) { deploymentSlot = DeploymentSlot.Staging; } else if (DeploymentSlot.Production.toString().equalsIgnoreCase(deployState)) { deploymentSlot = DeploymentSlot.Production; } else { throw new Exception("Invalid deployment slot name"); } do { Thread.sleep(5000); deployment = WindowsAzureRestUtils.getDeploymentBySlot(configuration, serviceName, deploymentSlot); for (RoleInstance instance : deployment.getRoleInstances()) { status = instance.getInstanceStatus(); if (InstanceStatus.ReadyRole.getInstanceStatus().equals(status) || InstanceStatus.CyclingRole.getInstanceStatus().equals(status) || InstanceStatus.FailedStartingVM.getInstanceStatus().equals(status) || InstanceStatus.UnresponsiveRole.getInstanceStatus().equals(status)) { break; } } } while (status != null && !(InstanceStatus.ReadyRole.getInstanceStatus().equals(status) || InstanceStatus.CyclingRole.getInstanceStatus().equals(status) || InstanceStatus.FailedStartingVM.getInstanceStatus().equals(status) || InstanceStatus.UnresponsiveRole.getInstanceStatus().equals(status))); if (!InstanceStatus.ReadyRole.getInstanceStatus().equals(status)) { throw new DeploymentException(status); } // check deployment status. And let Transitioning phase to finish DeploymentStatus deploymentStatus = null; do { Thread.sleep(10000); deployment = WindowsAzureRestUtils.getDeploymentBySlot(configuration, serviceName, deploymentSlot); deploymentStatus = deployment.getStatus(); } while (deploymentStatus != null && (deploymentStatus.equals(DeploymentStatus.RunningTransitioning) || deploymentStatus.equals(DeploymentStatus.SuspendedTransitioning))); return deployment; } private OperationStatus waitForStatus(Configuration configuration, WindowsAzureServiceManagement service, String requestId) throws Exception { OperationStatusResponse op; OperationStatus status = null; do { op = service.getOperationStatus(configuration, requestId); status = op.getStatus(); log(message("deplId") + op.getId()); log(message("deplStatus") + op.getStatus()); log(message("deplHttpStatus") + op.getHttpStatusCode()); if (op.getError() != null) { log(message("deplErrorMessage") + op.getError().getMessage()); throw new RestAPIException(op.getError().getMessage()); } Thread.sleep(5000); } while (status == OperationStatus.InProgress); return status; } private String createCspckTargetName(DeployDescriptor deploymentDesc) { String cspkgName = String.format(message("cspkgName"), deploymentDesc.getHostedService().getServiceName(), deploymentDesc.getDeployState()); return cspkgName; } private void deployToLocalEmulator(Module selectedModule) throws DeploymentException { WindowsAzureProjectManager waProjManager; try { waProjManager = WindowsAzureProjectManager.load(new File(PluginUtil.getModulePath(selectedModule))); waProjManager.deployToEmulator(); } catch (WindowsAzureInvalidProjectOperationException e) { throw new DeploymentException(e); } } /** * Unlike Eclipse plugin, here startDate is deployment start time, not the event timestamp */ public void notifyProgress(String deploymentId, Date startDate, String deploymentURL, int progress, OperationStatus inprogress, String message, Object... args) { DeploymentEventArgs arg = new DeploymentEventArgs(this); arg.setId(deploymentId); arg.setDeploymentURL(deploymentURL); arg.setDeployMessage(String.format(message, args)); arg.setDeployCompleteness(progress); arg.setStartTime(startDate); arg.setStatus(inprogress); AzurePlugin.fireDeploymentEvent(arg); } private void openWindowsAzureActivityLogView(final DeployDescriptor descriptor, final Project project) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { ToolWindowManager.getInstance(project).getToolWindow(ActivityLogToolWindowFactory.ACTIVITY_LOG_WINDOW).activate(null); } }); } public void undeploy(final String serviceName, final String deplymentName, final String deploymentState) throws WACommonException, RestAPIException, InterruptedException { Configuration configuration = WizardCacheManager.getCurrentPublishData().getCurrentConfiguration(); int[] progressArr = new int[]{50, 50}; unPublish(configuration, serviceName, deplymentName, progressArr); } /** * Unpublish deployment without notifying user. * * @param configuration * @param serviceName * @param deplymentName */ public void unPublish( Configuration configuration, String serviceName, String deplymentName, int[] progressArr) { String requestId = null; int retryCount = 0; boolean successfull = false; Date startDate = new Date(); while (!successfull) { try { retryCount++; WindowsAzureServiceManagement service = WizardCacheManager.createServiceManagementHelper(); // Commenting suspend deployment call since it is giving issues in china cloud. // notifyProgress(deplymentName, null, progressArr[0], OperationStatus.InProgress, // Messages.stoppingMsg, serviceName); // requestId = service.updateDeploymentStatus(configuration, // serviceName, // deplymentName, // UpdatedDeploymentStatus.Suspended // ); // waitForStatus(configuration, service, requestId); notifyProgress(deplymentName, startDate, null, progressArr[0], OperationStatus.InProgress, message("undeployProgressMsg"), deplymentName); requestId = service.deleteDeployment(configuration, serviceName, deplymentName); waitForStatus(configuration, service, requestId); notifyProgress(deplymentName, startDate, null, progressArr[1], OperationStatus.Succeeded, message("undeployCompletedMsg"), serviceName); successfull = true; } catch (Exception e) { // Retry 5 times if (retryCount > AzurePlugin.REST_SERVICE_MAX_RETRY_COUNT) { log(message("deplError"), e); notifyProgress(deplymentName, startDate, null, 100, OperationStatus.Failed, e.getMessage(), serviceName); } notifyProgress(deplymentName, startDate, null, -progressArr[0], OperationStatus.InProgress, message("undeployProgressMsg"), deplymentName); } } } }