2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
22 package org.openecomp.mso.cloudify.utils;
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.HashMap;
29 import java.util.List;
31 import java.util.Optional;
32 import java.util.zip.ZipEntry;
33 import java.util.zip.ZipOutputStream;
35 import org.openecomp.mso.adapters.vdu.CloudInfo;
36 import org.openecomp.mso.adapters.vdu.PluginAction;
37 import org.openecomp.mso.adapters.vdu.VduArtifact;
38 import org.openecomp.mso.adapters.vdu.VduArtifact.ArtifactType;
39 import org.openecomp.mso.adapters.vdu.VduException;
40 import org.openecomp.mso.adapters.vdu.VduInstance;
41 import org.openecomp.mso.adapters.vdu.VduModelInfo;
42 import org.openecomp.mso.adapters.vdu.VduPlugin;
43 import org.openecomp.mso.adapters.vdu.VduStateType;
44 import org.openecomp.mso.adapters.vdu.VduStatus;
45 import org.openecomp.mso.cloud.CloudConfig;
46 import org.openecomp.mso.cloud.CloudConfigFactory;
47 import org.openecomp.mso.cloud.CloudSite;
48 import org.openecomp.mso.cloud.CloudifyManager;
49 import org.openecomp.mso.cloudify.base.client.CloudifyBaseException;
50 import org.openecomp.mso.cloudify.base.client.CloudifyClientTokenProvider;
51 import org.openecomp.mso.cloudify.base.client.CloudifyConnectException;
52 import org.openecomp.mso.cloudify.base.client.CloudifyRequest;
53 import org.openecomp.mso.cloudify.base.client.CloudifyResponseException;
54 import org.openecomp.mso.cloudify.beans.DeploymentInfo;
55 import org.openecomp.mso.cloudify.beans.DeploymentStatus;
56 import org.openecomp.mso.cloudify.exceptions.MsoCloudifyException;
57 import org.openecomp.mso.cloudify.exceptions.MsoCloudifyManagerNotFound;
58 import org.openecomp.mso.cloudify.exceptions.MsoDeploymentAlreadyExists;
59 import org.openecomp.mso.cloudify.v3.client.BlueprintsResource.GetBlueprint;
60 import org.openecomp.mso.cloudify.v3.client.BlueprintsResource.UploadBlueprint;
61 import org.openecomp.mso.cloudify.v3.client.Cloudify;
62 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.CreateDeployment;
63 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.DeleteDeployment;
64 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.GetDeployment;
65 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.GetDeploymentOutputs;
66 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.CancelExecution;
67 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.GetExecution;
68 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.ListExecutions;
69 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.StartExecution;
70 import org.openecomp.mso.cloudify.v3.model.AzureConfig;
71 import org.openecomp.mso.cloudify.v3.model.Blueprint;
72 import org.openecomp.mso.cloudify.v3.model.CancelExecutionParams;
73 import org.openecomp.mso.cloudify.v3.model.CloudifyError;
74 import org.openecomp.mso.cloudify.v3.model.CreateDeploymentParams;
75 import org.openecomp.mso.cloudify.v3.model.Deployment;
76 import org.openecomp.mso.cloudify.v3.model.DeploymentOutputs;
77 import org.openecomp.mso.cloudify.v3.model.Execution;
78 import org.openecomp.mso.cloudify.v3.model.Executions;
79 import org.openecomp.mso.cloudify.v3.model.OpenstackConfig;
80 import org.openecomp.mso.cloudify.v3.model.StartExecutionParams;
81 import org.openecomp.mso.db.catalog.beans.HeatTemplateParam;
82 import org.openecomp.mso.logger.MessageEnum;
83 import org.openecomp.mso.logger.MsoAlarmLogger;
84 import org.openecomp.mso.logger.MsoLogger;
85 import org.openecomp.mso.openstack.exceptions.MsoAdapterException;
86 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
87 import org.openecomp.mso.openstack.exceptions.MsoException;
88 import org.openecomp.mso.openstack.exceptions.MsoExceptionCategory;
89 import org.openecomp.mso.openstack.exceptions.MsoIOException;
90 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
91 import org.openecomp.mso.openstack.utils.MsoCommonUtils;
92 import org.openecomp.mso.properties.MsoJavaProperties;
93 import org.openecomp.mso.properties.MsoPropertiesException;
94 import org.openecomp.mso.properties.MsoPropertiesFactory;
96 import com.fasterxml.jackson.core.JsonParseException;
97 import com.fasterxml.jackson.databind.JsonNode;
98 import com.fasterxml.jackson.databind.ObjectMapper;
100 public class MsoCloudifyUtils extends MsoCommonUtils implements VduPlugin{
102 private MsoPropertiesFactory msoPropertiesFactory;
103 private CloudConfigFactory cloudConfigFactory;
105 private static final String CLOUDIFY_ERROR = "CloudifyError";
107 private static final String CREATE_DEPLOYMENT = "CreateDeployment";
108 private static final String DELETE_DEPLOYMENT = "DeleteDeployment";
110 // Fetch cloud configuration each time (may be cached in CloudConfig class)
111 protected CloudConfig cloudConfig;
113 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
115 protected MsoJavaProperties msoProps = null;
117 // Properties names and variables (with default values)
118 protected String createPollIntervalProp = "ecomp.mso.adapters.heat.create.pollInterval";
119 private String deletePollIntervalProp = "ecomp.mso.adapters.heat.delete.pollInterval";
121 protected int createPollIntervalDefault = 15;
122 private int deletePollIntervalDefault = 15;
124 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
127 * This constructor MUST be used ONLY in the JUNIT tests, not for real code.
129 public MsoCloudifyUtils() {
133 * This constructor MUST be used ONLY in the JUNIT tests, not for real code.
134 * The MsoPropertiesFactory will be added by EJB injection.
136 * @param msoPropID ID of the mso pro config as defined in web.xml
137 * @param msoPropFactory The mso properties factory instanciated by EJB injection
138 * @param cloudConfFactory the Cloud Config instantiated by EJB injection
140 public MsoCloudifyUtils (String msoPropID, MsoPropertiesFactory msoPropFactory, CloudConfigFactory cloudConfFactory) {
141 msoPropertiesFactory = msoPropFactory;
142 cloudConfigFactory = cloudConfFactory;
143 // Dynamically get properties each time (in case reloaded).
146 msoProps = msoPropertiesFactory.getMsoJavaProperties (msoPropID);
147 } catch (MsoPropertiesException e) {
148 LOGGER.error (MessageEnum.LOAD_PROPERTIES_FAIL, "Unknown. Mso Properties ID not found in cache: " + msoPropID, "", "", MsoLogger.ErrorCode.DataError, "Exception - Mso Properties ID not found in cache", e);
150 cloudConfig = cloudConfigFactory.getCloudConfig ();
151 LOGGER.debug("MsoCloudifyUtils:" + msoPropID);
157 * Create a new Deployment from a specified blueprint, and install it in the specified
158 * cloud location and tenant. The blueprint identifier and parameter map are passed in
159 * as arguments, along with the cloud access credentials. The blueprint should have been
160 * previously uploaded to Cloudify.
162 * It is expected that parameters have been validated and contain at minimum the required
163 * parameters for the given template with no extra (undefined) parameters..
165 * The deployment ID supplied by the caller must be unique in the scope of the Cloudify
166 * tenant (not the Openstack tenant). However, it should also be globally unique, as it
167 * will be the identifier for the resource going forward in Inventory. This latter is
168 * managed by the higher levels invoking this function.
170 * This function executes the "install" workflow on the newly created workflow. Cloudify
171 * will be polled for completion unless the client requests otherwise.
173 * An error will be thrown if the requested Deployment already exists in the specified
176 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
177 * @param tenantId The Openstack ID of the tenant in which to create the Stack
178 * @param deploymentId The identifier (name) of the deployment to create
179 * @param blueprintId The blueprint from which to create the deployment.
180 * @param inputs A map of key/value inputs
181 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
182 * @param timeoutMinutes Timeout after which the "install" will be cancelled
183 * @param environment An optional yaml-format string to specify environmental parameters
184 * @param backout Flag to delete deployment on install Failure - defaulted to True
185 * @return A DeploymentInfo object
186 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
187 * @throws MsoIOException Thrown on Cloudify connection errors.
190 public DeploymentInfo createAndInstallDeployment (String cloudSiteId,
194 Map <String, ? extends Object> inputs,
195 boolean pollForCompletion,
197 boolean backout) throws MsoException
199 // Obtain the cloud site information where we will create the stack
200 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
201 if (!cloudSite.isPresent()) {
202 throw new MsoCloudSiteNotFound (cloudSiteId);
205 Cloudify cloudify = getCloudifyClient (cloudSite.get());
207 LOGGER.debug ("Ready to Create Deployment (" + deploymentId + ") with input params: " + inputs);
209 // Build up the inputs, including:
210 // - from provided "environment" file
211 // - passed in by caller
212 // - special input for cloud-specific Credentials
213 Map<String,Object> expandedInputs = new HashMap<> (inputs);
215 String platform = cloudSite.get().getPlatform();
216 if (platform == null || platform.equals("") || platform.equalsIgnoreCase("OPENSTACK")) {
217 // Create the Cloudify OpenstackConfig with the credentials
218 OpenstackConfig openstackConfig = getOpenstackConfig (cloudSite.get(), tenantId);
219 expandedInputs.put("openstack_config", openstackConfig);
220 } else if (platform.equalsIgnoreCase("AZURE")) {
221 // Create Cloudify AzureConfig with the credentials
222 AzureConfig azureConfig = getAzureConfig (cloudSite.get(), tenantId);
223 expandedInputs.put("azure_config", azureConfig);
226 // Build up the parameters to create a new deployment
227 CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
228 deploymentParams.setBlueprintId(blueprintId);
229 deploymentParams.setInputs((Map<String,Object>)expandedInputs);
231 Deployment deployment = null;
233 CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
234 LOGGER.debug (createDeploymentRequest.toString());
236 deployment = executeAndRecordCloudifyRequest (createDeploymentRequest);
238 catch (CloudifyResponseException e) {
239 // Since this came on the 'Create Deployment' command, nothing was changed
240 // in the cloud. Return the error as an exception.
241 if (e.getStatus () == 409) {
242 // Deployment already exists. Return a specific error for this case
243 MsoException me = new MsoDeploymentAlreadyExists (deploymentId, cloudSiteId);
244 me.addContext (CREATE_DEPLOYMENT);
247 // Convert the CloudifyResponseException to an MsoException
248 LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
249 MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
250 me.setCategory (MsoExceptionCategory.OPENSTACK);
253 } catch (CloudifyConnectException e) {
254 // Error connecting to Cloudify instance. Convert to an MsoException
255 MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
257 } catch (RuntimeException e) {
259 throw runtimeExceptionToMsoException (e, CREATE_DEPLOYMENT);
263 * It can take some time for Cloudify to be ready to execute a workflow
264 * on the deployment. Sleep 30 seconds based on observation of behavior
265 * in a Cloudify VM instance (delay due to "create_deployment_environment").
269 } catch (InterruptedException e) {}
272 * Next execute the "install" workflow.
273 * Note - this assumes there are no additional parameters required for the workflow.
275 int createPollInterval = msoProps.getIntProperty (createPollIntervalProp, createPollIntervalDefault);
276 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
278 Execution installWorkflow = null;
281 installWorkflow = executeWorkflow (cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout, createPollInterval);
283 if (installWorkflow.getStatus().equals("terminated")) {
285 // Create and return a DeploymentInfo structure. Include the Runtime outputs
286 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
287 DeploymentInfo deploymentInfo = new DeploymentInfo (deployment, outputs, installWorkflow);
288 return deploymentInfo;
291 // The workflow completed with errors. Must try to back it out.
294 LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
297 // Poll on delete if we rollback - use same values for now
298 int deletePollInterval = createPollInterval;
299 int deletePollTimeout = pollTimeout;
302 // Run the uninstall to undo the install
303 Execution uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
305 if (uninstallWorkflow.getStatus().equals("terminated"))
307 // The uninstall completed. Delete the deployment itself
308 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
309 executeAndRecordCloudifyRequest (deleteRequest);
312 // Didn't uninstall successfully. Log this error
313 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Deployment: Cloudify error rolling back deployment install: " + installWorkflow.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Cloudify error rolling back deployment installation");
316 catch (Exception e) {
317 // Catch-all for backout errors trying to uninstall/delete
318 // Log this error, and return the original exception
319 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back deployment install: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back deployment installation");
323 MsoCloudifyException me = new MsoCloudifyException (0, "Workflow Execution Failed", installWorkflow.getError());
324 me.addContext (CREATE_DEPLOYMENT);
325 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
329 catch (MsoException me) {
330 // Install failed. Unless requested otherwise, back out the deployment
334 LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
337 // Poll on delete if we rollback - use same values for now
338 int deletePollInterval = createPollInterval;
339 int deletePollTimeout = pollTimeout;
342 // Run the uninstall to undo the install.
343 // Always try to run it, as it should be idempotent
344 executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
346 // Delete the deployment itself
347 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
348 executeAndRecordCloudifyRequest (deleteRequest);
350 catch (Exception e) {
351 // Catch-all for backout errors trying to uninstall/delete
352 // Log this error, and return the original exception
353 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back deployment install: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back deployment installation");
358 // Propagate the original exception from Stack Query.
359 me.addContext (CREATE_DEPLOYMENT);
360 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
367 * Get the runtime Outputs of a deployment.
368 * Return the Map of tag/value outputs.
370 private DeploymentOutputs getDeploymentOutputs (Cloudify cloudify, String deploymentId)
373 // Build and send the Cloudify request
374 DeploymentOutputs deploymentOutputs = null;
376 GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
377 LOGGER.debug (queryDeploymentOutputs.toString());
379 deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs, msoProps);
381 catch (CloudifyConnectException ce) {
382 // Couldn't connect to Cloudify
383 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeploymentOutputs: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeploymentOutputs: Cloudify connection failure");
384 throw new MsoIOException (ce.getMessage(), ce);
386 catch (CloudifyResponseException re) {
387 if (re.getStatus () == 404) {
391 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
393 catch (Exception e) {
395 throw new MsoAdapterException (e.getMessage(), e);
398 return deploymentOutputs;
402 * Execute a workflow on a deployment. Handle polling for completion with timeout.
403 * Return the final Execution object with status.
404 * Throw an exception on Errors.
405 * Question - how does the client know whether rollback needs to be done?
407 private Execution executeWorkflow (Cloudify cloudify, String deploymentId, String workflowId, Map<String,Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
408 throws MsoCloudifyException
410 LOGGER.debug("Executing '" + workflowId + "' workflow on deployment '" + deploymentId + "'");
412 StartExecutionParams executeParams = new StartExecutionParams();
413 executeParams.setWorkflowId(workflowId);
414 executeParams.setDeploymentId(deploymentId);
415 executeParams.setParameters(workflowParams);
417 Execution execution = null;
418 String executionId = null;
419 String command = "start";
420 Exception savedException = null;
423 StartExecution executionRequest = cloudify.executions().start(executeParams);
424 LOGGER.debug (executionRequest.toString());
425 execution = executeAndRecordCloudifyRequest (executionRequest);
426 executionId = execution.getId();
428 if (!pollForCompletion) {
429 // Client did not request polling, so just return the Execution object
433 // Enter polling loop
434 boolean timedOut = false;
435 int pollTimeout = timeout;
437 String status = execution.getStatus();
439 // Create a reusable cloudify query request
440 GetExecution queryExecution = cloudify.executions().byId(executionId);
443 while (!timedOut && !(status.equals("terminated") || status.equals("failed") || status.equals("cancelled")))
445 // workflow is still running; check for timeout
446 if (pollTimeout <= 0) {
447 LOGGER.debug ("workflow " + execution.getWorkflowId() + " timed out on deployment " + execution.getDeploymentId());
453 Thread.sleep (pollInterval * 1000L);
454 } catch (InterruptedException e) {}
456 pollTimeout -= pollInterval;
457 LOGGER.debug("pollTimeout remaining: " + pollTimeout);
459 execution = queryExecution.execute();
460 status = execution.getStatus();
463 // Broke the loop. Check again for a terminal state
464 if (status.equals("terminated")){
466 LOGGER.debug ("Workflow '" + workflowId + "' completed successfully on deployment '" + deploymentId + "'");
469 else if (status.equals("failed")){
470 // Workflow failed. Log it and return the execution object (don't throw exception here)
471 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow failure: " + execution.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Failed: " + execution.getError());
474 else if (status.equals("cancelled")){
475 // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the execution object (don't throw exception here)
476 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow cancelled. Deployment is in an indeterminate state", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow cancelled: " + workflowId);
480 // Can only get here after a timeout
481 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow timeout", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Timed Out");
484 catch (CloudifyConnectException ce) {
485 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify connection failure");
488 catch (CloudifyResponseException re) {
489 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify response error: " + re, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify error" + re.getMessage());
492 catch (RuntimeException e) {
494 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
498 // Get to this point ONLY on an error or timeout
499 // The cloudify execution is still running (we've not received a terminal status),
500 // so try to Cancel it.
501 CancelExecutionParams cancelParams = new CancelExecutionParams();
502 cancelParams.setAction("cancel");
503 // TODO: Use force_cancel?
505 Execution cancelExecution = null;
508 CancelExecution cancelRequest = cloudify.executions().cancel(executionId, cancelParams);
509 LOGGER.debug (cancelRequest.toString());
510 cancelExecution = cancelRequest.execute();
512 // Enter polling loop
513 boolean timedOut = false;
514 int cancelTimeout = timeout; // TODO: For now, just use same timeout
516 String status = cancelExecution.getStatus();
518 // Poll for completion. Create a reusable cloudify query request
519 GetExecution queryExecution = cloudify.executions().byId(executionId);
521 while (!timedOut && !status.equals("cancelled"))
523 // workflow is still running; check for timeout
524 if (cancelTimeout <= 0) {
525 LOGGER.debug ("Cancel timeout for workflow " + workflowId + " on deployment " + deploymentId);
531 Thread.sleep (pollInterval * 1000L);
532 } catch (InterruptedException e) {}
534 cancelTimeout -= pollInterval;
535 LOGGER.debug("pollTimeout remaining: " + cancelTimeout);
537 execution = queryExecution.execute();
538 status = execution.getStatus();
541 // Broke the loop. Check again for a terminal state
542 if (status.equals("cancelled")){
543 // Finished cancelling. Return the original exception
544 LOGGER.debug ("Cancel workflow " + workflowId + " completed on deployment " + deploymentId);
545 throw new MsoCloudifyException (-1, "", "", savedException);
548 // Can only get here after a timeout
549 LOGGER.debug ("Cancel workflow " + workflowId + " timeout out on deployment " + deploymentId);
550 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
551 exception.setPendingWorkflow(true);
555 catch (Exception e) {
556 // Catch-all. Log the message and throw the original exception
557 // LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
558 LOGGER.debug ("Cancel workflow " + workflowId + " failed for deployment " + deploymentId + ": " + e.getMessage());
559 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
560 exception.setPendingWorkflow(true);
568 * Query for a Cloudify Deployment (by Name). This call will always return a
569 * DeploymentInfo object. If the deployment does not exist, an "empty" DeploymentInfo will be
570 * returned - containing only the deployment ID and a special status of NOTFOUND.
572 * @param tenantId The Openstack ID of the tenant in which to query
573 * @param cloudSiteId The cloud identifier (may be a region) in which to query
574 * @param stackName The name of the stack to query (may be simple or canonical)
575 * @return A StackInfo object
576 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
578 public DeploymentInfo queryDeployment (String cloudSiteId, String tenantId, String deploymentId)
581 LOGGER.debug ("Query Cloudify Deployment: " + deploymentId + " in tenant " + tenantId);
583 // Obtain the cloud site information where we will create the stack
584 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
585 if (!cloudSite.isPresent()) {
586 throw new MsoCloudSiteNotFound (cloudSiteId);
589 Cloudify cloudify = getCloudifyClient (cloudSite.get());
591 // Build and send the Cloudify request
592 Deployment deployment = null;
593 DeploymentOutputs outputs = null;
595 GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
596 LOGGER.debug (queryDeployment.toString());
598 // deployment = queryDeployment.execute();
599 deployment = executeAndRecordCloudifyRequest(queryDeployment, msoProps);
601 outputs = getDeploymentOutputs (cloudify, deploymentId);
603 // Next look for the latest execution
604 ListExecutions listExecutions = cloudify.executions().listFiltered ("deployment_id=" + deploymentId, "-created_at");
605 Executions executions = listExecutions.execute();
607 // If no executions, does this give NOT_FOUND or empty set?
608 if (executions.getItems().isEmpty()) {
609 return new DeploymentInfo (deployment);
612 return new DeploymentInfo (deployment, outputs, executions.getItems().get(0));
615 catch (CloudifyConnectException ce) {
616 // Couldn't connect to Cloudify
617 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeployment: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeployment: Cloudify connection failure");
618 throw new MsoIOException (ce.getMessage(), ce);
620 catch (CloudifyResponseException re) {
621 if (re.getStatus () == 404) {
622 // Got a NOT FOUND error. React differently based on deployment vs. execution
623 if (deployment != null) {
624 // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
625 return new DeploymentInfo (deployment, outputs, null);
627 // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
628 return new DeploymentInfo (deploymentId);
631 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
633 catch (Exception e) {
635 throw new MsoAdapterException (e.getMessage(), e);
641 * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be
642 * considered a successful deletion. The return value is a DeploymentInfo object which
643 * contains the last deployment status.
645 * There is no rollback from a successful deletion. A deletion failure will
646 * also result in an undefined deployment state - the components may or may not have been
647 * all or partially deleted, so the resulting deployment must be considered invalid.
649 * @param tenantId The Openstack ID of the tenant in which to perform the delete
650 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
651 * @param stackName The name/id of the stack to delete. May be simple or canonical
652 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
653 * @return A StackInfo object
654 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
655 * @throws MsoCloudSiteNotFound
657 public DeploymentInfo uninstallAndDeleteDeployment (String cloudSiteId,
660 int timeoutMinutes) throws MsoException
662 // Obtain the cloud site information where we will create the stack
663 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
664 if (!cloudSite.isPresent()) {
665 throw new MsoCloudSiteNotFound (cloudSiteId);
668 Cloudify cloudify = getCloudifyClient (cloudSite.get());
670 LOGGER.debug ("Ready to Uninstall/Delete Deployment (" + deploymentId + ")");
672 // Query first to save the trouble if deployment not found
673 Deployment deployment = null;
675 GetDeployment queryDeploymentRequest = cloudify.deployments().byId(deploymentId);
676 LOGGER.debug (queryDeploymentRequest.toString());
678 deployment = executeAndRecordCloudifyRequest (queryDeploymentRequest);
680 catch (CloudifyResponseException e) {
681 // Since this came on the 'Create Deployment' command, nothing was changed
682 // in the cloud. Return the error as an exception.
683 if (e.getStatus () == 404) {
684 // Deployment doesn't exist. Return a "NOTFOUND" DeploymentInfo object
685 // TODO: Should return NULL?
686 LOGGER.debug("Deployment requested for deletion does not exist: " + deploymentId);
687 return new DeploymentInfo (deploymentId, DeploymentStatus.NOTFOUND);
689 // Convert the CloudifyResponseException to an MsoOpenstackException
690 LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
691 MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
692 me.setCategory (MsoExceptionCategory.INTERNAL);
695 } catch (CloudifyConnectException e) {
696 // Error connecting to Cloudify instance. Convert to an MsoException
697 MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
699 } catch (RuntimeException e) {
701 throw runtimeExceptionToMsoException (e, DELETE_DEPLOYMENT);
705 * Query the outputs before deleting so they can be returned as well
707 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
710 * Next execute the "uninstall" workflow.
711 * Note - this assumes there are no additional parameters required for the workflow.
713 // TODO: No deletePollInterval that I'm aware of. Use the create interval
714 int deletePollInterval = msoProps.getIntProperty (deletePollIntervalProp, deletePollIntervalDefault);
715 int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
717 Execution uninstallWorkflow = null;
720 uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, true, pollTimeout, deletePollInterval);
722 if (uninstallWorkflow.getStatus().equals("terminated")) {
723 // Successful uninstall.
724 LOGGER.debug("Uninstall successful for deployment " + deploymentId);
727 // The uninstall workflow completed with an error. Must fail the request, but will
728 // leave the deployment in an indeterminate state, as cloud resources may still exist.
729 MsoCloudifyException me = new MsoCloudifyException (0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
730 me.addContext (DELETE_DEPLOYMENT);
731 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
735 catch (MsoException me) {
736 // Uninstall workflow has failed.
737 // Must fail the deletion... may leave the deployment in an inconclusive state
738 me.addContext (DELETE_DEPLOYMENT);
739 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
743 // At this point, the deployment has been successfully uninstalled.
744 // Next step is to delete the deployment itself
746 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
747 LOGGER.debug(deleteRequest.toString());
749 // The delete request returns the deleted deployment
750 deployment = deleteRequest.execute();
753 catch (CloudifyConnectException ce) {
754 // Failed to delete. Must fail the request, but will leave the (uninstalled)
755 // deployment in Cloudify DB.
756 MsoCloudifyException me = new MsoCloudifyException (0, "Deployment Delete Failed", ce.getMessage(), ce);
757 me.addContext (DELETE_DEPLOYMENT);
758 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
761 catch (CloudifyResponseException re) {
762 // Failed to delete. Must fail the request, but will leave the (uninstalled)
763 // deployment in the Cloudify DB.
764 MsoCloudifyException me = new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getMessage(), re);
765 me.addContext (DELETE_DEPLOYMENT);
766 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
769 catch (Exception e) {
771 MsoAdapterException ae = new MsoAdapterException (e.getMessage(), e);
772 ae.addContext (DELETE_DEPLOYMENT);
773 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, ae.getContextMessage());
777 // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
778 return new DeploymentInfo (deployment, outputs, uninstallWorkflow);
783 * Check if a blueprint is available for use at a targeted cloud site.
784 * This requires checking the Cloudify Manager which is servicing that
785 * cloud site to see if the specified blueprint has been loaded.
787 * @param cloudSiteId The cloud site where the blueprint is needed
788 * @param blueprintId The ID for the blueprint in Cloudify
790 public boolean isBlueprintLoaded (String cloudSiteId, String blueprintId)
793 // Obtain the cloud site information where we will load the blueprint
794 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
795 if (!cloudSite.isPresent()) {
796 throw new MsoCloudSiteNotFound (cloudSiteId);
799 Cloudify cloudify = getCloudifyClient (cloudSite.get());
801 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
803 Blueprint bp = getRequest.execute();
804 LOGGER.debug("Blueprint exists: " + bp.getId());
807 catch (CloudifyResponseException ce) {
808 if (ce.getStatus() == 404) {
813 } catch (Exception e) {
819 * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site.
820 * The blueprint currently must be structured as a single directory with all
821 * of the required files. One of those files is designated the "main file"
822 * for the blueprint. Files are provided as byte arrays, though expect only
823 * text files will be distributed from ASDC and stored by MSO.
825 * Cloudify requires a single root directory in its blueprint zip files.
826 * The requested blueprint ID will also be used as the directory.
827 * All of the files will be added to this directory in the zip file.
829 public void uploadBlueprint (String cloudSiteId,
832 Map<String,byte[]> blueprintFiles,
833 boolean failIfExists)
836 // Obtain the cloud site information where we will load the blueprint
837 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
838 if (!cloudSite.isPresent()) {
839 throw new MsoCloudSiteNotFound (cloudSiteId);
842 Cloudify cloudify = getCloudifyClient (cloudSite.get());
844 boolean blueprintUploaded = uploadBlueprint (cloudify, blueprintId, mainFileName, blueprintFiles);
846 if (!blueprintUploaded && failIfExists) {
847 throw new MsoAdapterException ("Blueprint already exists");
852 * Common method to load a blueprint. May be called from
854 protected boolean uploadBlueprint (Cloudify cloudify, String blueprintId, String mainFileName, Map<String,byte[]> blueprintFiles)
857 // Check if it already exists. If so, return false.
858 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
860 Blueprint bp = getRequest.execute();
861 LOGGER.debug("Blueprint " + bp.getId() + " already exists.");
864 catch (CloudifyResponseException ce) {
865 if (ce.getStatus() == 404) {
866 // This is the expected result.
867 LOGGER.debug("Verified that Blueprint doesn't exist yet");
871 } catch (Exception e) {
875 // Create a blueprint ZIP file in memory
876 ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
877 ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
880 // Put the root directory
881 String rootDir = blueprintId + ((blueprintId.endsWith("/") ? "" : "/"));
882 zipOut.putNextEntry(new ZipEntry (rootDir));
885 for (String fileName : blueprintFiles.keySet()) {
886 ZipEntry ze = new ZipEntry (rootDir + fileName);
887 zipOut.putNextEntry (ze);
888 zipOut.write (blueprintFiles.get(fileName));
893 catch (IOException e) {
894 // Since we're writing to a byte array, this should never happen
896 LOGGER.debug ("Blueprint zip file size: " + zipBuffer.size());
898 // Ready to upload the blueprint zip
899 InputStream blueprintStream = new ByteArrayInputStream (zipBuffer.toByteArray());
901 UploadBlueprint uploadRequest = cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
902 Blueprint blueprint = uploadRequest.execute();
903 System.out.println("Successfully uploaded blueprint " + blueprint.getId());
905 catch (CloudifyResponseException e) {
906 MsoException me = cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
909 catch (CloudifyConnectException e) {
910 MsoException me = cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
913 catch (RuntimeException e) {
915 MsoException me = runtimeExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
920 blueprintStream.close();
921 } catch (IOException e) {}
929 // ---------------------------------------------------------------
930 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
933 * Get a Cloudify client for the specified cloud site.
934 * Everything that is required can be found in the Cloud Config.
937 * @return a Cloudify object
939 public Cloudify getCloudifyClient (CloudSite cloudSite) throws MsoException
941 CloudifyManager cloudifyConfig = cloudSite.getCloudifyManager();
942 if (cloudifyConfig == null) {
943 throw new MsoCloudifyManagerNotFound (cloudSite.getId());
946 // Get a Cloudify client
947 // Set a Token Provider to fetch tokens from Cloudify itself.
948 String cloudifyUrl = cloudifyConfig.getCloudifyUrl();
949 Cloudify cloudify = new Cloudify (cloudifyUrl);
950 cloudify.setTokenProvider(new CloudifyClientTokenProvider(cloudifyUrl, cloudifyConfig.getUsername(), cloudifyConfig.getPassword()));
957 * Query for a Cloudify Deployment. This function is needed in several places, so
958 * a common method is useful. This method takes an authenticated CloudifyClient
959 * (which internally identifies the cloud & tenant to search), and returns
960 * a Deployment object if found, Null if not found, or an MsoCloudifyException
961 * if the Cloudify API call fails.
963 * @param cloudifyClient an authenticated Cloudify client
965 * @param deploymentId the deployment to query
967 * @return a Deployment object or null if the requested deployment doesn't exist.
969 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception
971 protected Deployment queryDeployment (Cloudify cloudify, String deploymentId) throws MsoException {
972 if (deploymentId == null) {
976 GetDeployment request = cloudify.deployments().byId (deploymentId);
977 return executeAndRecordCloudifyRequest (request, msoProps);
978 } catch (CloudifyResponseException e) {
979 if (e.getStatus () == 404) {
980 LOGGER.debug ("queryDeployment - not found: " + deploymentId);
983 // Convert the CloudifyResponseException to an MsoCloudifyException
984 throw cloudifyExceptionToMsoException (e, "QueryDeployment");
986 } catch (CloudifyConnectException e) {
987 // Connection to Openstack failed
988 throw cloudifyExceptionToMsoException (e, "QueryDeployment");
993 public void copyStringOutputsToInputs(Map<String, String> inputs,
994 Map<String, Object> otherStackOutputs, boolean overWrite) {
995 if (inputs == null || otherStackOutputs == null)
997 for (String key : otherStackOutputs.keySet()) {
998 if (!inputs.containsKey(key)) {
999 Object obj = otherStackOutputs.get(key);
1000 if (obj instanceof String) {
1001 inputs.put(key, (String) otherStackOutputs.get(key));
1002 } else if (obj instanceof JsonNode ){
1003 // This is a bit of mess - but I think it's the least impacting
1004 // let's convert it BACK to a string - then it will get converted back later
1006 String str = this.convertNode((JsonNode) obj);
1007 inputs.put(key, str);
1008 } catch (Exception e) {
1009 LOGGER.debug("WARNING: unable to convert JsonNode output value for "+ key);
1010 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1012 } else if (obj instanceof java.util.LinkedHashMap) {
1013 LOGGER.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
1015 String str = JSON_MAPPER.writeValueAsString(obj);
1016 inputs.put(key, str);
1017 } catch (Exception e) {
1018 LOGGER.debug("WARNING: unable to convert LinkedHashMap output value for "+ key);
1021 // just try to cast it - could be an integer or some such
1023 String str = (String) obj;
1024 inputs.put(key, str);
1025 } catch (Exception e) {
1026 LOGGER.debug("WARNING: unable to convert output value for "+ key);
1027 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1036 * Normalize an input value to an Object, based on the target parameter type.
1037 * If the type is not recognized, it will just be returned unchanged (as a string).
1039 public Object convertInputValue (String inputValue, HeatTemplateParam templateParam)
1041 String type = templateParam.getParamType();
1042 LOGGER.debug("Parameter: " + templateParam.getParamName() + " is of type " + type);
1044 if (type.equalsIgnoreCase("number")) {
1046 return Integer.valueOf(inputValue);
1048 catch (Exception e) {
1049 LOGGER.debug("Unable to convert " + inputValue + " to an integer!");
1052 } else if (type.equalsIgnoreCase("json")) {
1054 JsonNode jsonNode = new ObjectMapper().readTree(inputValue);
1057 catch (Exception e) {
1058 LOGGER.debug("Unable to convert " + inputValue + " to a JsonNode!");
1061 } else if (type.equalsIgnoreCase("boolean")) {
1062 return new Boolean(inputValue);
1065 // Nothing else matched. Return the original string
1070 private String convertNode(final JsonNode node) {
1072 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
1073 final String json = JSON_MAPPER.writeValueAsString(obj);
1075 } catch (JsonParseException jpe) {
1076 LOGGER.debug("Error converting json to string " + jpe.getMessage());
1077 } catch (Exception e) {
1078 LOGGER.debug("Error converting json to string " + e.getMessage());
1080 return "[Error converting json to string]";
1085 * Method to execute a Cloudify command and track its execution time.
1086 * For the metrics log, a category of "Cloudify" is used along with a
1087 * sub-category that identifies the specific call (using the real
1088 * cloudify-client classname of the CloudifyRequest<T> parameter).
1091 protected static <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request)
1093 return executeAndRecordCloudifyRequest (request, null);
1095 protected static <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request, MsoJavaProperties msoProps) {
1098 // Get the name and method name of the parent class, which triggered this method
1099 StackTraceElement[] classArr = new Exception ().getStackTrace ();
1100 if (classArr.length >=2) {
1103 limit = classArr.length;
1105 String parentServiceMethodName = classArr[0].getClassName () + "." + classArr[0].getMethodName ();
1106 for (int i = 1; i < limit; i++) {
1107 String className = classArr[i].getClassName ();
1108 if (!className.equals (MsoCommonUtils.class.getName ())) {
1109 parentServiceMethodName = className + "." + classArr[i].getMethodName ();
1115 if (request.getClass ().getEnclosingClass () != null) {
1116 requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
1117 + request.getClass ().getSimpleName ();
1119 requestType = request.getClass ().getSimpleName ();
1122 int retryDelay = retryDelayDefault;
1123 int retryCount = retryCountDefault;
1124 String retryCodes = retryCodesDefault;
1125 if (msoProps != null) //extra check to avoid NPE
1127 retryDelay = msoProps.getIntProperty (retryDelayProp, retryDelayDefault);
1128 retryCount = msoProps.getIntProperty (retryCountProp, retryCountDefault);
1129 retryCodes = msoProps.getProperty (retryCodesProp, retryCodesDefault);
1132 // Run the actual command. All exceptions will be propagated
1136 return request.execute ();
1138 catch (CloudifyResponseException e) {
1139 boolean retry = false;
1140 if (retryCodes != null ) {
1141 int code = e.getStatus();
1142 LOGGER.debug ("Config values RetryDelay:" + retryDelay + " RetryCount:" + retryCount + " RetryCodes:" + retryCodes + " ResponseCode:" + code);
1143 for (String rCode : retryCodes.split (",")) {
1145 if (retryCount > 0 && code == Integer.parseInt (rCode))
1149 LOGGER.debug ("CloudifyResponseException ResponseCode:" + code + " at:" + parentServiceMethodName + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
1152 } catch (NumberFormatException e1) {
1153 LOGGER.error (MessageEnum.RA_CONFIG_EXC, "No retries. Exception in parsing retry code in config:" + rCode, "", "", MsoLogger.ErrorCode.SchemaError, "Exception in parsing retry code in config");
1161 Thread.sleep (retryDelay * 1000L);
1162 } catch (InterruptedException e1) {
1163 LOGGER.debug ("Thread interrupted while sleeping", e1);
1167 throw e; // exceeded retryCount or code is not retryable
1169 catch (CloudifyConnectException e) {
1170 // Connection to Cloudify failed
1174 LOGGER.debug ("CloudifyConnectException at:" + parentServiceMethodName + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
1176 Thread.sleep (retryDelay * 1000L);
1177 } catch (InterruptedException e1) {
1178 LOGGER.debug ("Thread interrupted while sleeping", e1);
1188 * Convert an Exception on a Cloudify call to an MsoCloudifyException.
1189 * This method supports CloudifyResponseException and CloudifyConnectException.
1191 protected MsoException cloudifyExceptionToMsoException (CloudifyBaseException e, String context) {
1192 MsoException me = null;
1194 if (e instanceof CloudifyResponseException) {
1195 CloudifyResponseException re = (CloudifyResponseException) e;
1198 // Failed Cloudify calls return an error entity body.
1199 CloudifyError error = re.getResponse ().getErrorEntity (CloudifyError.class);
1200 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Cloudify", "Cloudify Error on " + context + ": " + error.getErrorCode(), "Cloudify", "", MsoLogger.ErrorCode.DataError, "Exception - Cloudify Error on " + context);
1201 String fullError = error.getErrorCode() + ": " + error.getMessage();
1202 LOGGER.debug(fullError);
1203 me = new MsoCloudifyException (re.getStatus(),
1206 } catch (Exception e2) {
1207 // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
1208 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Cloudify", "HTTP Error on " + context + ": " + re.getStatus() + "," + e.getMessage(), "Cloudify", "", MsoLogger.ErrorCode.DataError, "Exception - HTTP Error on " + context, e2);
1209 me = new MsoCloudifyException (re.getStatus (), re.getMessage (), "");
1212 // Add the context of the error
1213 me.addContext (context);
1215 // Generate an alarm for 5XX and higher errors.
1216 if (re.getStatus () >= 500) {
1217 alarmLogger.sendAlarm ("CloudifyError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
1219 } else if (e instanceof CloudifyConnectException) {
1220 CloudifyConnectException ce = (CloudifyConnectException) e;
1222 me = new MsoIOException (ce.getMessage ());
1223 me.addContext (context);
1225 // Generate an alarm for all connection errors.
1226 alarmLogger.sendAlarm ("CloudifyIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
1227 LOGGER.error(MessageEnum.RA_CONNECTION_EXCEPTION, "Cloudify", "Cloudify connection error on " + context + ": " + e, "Cloudify", "", MsoLogger.ErrorCode.DataError, "Cloudify connection error on " + context);
1235 /*******************************************************************************
1237 * Methods (and associated utilities) to implement the VduPlugin interface
1239 *******************************************************************************/
1242 * VduPlugin interface for instantiate function.
1244 * This one is a bit more complex, in that it will first upload the blueprint if needed,
1245 * then create the Cloudify deployment and execute the install workflow.
1247 * This implementation also merges any parameters defined in the ENV file with the other
1248 * other input parameters for any undefined parameters).
1249 * The basic MsoCloudifyUtils separates blueprint management from deploument actions,
1250 * but the VduPlugin does not declare blueprint management operations.
1252 public VduInstance instantiateVdu (
1253 CloudInfo cloudInfo,
1254 String instanceName,
1255 Map<String,Object> inputs,
1256 VduModelInfo vduModel,
1257 boolean rollbackOnFailure)
1260 String cloudSiteId = cloudInfo.getCloudSiteId();
1261 String tenantId = cloudInfo.getTenantId();
1263 // Translate the VDU ModelInformation structure to that which is needed for
1264 // creating and uploading a blueprint. Use the model customization UUID as
1265 // the blueprint identifier.
1267 String blueprintId = vduModel.getModelCustomizationUUID();
1271 if (! isBlueprintLoaded (cloudSiteId, blueprintId)) {
1272 LOGGER.debug ("Blueprint " + blueprintId + " is not loaded. Will upload it now.");
1274 // Prepare the blueprint inputs. Need the set of blueprint templates and files,
1275 // plus the main blueprint name.
1276 Map<String,byte[]> blueprintFiles = new HashMap<>();
1277 String mainTemplate = "";
1279 // Add all of the blueprint artifacts from the VDU model
1280 List<VduArtifact> vduArtifacts = vduModel.getArtifacts();
1281 for (VduArtifact vduArtifact: vduArtifacts)
1283 // Add all artifacts to the blueprint, with one exception.
1284 // ENVIRONMENT files will be processed later as additional parameters.
1286 ArtifactType artifactType = vduArtifact.getType();
1287 if (artifactType != ArtifactType.ENVIRONMENT) {
1288 blueprintFiles.put(vduArtifact.getName(), vduArtifact.getContent());
1290 if (artifactType == ArtifactType.MAIN_TEMPLATE) {
1291 mainTemplate = vduArtifact.getName();
1296 // Upload the blueprint package
1297 uploadBlueprint(cloudSiteId, blueprintId, mainTemplate, blueprintFiles, false);
1300 catch (Exception e) {
1301 throw new VduException ("CloudifyUtils (instantiateVDU): blueprint Exception", e);
1305 // Next, create and install a new deployment based on the blueprint.
1306 // For Cloudify, the deploymentId is specified by the client. Just use the instance name
1310 // Query the Cloudify Deployment object and populate a VduInstance
1311 DeploymentInfo deployment = createAndInstallDeployment (cloudSiteId,
1316 true, // (poll for completion)
1317 vduModel.getTimeoutMinutes(),
1320 VduInstance vduInstance = deploymentInfoToVduInstance(deployment);
1324 catch (Exception e) {
1325 throw new VduException ("CloudifyUtils (instantiateVDU): Create-and-install-deployment Exception", e);
1331 * VduPlugin interface for query function.
1333 public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
1336 String cloudSiteId = cloudInfo.getCloudSiteId();
1337 String tenantId = cloudInfo.getTenantId();
1340 // Query the Cloudify Deployment object and populate a VduInstance
1341 DeploymentInfo deployment = queryDeployment (cloudSiteId, tenantId, instanceId);
1343 VduInstance vduInstance = deploymentInfoToVduInstance(deployment);
1347 catch (Exception e) {
1348 throw new VduException ("Query VDU Exception", e);
1354 * VduPlugin interface for delete function.
1356 public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
1359 String cloudSiteId = cloudInfo.getCloudSiteId();
1360 String tenantId = cloudInfo.getTenantId();
1363 // Uninstall and delete the Cloudify Deployment
1364 DeploymentInfo deployment = uninstallAndDeleteDeployment (cloudSiteId, tenantId, instanceId, timeoutMinutes);
1366 // Populate a VduInstance based on the deleted Cloudify Deployment object
1367 VduInstance vduInstance = deploymentInfoToVduInstance(deployment);
1371 catch (Exception e) {
1372 throw new VduException ("Delete VDU Exception", e);
1378 * VduPlugin interface for update function.
1380 * Update is currently not supported in the MsoCloudifyUtils implementation.
1381 * Just return a VduException.
1384 public VduInstance updateVdu (
1385 CloudInfo cloudInfo,
1387 Map<String,Object> inputs,
1388 VduModelInfo vduModel,
1389 boolean rollbackOnFailure)
1392 throw new VduException ("CloudifyUtils: updateVDU interface not supported");
1397 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1399 protected VduInstance deploymentInfoToVduInstance (DeploymentInfo deployment)
1401 VduInstance vduInstance = new VduInstance();
1403 // only one ID in Cloudify, use for both VDU name and ID
1404 vduInstance.setVduInstanceId(deployment.getId());
1405 vduInstance.setVduInstanceName(deployment.getId());
1407 // Copy inputs and outputs
1408 vduInstance.setInputs(deployment.getInputs());
1409 vduInstance.setOutputs(deployment.getOutputs());
1411 // Translate the status elements
1412 vduInstance.setStatus(deploymentStatusToVduStatus (deployment));
1417 protected VduStatus deploymentStatusToVduStatus (DeploymentInfo deployment)
1419 VduStatus vduStatus = new VduStatus();
1421 // Determine the status based on last action & status
1422 // DeploymentInfo object should be enhanced to report a better status internally.
1423 DeploymentStatus status = deployment.getStatus();
1425 if (status == null) {
1426 vduStatus.setState(VduStateType.UNKNOWN);
1428 else if (status == DeploymentStatus.NOTFOUND) {
1429 vduStatus.setState(VduStateType.NOTFOUND);
1431 else if (status == DeploymentStatus.INSTALLED) {
1432 vduStatus.setState(VduStateType.INSTANTIATED);
1434 else if (status == DeploymentStatus.CREATED) {
1435 // Deployment exists but is not installed. This shouldn't really happen,
1436 // since create + install or uninstall + delete are always done together.
1437 // But account for it anyway, assuming the operation is still in progress.
1438 String lastAction = deployment.getLastAction();
1439 if (lastAction == null)
1440 vduStatus.setState(VduStateType.INSTANTIATING);
1442 vduStatus.setState(VduStateType.DELETING);
1444 else if (status == DeploymentStatus.FAILED) {
1445 vduStatus.setState(VduStateType.FAILED);
1447 vduStatus.setState(VduStateType.UNKNOWN);
1450 vduStatus.setErrorMessage(deployment.getErrorMessage());
1451 vduStatus.setLastAction(new PluginAction(deployment.getLastAction(), deployment.getActionStatus(), deployment.getErrorMessage()));
1457 * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in.
1458 * Base the values on the CloudSite definition.
1460 protected OpenstackConfig getOpenstackConfig (CloudSite cloudSite, String tenantId) {
1461 OpenstackConfig openstackConfig = new OpenstackConfig();
1462 openstackConfig.setRegion (cloudSite.getRegionId());
1463 openstackConfig.setAuthUrl (cloudSite.getIdentityService().getIdentityUrl());
1464 openstackConfig.setUsername (cloudSite.getIdentityService().getMsoId());
1465 openstackConfig.setPassword (cloudSite.getIdentityService().getMsoPass());
1466 openstackConfig.setTenantName (tenantId);
1467 return openstackConfig;
1471 * Return an Azure object as expected by Cloudify Azure Plug-in.
1472 * Base the values on the CloudSite definition.
1474 protected AzureConfig getAzureConfig (CloudSite cloudSite, String tenantId) {
1475 AzureConfig azureConfig = new AzureConfig();
1476 // TODO: Use adminTenant for now, instead of adding another element
1477 azureConfig.setSubscriptionId (cloudSite.getIdentityService().getAdminTenant());
1478 azureConfig.setTenantId (tenantId);
1479 azureConfig.setClientId (cloudSite.getIdentityService().getMsoId());
1480 azureConfig.setClientSecret (cloudSite.getIdentityService().getMsoPass());