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=========================================================
21 package org.onap.so.cloudify.utils;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Optional;
31 import java.util.zip.ZipEntry;
32 import java.util.zip.ZipOutputStream;
34 import org.onap.so.adapters.vdu.CloudInfo;
35 import org.onap.so.adapters.vdu.PluginAction;
36 import org.onap.so.adapters.vdu.VduArtifact;
37 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
38 import org.onap.so.adapters.vdu.VduException;
39 import org.onap.so.adapters.vdu.VduInstance;
40 import org.onap.so.adapters.vdu.VduModelInfo;
41 import org.onap.so.adapters.vdu.VduPlugin;
42 import org.onap.so.adapters.vdu.VduStateType;
43 import org.onap.so.adapters.vdu.VduStatus;
44 import org.onap.so.cloud.CloudConfig;
45 import org.onap.so.cloud.CloudSite;
46 import org.onap.so.cloud.CloudifyManager;
47 import org.onap.so.cloudify.base.client.CloudifyBaseException;
48 import org.onap.so.cloudify.base.client.CloudifyClientTokenProvider;
49 import org.onap.so.cloudify.base.client.CloudifyConnectException;
50 import org.onap.so.cloudify.base.client.CloudifyRequest;
51 import org.onap.so.cloudify.base.client.CloudifyResponseException;
52 import org.onap.so.cloudify.beans.DeploymentInfo;
53 import org.onap.so.cloudify.beans.DeploymentStatus;
54 import org.onap.so.cloudify.exceptions.MsoCloudifyException;
55 import org.onap.so.cloudify.exceptions.MsoCloudifyManagerNotFound;
56 import org.onap.so.cloudify.exceptions.MsoDeploymentAlreadyExists;
57 import org.onap.so.cloudify.v3.client.BlueprintsResource.GetBlueprint;
58 import org.onap.so.cloudify.v3.client.BlueprintsResource.UploadBlueprint;
59 import org.onap.so.cloudify.v3.client.Cloudify;
60 import org.onap.so.cloudify.v3.client.DeploymentsResource.CreateDeployment;
61 import org.onap.so.cloudify.v3.client.DeploymentsResource.DeleteDeployment;
62 import org.onap.so.cloudify.v3.client.DeploymentsResource.GetDeployment;
63 import org.onap.so.cloudify.v3.client.DeploymentsResource.GetDeploymentOutputs;
64 import org.onap.so.cloudify.v3.client.ExecutionsResource.CancelExecution;
65 import org.onap.so.cloudify.v3.client.ExecutionsResource.GetExecution;
66 import org.onap.so.cloudify.v3.client.ExecutionsResource.ListExecutions;
67 import org.onap.so.cloudify.v3.client.ExecutionsResource.StartExecution;
68 import org.onap.so.cloudify.v3.model.AzureConfig;
69 import org.onap.so.cloudify.v3.model.Blueprint;
70 import org.onap.so.cloudify.v3.model.CancelExecutionParams;
71 import org.onap.so.cloudify.v3.model.CloudifyError;
72 import org.onap.so.cloudify.v3.model.CreateDeploymentParams;
73 import org.onap.so.cloudify.v3.model.Deployment;
74 import org.onap.so.cloudify.v3.model.DeploymentOutputs;
75 import org.onap.so.cloudify.v3.model.Execution;
76 import org.onap.so.cloudify.v3.model.Executions;
77 import org.onap.so.cloudify.v3.model.OpenstackConfig;
78 import org.onap.so.cloudify.v3.model.StartExecutionParams;
79 import org.onap.so.config.beans.PoConfig;
80 import org.onap.so.db.catalog.beans.HeatTemplateParam;
81 import org.onap.so.logger.MessageEnum;
82 import org.onap.so.logger.MsoAlarmLogger;
83 import org.onap.so.logger.MsoLogger;
84 import org.onap.so.openstack.exceptions.MsoAdapterException;
85 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
86 import org.onap.so.openstack.exceptions.MsoException;
87 import org.onap.so.openstack.exceptions.MsoExceptionCategory;
88 import org.onap.so.openstack.exceptions.MsoIOException;
89 import org.onap.so.openstack.exceptions.MsoOpenstackException;
90 import org.onap.so.openstack.utils.MsoCommonUtils;
91 import org.onap.so.utils.CryptoUtils;
92 import org.springframework.beans.factory.annotation.Autowired;
93 import org.springframework.core.env.Environment;
94 import org.springframework.stereotype.Component;
96 import com.fasterxml.jackson.core.JsonParseException;
97 import com.fasterxml.jackson.databind.JsonNode;
98 import com.fasterxml.jackson.databind.ObjectMapper;
101 public class MsoCloudifyUtils extends MsoCommonUtils implements VduPlugin{
103 private static final String CLOUDIFY_ERROR = "CloudifyError";
104 private static final String CLOUDIFY = "Cloudify";
105 private static final String CREATE_DEPLOYMENT = "CreateDeployment";
106 private static final String DELETE_DEPLOYMENT = "DeleteDeployment";
107 private static final String TERMINATED = "terminated";
108 private static final String CANCELLED = "cancelled";
110 // Fetch cloud configuration each time (may be cached in CloudConfig class)
112 protected CloudConfig cloudConfig;
115 private Environment environment;
118 private PoConfig poConfig;
120 private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoCloudifyUtils.class);
122 // Properties names and variables (with default values)
123 protected String createPollIntervalProp = "ecomp.mso.adapters.po.pollInterval";
124 private String deletePollIntervalProp = "ecomp.mso.adapters.po.pollInterval";
126 protected String createPollIntervalDefault = "15";
127 private String deletePollIntervalDefault = "15";
129 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
132 * Create a new Deployment from a specified blueprint, and install it in the specified
133 * cloud location and tenant. The blueprint identifier and parameter map are passed in
134 * as arguments, along with the cloud access credentials. The blueprint should have been
135 * previously uploaded to Cloudify.
137 * It is expected that parameters have been validated and contain at minimum the required
138 * parameters for the given template with no extra (undefined) parameters..
140 * The deployment ID supplied by the caller must be unique in the scope of the Cloudify
141 * tenant (not the Openstack tenant). However, it should also be globally unique, as it
142 * will be the identifier for the resource going forward in Inventory. This latter is
143 * managed by the higher levels invoking this function.
145 * This function executes the "install" workflow on the newly created workflow. Cloudify
146 * will be polled for completion unless the client requests otherwise.
148 * An error will be thrown if the requested Deployment already exists in the specified
151 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
152 * @param tenantId The Openstack ID of the tenant in which to create the Stack
153 * @param deploymentId The identifier (name) of the deployment to create
154 * @param blueprintId The blueprint from which to create the deployment.
155 * @param inputs A map of key/value inputs
156 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
157 * @param timeoutMinutes Timeout after which the "install" will be cancelled
158 * @param environment An optional yaml-format string to specify environmental parameters
159 * @param backout Flag to delete deployment on install Failure - defaulted to True
160 * @return A DeploymentInfo object
161 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
162 * @throws MsoIOException Thrown on Cloudify connection errors.
165 public DeploymentInfo createAndInstallDeployment (String cloudSiteId,
169 Map <String, ? extends Object> inputs,
170 boolean pollForCompletion,
172 boolean backout) throws MsoException
174 // Obtain the cloud site information where we will create the stack
175 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
176 if (!cloudSite.isPresent()) {
177 throw new MsoCloudSiteNotFound (cloudSiteId);
180 Cloudify cloudify = getCloudifyClient (cloudSite.get());
182 LOGGER.debug ("Ready to Create Deployment (" + deploymentId + ") with input params: " + inputs);
184 // Build up the inputs, including:
185 // - from provided "environment" file
186 // - passed in by caller
187 // - special input for cloud-specific Credentials
188 Map<String,Object> expandedInputs = new HashMap<> (inputs);
190 String platform = cloudSite.get().getPlatform();
191 if (platform == null || platform.equals("") || platform.equalsIgnoreCase("OPENSTACK")) {
192 // Create the Cloudify OpenstackConfig with the credentials
193 OpenstackConfig openstackConfig = getOpenstackConfig (cloudSite.get(), tenantId);
194 expandedInputs.put("openstack_config", openstackConfig);
195 } else if (platform.equalsIgnoreCase("AZURE")) {
196 // Create Cloudify AzureConfig with the credentials
197 AzureConfig azureConfig = getAzureConfig (cloudSite.get(), tenantId);
198 expandedInputs.put("azure_config", azureConfig);
201 // Build up the parameters to create a new deployment
202 CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
203 deploymentParams.setBlueprintId(blueprintId);
204 deploymentParams.setInputs(expandedInputs);
206 Deployment deployment = null;
208 CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
209 LOGGER.debug (createDeploymentRequest.toString());
211 deployment = executeAndRecordCloudifyRequest (createDeploymentRequest);
213 catch (CloudifyResponseException e) {
214 // Since this came on the 'Create Deployment' command, nothing was changed
215 // in the cloud. Return the error as an exception.
216 if (e.getStatus () == 409) {
217 // Deployment already exists. Return a specific error for this case
218 MsoException me = new MsoDeploymentAlreadyExists (deploymentId, cloudSiteId);
219 me.addContext (CREATE_DEPLOYMENT);
222 // Convert the CloudifyResponseException to an MsoException
223 LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
224 MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
225 me.setCategory (MsoExceptionCategory.OPENSTACK);
228 } catch (CloudifyConnectException e) {
229 // Error connecting to Cloudify instance. Convert to an MsoException
230 throw cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
231 } catch (RuntimeException e) {
233 throw runtimeExceptionToMsoException (e, CREATE_DEPLOYMENT);
237 * It can take some time for Cloudify to be ready to execute a workflow
238 * on the deployment. Sleep 30 seconds based on observation of behavior
239 * in a Cloudify VM instance (delay due to "create_deployment_environment").
244 * Next execute the "install" workflow.
245 * Note - this assumes there are no additional parameters required for the workflow.
247 int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
248 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
250 Execution installWorkflow = null;
253 installWorkflow = executeWorkflow (cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout, createPollInterval);
255 if (installWorkflow.getStatus().equals(TERMINATED)) {
257 // Create and return a DeploymentInfo structure. Include the Runtime outputs
258 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
259 return new DeploymentInfo (deployment, outputs, installWorkflow);
262 // The workflow completed with errors. Must try to back it out.
265 LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
268 // Poll on delete if we rollback - use same values for now
269 int deletePollInterval = createPollInterval;
270 int deletePollTimeout = pollTimeout;
273 // Run the uninstall to undo the install
274 Execution uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
276 if (uninstallWorkflow.getStatus().equals(TERMINATED))
278 // The uninstall completed. Delete the deployment itself
279 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
280 executeAndRecordCloudifyRequest (deleteRequest);
283 // Didn't uninstall successfully. Log this error
284 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");
287 catch (Exception e) {
288 // Catch-all for backout errors trying to uninstall/delete
289 // Log this error, and return the original exception
290 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");
294 MsoCloudifyException me = new MsoCloudifyException (0, "Workflow Execution Failed", installWorkflow.getError());
295 me.addContext (CREATE_DEPLOYMENT);
296 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
300 catch (MsoException me) {
301 // Install failed. Unless requested otherwise, back out the deployment
305 LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
308 // Poll on delete if we rollback - use same values for now
309 int deletePollInterval = createPollInterval;
310 int deletePollTimeout = pollTimeout;
313 // Run the uninstall to undo the install.
314 // Always try to run it, as it should be idempotent
315 executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
317 // Delete the deployment itself
318 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
319 executeAndRecordCloudifyRequest (deleteRequest);
321 catch (Exception e) {
322 // Catch-all for backout errors trying to uninstall/delete
323 // Log this error, and return the original exception
324 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");
329 // Propagate the original exception from Stack Query.
330 me.addContext (CREATE_DEPLOYMENT);
331 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
338 * Get the runtime Outputs of a deployment.
339 * Return the Map of tag/value outputs.
341 private DeploymentOutputs getDeploymentOutputs (Cloudify cloudify, String deploymentId)
344 // Build and send the Cloudify request
345 DeploymentOutputs deploymentOutputs = null;
347 GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
348 LOGGER.debug (queryDeploymentOutputs.toString());
350 deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs);
352 catch (CloudifyConnectException ce) {
353 // Couldn't connect to Cloudify
354 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeploymentOutputs: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeploymentOutputs: Cloudify connection failure");
355 throw new MsoIOException (ce.getMessage(), ce);
357 catch (CloudifyResponseException re) {
358 if (re.getStatus () == 404) {
362 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
364 catch (Exception e) {
366 throw new MsoAdapterException (e.getMessage(), e);
369 return deploymentOutputs;
373 * Execute a workflow on a deployment. Handle polling for completion with timeout.
374 * Return the final Execution object with status.
375 * Throw an exception on Errors.
376 * Question - how does the client know whether rollback needs to be done?
378 private Execution executeWorkflow (Cloudify cloudify, String deploymentId, String workflowId, Map<String,Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
379 throws MsoCloudifyException
381 LOGGER.debug("Executing '" + workflowId + "' workflow on deployment '" + deploymentId + "'");
383 StartExecutionParams executeParams = new StartExecutionParams();
384 executeParams.setWorkflowId(workflowId);
385 executeParams.setDeploymentId(deploymentId);
386 executeParams.setParameters(workflowParams);
388 Execution execution = null;
389 String executionId = null;
390 String command = "start";
391 Exception savedException = null;
394 StartExecution executionRequest = cloudify.executions().start(executeParams);
395 LOGGER.debug (executionRequest.toString());
396 execution = executeAndRecordCloudifyRequest (executionRequest);
397 executionId = execution.getId();
399 if (!pollForCompletion) {
400 // Client did not request polling, so just return the Execution object
404 // Enter polling loop
405 boolean timedOut = false;
406 int pollTimeout = timeout;
408 String status = execution.getStatus();
410 // Create a reusable cloudify query request
411 GetExecution queryExecution = cloudify.executions().byId(executionId);
414 while (!timedOut && !(status.equals(TERMINATED) || status.equals("failed") || status.equals(CANCELLED)))
416 // workflow is still running; check for timeout
417 if (pollTimeout <= 0) {
418 LOGGER.debug ("workflow " + execution.getWorkflowId() + " timed out on deployment " + execution.getDeploymentId());
423 sleep(pollInterval * 1000L);
425 pollTimeout -= pollInterval;
426 LOGGER.debug("pollTimeout remaining: " + pollTimeout);
428 execution = queryExecution.execute();
429 status = execution.getStatus();
432 // Broke the loop. Check again for a terminal state
433 if (status.equals(TERMINATED)){
435 LOGGER.debug ("Workflow '" + workflowId + "' completed successfully on deployment '" + deploymentId + "'");
438 else if (status.equals("failed")){
439 // Workflow failed. Log it and return the execution object (don't throw exception here)
440 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow failure: " + execution.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Failed: " + execution.getError());
443 else if (status.equals(CANCELLED)){
444 // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the execution object (don't throw exception here)
445 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow cancelled. Deployment is in an indeterminate state", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow cancelled: " + workflowId);
449 // Can only get here after a timeout
450 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow timeout", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Timed Out");
453 catch (CloudifyConnectException ce) {
454 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify connection failure");
457 catch (CloudifyResponseException re) {
458 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify response error: " + re, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify error" + re.getMessage());
461 catch (RuntimeException e) {
463 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
467 // Get to this point ONLY on an error or timeout
468 // The cloudify execution is still running (we've not received a terminal status),
469 // so try to Cancel it.
470 CancelExecutionParams cancelParams = new CancelExecutionParams();
471 cancelParams.setAction("cancel");
472 // TODO: Use force_cancel?
474 Execution cancelExecution = null;
477 CancelExecution cancelRequest = cloudify.executions().cancel(executionId, cancelParams);
478 LOGGER.debug (cancelRequest.toString());
479 cancelExecution = cancelRequest.execute();
481 // Enter polling loop
482 boolean timedOut = false;
483 int cancelTimeout = timeout; // TODO: For now, just use same timeout
485 String status = cancelExecution.getStatus();
487 // Poll for completion. Create a reusable cloudify query request
488 GetExecution queryExecution = cloudify.executions().byId(executionId);
490 while (!timedOut && !status.equals(CANCELLED))
492 // workflow is still running; check for timeout
493 if (cancelTimeout <= 0) {
494 LOGGER.debug ("Cancel timeout for workflow " + workflowId + " on deployment " + deploymentId);
499 sleep(pollInterval * 1000L);
501 cancelTimeout -= pollInterval;
502 LOGGER.debug("pollTimeout remaining: " + cancelTimeout);
504 execution = queryExecution.execute();
505 status = execution.getStatus();
508 // Broke the loop. Check again for a terminal state
509 if (status.equals(CANCELLED)){
510 // Finished cancelling. Return the original exception
511 LOGGER.debug ("Cancel workflow " + workflowId + " completed on deployment " + deploymentId);
512 throw new MsoCloudifyException (-1, "", "", savedException);
515 // Can only get here after a timeout
516 LOGGER.debug ("Cancel workflow " + workflowId + " timeout out on deployment " + deploymentId);
517 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
518 exception.setPendingWorkflow(true);
522 catch (Exception e) {
523 // Catch-all. Log the message and throw the original exception
524 // LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
525 LOGGER.debug ("Cancel workflow " + workflowId + " failed for deployment " + deploymentId + ": " + e.getMessage());
526 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
527 exception.setPendingWorkflow(true);
535 * Query for a Cloudify Deployment (by Name). This call will always return a
536 * DeploymentInfo object. If the deployment does not exist, an "empty" DeploymentInfo will be
537 * returned - containing only the deployment ID and a special status of NOTFOUND.
539 * @param tenantId The Openstack ID of the tenant in which to query
540 * @param cloudSiteId The cloud identifier (may be a region) in which to query
541 * @param stackName The name of the stack to query (may be simple or canonical)
542 * @return A StackInfo object
543 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
545 public DeploymentInfo queryDeployment (String cloudSiteId, String tenantId, String deploymentId)
548 LOGGER.debug ("Query Cloudify Deployment: " + deploymentId + " in tenant " + tenantId);
550 // Obtain the cloud site information where we will create the stack
551 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
552 if (!cloudSite.isPresent()) {
553 throw new MsoCloudSiteNotFound (cloudSiteId);
556 Cloudify cloudify = getCloudifyClient (cloudSite.get());
558 // Build and send the Cloudify request
559 Deployment deployment = null;
560 DeploymentOutputs outputs = null;
562 GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
563 LOGGER.debug (queryDeployment.toString());
565 // deployment = queryDeployment.execute();
566 deployment = executeAndRecordCloudifyRequest(queryDeployment);
568 outputs = getDeploymentOutputs (cloudify, deploymentId);
570 // Next look for the latest execution
571 ListExecutions listExecutions = cloudify.executions().listFiltered ("deployment_id=" + deploymentId, "-created_at");
572 Executions executions = listExecutions.execute();
574 // If no executions, does this give NOT_FOUND or empty set?
575 if (executions.getItems().isEmpty()) {
576 return new DeploymentInfo (deployment);
579 return new DeploymentInfo (deployment, outputs, executions.getItems().get(0));
582 catch (CloudifyConnectException ce) {
583 // Couldn't connect to Cloudify
584 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeployment: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeployment: Cloudify connection failure");
585 throw new MsoIOException (ce.getMessage(), ce);
587 catch (CloudifyResponseException re) {
588 if (re.getStatus () == 404) {
589 // Got a NOT FOUND error. React differently based on deployment vs. execution
590 if (deployment != null) {
591 // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
592 return new DeploymentInfo (deployment, outputs, null);
594 // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
595 return new DeploymentInfo (deploymentId);
598 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
600 catch (Exception e) {
602 throw new MsoAdapterException (e.getMessage(), e);
608 * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be
609 * considered a successful deletion. The return value is a DeploymentInfo object which
610 * contains the last deployment status.
612 * There is no rollback from a successful deletion. A deletion failure will
613 * also result in an undefined deployment state - the components may or may not have been
614 * all or partially deleted, so the resulting deployment must be considered invalid.
616 * @param tenantId The Openstack ID of the tenant in which to perform the delete
617 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
618 * @param stackName The name/id of the stack to delete. May be simple or canonical
619 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
620 * @return A StackInfo object
621 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
622 * @throws MsoCloudSiteNotFound
624 public DeploymentInfo uninstallAndDeleteDeployment (String cloudSiteId,
627 int timeoutMinutes) throws MsoException
629 // Obtain the cloud site information where we will create the stack
630 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
631 if (!cloudSite.isPresent()) {
632 throw new MsoCloudSiteNotFound (cloudSiteId);
635 Cloudify cloudify = getCloudifyClient (cloudSite.get());
637 LOGGER.debug ("Ready to Uninstall/Delete Deployment (" + deploymentId + ")");
639 // Query first to save the trouble if deployment not found
640 Deployment deployment = null;
642 GetDeployment queryDeploymentRequest = cloudify.deployments().byId(deploymentId);
643 LOGGER.debug (queryDeploymentRequest.toString());
645 deployment = executeAndRecordCloudifyRequest (queryDeploymentRequest);
647 catch (CloudifyResponseException e) {
648 // Since this came on the 'Create Deployment' command, nothing was changed
649 // in the cloud. Return the error as an exception.
650 if (e.getStatus () == 404) {
651 // Deployment doesn't exist. Return a "NOTFOUND" DeploymentInfo object
652 // TODO: Should return NULL?
653 LOGGER.debug("Deployment requested for deletion does not exist: " + deploymentId);
654 return new DeploymentInfo (deploymentId, DeploymentStatus.NOTFOUND);
656 // Convert the CloudifyResponseException to an MsoOpenstackException
657 LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
658 MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
659 me.setCategory (MsoExceptionCategory.INTERNAL);
662 } catch (CloudifyConnectException e) {
663 // Error connecting to Cloudify instance. Convert to an MsoException
664 throw cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
665 } catch (RuntimeException e) {
667 throw runtimeExceptionToMsoException (e, DELETE_DEPLOYMENT);
671 * Query the outputs before deleting so they can be returned as well
673 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
676 * Next execute the "uninstall" workflow.
677 * Note - this assumes there are no additional parameters required for the workflow.
679 // TODO: No deletePollInterval that I'm aware of. Use the create interval
680 int deletePollInterval = Integer.parseInt(this.environment.getProperty (deletePollIntervalProp, deletePollIntervalDefault));
681 int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
683 Execution uninstallWorkflow = null;
686 uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, true, pollTimeout, deletePollInterval);
688 if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
689 // Successful uninstall.
690 LOGGER.debug("Uninstall successful for deployment " + deploymentId);
693 // The uninstall workflow completed with an error. Must fail the request, but will
694 // leave the deployment in an indeterminate state, as cloud resources may still exist.
695 MsoCloudifyException me = new MsoCloudifyException (0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
696 me.addContext (DELETE_DEPLOYMENT);
697 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
701 catch (MsoException me) {
702 // Uninstall workflow has failed.
703 // Must fail the deletion... may leave the deployment in an inconclusive state
704 me.addContext (DELETE_DEPLOYMENT);
705 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
709 // At this point, the deployment has been successfully uninstalled.
710 // Next step is to delete the deployment itself
712 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
713 LOGGER.debug(deleteRequest.toString());
715 // The delete request returns the deleted deployment
716 deployment = deleteRequest.execute();
719 catch (CloudifyConnectException ce) {
720 // Failed to delete. Must fail the request, but will leave the (uninstalled)
721 // deployment in Cloudify DB.
722 MsoCloudifyException me = new MsoCloudifyException (0, "Deployment Delete Failed", ce.getMessage(), ce);
723 me.addContext (DELETE_DEPLOYMENT);
724 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
727 catch (CloudifyResponseException re) {
728 // Failed to delete. Must fail the request, but will leave the (uninstalled)
729 // deployment in the Cloudify DB.
730 MsoCloudifyException me = new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getMessage(), re);
731 me.addContext (DELETE_DEPLOYMENT);
732 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
735 catch (Exception e) {
737 MsoAdapterException ae = new MsoAdapterException (e.getMessage(), e);
738 ae.addContext (DELETE_DEPLOYMENT);
739 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, ae.getContextMessage());
743 // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
744 return new DeploymentInfo (deployment, outputs, uninstallWorkflow);
749 * Check if a blueprint is available for use at a targeted cloud site.
750 * This requires checking the Cloudify Manager which is servicing that
751 * cloud site to see if the specified blueprint has been loaded.
753 * @param cloudSiteId The cloud site where the blueprint is needed
754 * @param blueprintId The ID for the blueprint in Cloudify
756 public boolean isBlueprintLoaded (String cloudSiteId, String blueprintId)
759 // Obtain the cloud site information where we will load the blueprint
760 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
761 if (!cloudSite.isPresent()) {
762 throw new MsoCloudSiteNotFound (cloudSiteId);
765 Cloudify cloudify = getCloudifyClient (cloudSite.get());
767 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
769 Blueprint bp = getRequest.execute();
770 LOGGER.debug("Blueprint exists: " + bp.getId());
773 catch (CloudifyResponseException ce) {
774 if (ce.getStatus() == 404) {
779 } catch (Exception e) {
785 * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site.
786 * The blueprint currently must be structured as a single directory with all
787 * of the required files. One of those files is designated the "main file"
788 * for the blueprint. Files are provided as byte arrays, though expect only
789 * text files will be distributed from ASDC and stored by MSO.
791 * Cloudify requires a single root directory in its blueprint zip files.
792 * The requested blueprint ID will also be used as the directory.
793 * All of the files will be added to this directory in the zip file.
795 public void uploadBlueprint (String cloudSiteId,
798 Map<String,byte[]> blueprintFiles,
799 boolean failIfExists)
802 // Obtain the cloud site information where we will load the blueprint
803 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
804 if (!cloudSite.isPresent()) {
805 throw new MsoCloudSiteNotFound (cloudSiteId);
808 Cloudify cloudify = getCloudifyClient (cloudSite.get());
810 boolean blueprintUploaded = uploadBlueprint (cloudify, blueprintId, mainFileName, blueprintFiles);
812 if (!blueprintUploaded && failIfExists) {
813 throw new MsoAdapterException ("Blueprint already exists");
818 * Common method to load a blueprint. May be called from
820 protected boolean uploadBlueprint (Cloudify cloudify, String blueprintId, String mainFileName, Map<String,byte[]> blueprintFiles)
823 // Check if it already exists. If so, return false.
824 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
826 Blueprint bp = getRequest.execute();
827 LOGGER.debug("Blueprint " + bp.getId() + " already exists.");
830 catch (CloudifyResponseException ce) {
831 if (ce.getStatus() == 404) {
832 // This is the expected result.
833 LOGGER.debug("Verified that Blueprint doesn't exist yet");
837 } catch (Exception e) {
841 // Create a blueprint ZIP file in memory
842 ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
843 ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
846 // Put the root directory
847 String rootDir = blueprintId + ((blueprintId.endsWith("/") ? "" : "/"));
848 zipOut.putNextEntry(new ZipEntry (rootDir));
851 for (String fileName : blueprintFiles.keySet()) {
852 ZipEntry ze = new ZipEntry (rootDir + fileName);
853 zipOut.putNextEntry (ze);
854 zipOut.write (blueprintFiles.get(fileName));
859 catch (IOException e) {
860 // Since we're writing to a byte array, this should never happen
862 LOGGER.debug ("Blueprint zip file size: " + zipBuffer.size());
864 // Ready to upload the blueprint zip
866 try (InputStream blueprintStream = new ByteArrayInputStream (zipBuffer.toByteArray())) {
867 UploadBlueprint uploadRequest = cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
868 Blueprint blueprint = uploadRequest.execute();
869 System.out.println("Successfully uploaded blueprint " + blueprint.getId());
871 catch (CloudifyResponseException | CloudifyConnectException e) {
872 throw cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
874 catch (RuntimeException e) {
876 throw runtimeExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
877 } catch (IOException e) {
878 // for try-with-resources
879 throw ioExceptionToMsoException(e, "UPLOAD_BLUEPRINT");
887 // ---------------------------------------------------------------
888 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
891 * Get a Cloudify client for the specified cloud site.
892 * Everything that is required can be found in the Cloud Config.
895 * @return a Cloudify object
897 public Cloudify getCloudifyClient (CloudSite cloudSite) throws MsoException
899 CloudifyManager cloudifyConfig = cloudConfig.getCloudifyManager(cloudSite.getCloudifyId());
900 if (cloudifyConfig == null) {
901 throw new MsoCloudifyManagerNotFound (cloudConfig.getCloudSiteId(cloudSite));
904 // Get a Cloudify client
905 // Set a Token Provider to fetch tokens from Cloudify itself.
906 String cloudifyUrl = cloudifyConfig.getCloudifyUrl();
907 Cloudify cloudify = new Cloudify (cloudifyUrl);
908 cloudify.setTokenProvider(new CloudifyClientTokenProvider(cloudifyUrl, cloudifyConfig.getUsername(), CryptoUtils.decryptCloudConfigPassword(cloudifyConfig.getPassword())));
915 * Query for a Cloudify Deployment. This function is needed in several places, so
916 * a common method is useful. This method takes an authenticated CloudifyClient
917 * (which internally identifies the cloud & tenant to search), and returns
918 * a Deployment object if found, Null if not found, or an MsoCloudifyException
919 * if the Cloudify API call fails.
921 * @param cloudifyClient an authenticated Cloudify client
923 * @param deploymentId the deployment to query
925 * @return a Deployment object or null if the requested deployment doesn't exist.
927 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception
929 protected Deployment queryDeployment (Cloudify cloudify, String deploymentId) throws MsoException {
930 if (deploymentId == null) {
934 GetDeployment request = cloudify.deployments().byId (deploymentId);
935 return executeAndRecordCloudifyRequest (request);
936 } catch (CloudifyResponseException e) {
937 if (e.getStatus () == 404) {
938 LOGGER.debug ("queryDeployment - not found: " + deploymentId);
941 // Convert the CloudifyResponseException to an MsoCloudifyException
942 throw cloudifyExceptionToMsoException (e, "QueryDeployment");
944 } catch (CloudifyConnectException e) {
945 // Connection to Openstack failed
946 throw cloudifyExceptionToMsoException (e, "QueryDeployment");
951 public void copyStringOutputsToInputs(Map<String, String> inputs,
952 Map<String, Object> otherStackOutputs, boolean overWrite) {
953 if (inputs == null || otherStackOutputs == null)
956 for (Map.Entry<String, Object> entry : otherStackOutputs.entrySet()) {
957 String key = entry.getKey();
958 Object value = entry.getValue();
960 if (value instanceof JsonNode) {
961 // This is a bit of mess - but I think it's the least impacting
962 // let's convert it BACK to a string - then it will get converted back later
964 inputs.put(key, this.convertNode((JsonNode) value));
965 } catch (Exception e) {
966 LOGGER.debug("WARNING: unable to convert JsonNode output value for "+ key);
967 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
969 } else if (value instanceof java.util.LinkedHashMap) {
970 LOGGER.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
972 inputs.put(key, JSON_MAPPER.writeValueAsString(value));
973 } catch (Exception e) {
974 LOGGER.debug("WARNING: unable to convert LinkedHashMap output value for "+ key);
977 // just try to cast it - could be an integer or some such
979 inputs.put(key, (String) value);
980 } catch (Exception e) {
981 LOGGER.debug("WARNING: unable to convert output value for "+ key);
982 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
990 * Normalize an input value to an Object, based on the target parameter type.
991 * If the type is not recognized, it will just be returned unchanged (as a string).
993 public Object convertInputValue (String inputValue, HeatTemplateParam templateParam)
995 String type = templateParam.getParamType();
996 LOGGER.debug("Parameter: " + templateParam.getParamName() + " is of type " + type);
998 if (type.equalsIgnoreCase("number")) {
1000 return Integer.valueOf(inputValue);
1002 catch (Exception e) {
1003 LOGGER.debug("Unable to convert " + inputValue + " to an integer!");
1006 } else if (type.equalsIgnoreCase("json")) {
1008 return new ObjectMapper().readTree(inputValue);
1010 catch (Exception e) {
1011 LOGGER.debug("Unable to convert " + inputValue + " to a JsonNode!");
1014 } else if (type.equalsIgnoreCase("boolean")) {
1015 return new Boolean(inputValue);
1018 // Nothing else matched. Return the original string
1023 private String convertNode(final JsonNode node) {
1025 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
1026 return JSON_MAPPER.writeValueAsString(obj);
1027 } catch (JsonParseException jpe) {
1028 LOGGER.debug("Error converting json to string " + jpe.getMessage());
1029 } catch (Exception e) {
1030 LOGGER.debug("Error converting json to string " + e.getMessage());
1032 return "[Error converting json to string]";
1037 * Method to execute a Cloudify command and track its execution time.
1038 * For the metrics log, a category of "Cloudify" is used along with a
1039 * sub-category that identifies the specific call (using the real
1040 * cloudify-client classname of the CloudifyRequest<T> parameter).
1044 protected <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request) {
1047 if (request.getClass ().getEnclosingClass () != null) {
1048 requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
1049 + request.getClass ().getSimpleName ();
1051 requestType = request.getClass ().getSimpleName ();
1054 int retryDelay = poConfig.getRetryDelay();
1055 int retryCount = poConfig.getRetryCount();
1056 String retryCodes = poConfig.getRetryCodes();
1058 // Run the actual command. All exceptions will be propagated
1062 return request.execute ();
1064 catch (CloudifyResponseException e) {
1065 boolean retry = false;
1066 if (retryCodes != null ) {
1067 int code = e.getStatus();
1068 LOGGER.debug ("Config values RetryDelay:" + retryDelay + " RetryCount:" + retryCount + " RetryCodes:" + retryCodes + " ResponseCode:" + code);
1069 for (String rCode : retryCodes.split (",")) {
1071 if (retryCount > 0 && code == Integer.parseInt (rCode))
1075 LOGGER.debug ("CloudifyResponseException ResponseCode:" + code + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
1078 } catch (NumberFormatException e1) {
1079 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");
1086 sleep(retryDelay * 1000L);
1089 throw e; // exceeded retryCount or code is not retryable
1091 catch (CloudifyConnectException e) {
1092 // Connection to Cloudify failed
1096 LOGGER.debug (" request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
1097 sleep(retryDelay * 1000L);
1106 * Convert an Exception on a Cloudify call to an MsoCloudifyException.
1107 * This method supports CloudifyResponseException and CloudifyConnectException.
1109 protected MsoException cloudifyExceptionToMsoException (CloudifyBaseException e, String context) {
1110 MsoException me = null;
1112 if (e instanceof CloudifyResponseException) {
1113 CloudifyResponseException re = (CloudifyResponseException) e;
1116 // Failed Cloudify calls return an error entity body.
1117 CloudifyError error = re.getResponse ().getErrorEntity (CloudifyError.class);
1118 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY, "Cloudify Error on " + context + ": " + error.getErrorCode(), CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Exception - Cloudify Error on " + context);
1119 String fullError = error.getErrorCode() + ": " + error.getMessage();
1120 LOGGER.debug(fullError);
1121 me = new MsoCloudifyException (re.getStatus(),
1124 } catch (Exception e2) {
1125 // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
1126 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);
1127 me = new MsoCloudifyException (re.getStatus (), re.getMessage (), "");
1130 // Add the context of the error
1131 me.addContext (context);
1133 // Generate an alarm for 5XX and higher errors.
1134 if (re.getStatus () >= 500) {
1135 alarmLogger.sendAlarm (CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage ());
1137 } else if (e instanceof CloudifyConnectException) {
1138 CloudifyConnectException ce = (CloudifyConnectException) e;
1140 me = new MsoIOException (ce.getMessage ());
1141 me.addContext (context);
1143 // Generate an alarm for all connection errors.
1144 alarmLogger.sendAlarm ("CloudifyIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
1145 LOGGER.error(MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY, "Cloudify connection error on " + context + ": " + e, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Cloudify connection error on " + context);
1153 /*******************************************************************************
1155 * Methods (and associated utilities) to implement the VduPlugin interface
1157 *******************************************************************************/
1160 * VduPlugin interface for instantiate function.
1162 * This one is a bit more complex, in that it will first upload the blueprint if needed,
1163 * then create the Cloudify deployment and execute the install workflow.
1165 * This implementation also merges any parameters defined in the ENV file with the other
1166 * other input parameters for any undefined parameters).
1167 * The basic MsoCloudifyUtils separates blueprint management from deploument actions,
1168 * but the VduPlugin does not declare blueprint management operations.
1171 public VduInstance instantiateVdu (
1172 CloudInfo cloudInfo,
1173 String instanceName,
1174 Map<String,Object> inputs,
1175 VduModelInfo vduModel,
1176 boolean rollbackOnFailure)
1179 String cloudSiteId = cloudInfo.getCloudSiteId();
1180 String tenantId = cloudInfo.getTenantId();
1182 // Translate the VDU ModelInformation structure to that which is needed for
1183 // creating and uploading a blueprint. Use the model customization UUID as
1184 // the blueprint identifier.
1186 String blueprintId = vduModel.getModelCustomizationUUID();
1190 if (! isBlueprintLoaded (cloudSiteId, blueprintId)) {
1191 LOGGER.debug ("Blueprint " + blueprintId + " is not loaded. Will upload it now.");
1193 // Prepare the blueprint inputs. Need the set of blueprint templates and files,
1194 // plus the main blueprint name.
1195 Map<String,byte[]> blueprintFiles = new HashMap<>();
1196 String mainTemplate = "";
1198 // Add all of the blueprint artifacts from the VDU model
1199 List<VduArtifact> vduArtifacts = vduModel.getArtifacts();
1200 for (VduArtifact vduArtifact: vduArtifacts)
1202 // Add all artifacts to the blueprint, with one exception.
1203 // ENVIRONMENT files will be processed later as additional parameters.
1205 ArtifactType artifactType = vduArtifact.getType();
1206 if (artifactType != ArtifactType.ENVIRONMENT) {
1207 blueprintFiles.put(vduArtifact.getName(), vduArtifact.getContent());
1209 if (artifactType == ArtifactType.MAIN_TEMPLATE) {
1210 mainTemplate = vduArtifact.getName();
1215 // Upload the blueprint package
1216 uploadBlueprint(cloudSiteId, blueprintId, mainTemplate, blueprintFiles, false);
1219 catch (Exception e) {
1220 throw new VduException ("CloudifyUtils (instantiateVDU): blueprint Exception", e);
1224 // Next, create and install a new deployment based on the blueprint.
1225 // For Cloudify, the deploymentId is specified by the client. Just use the instance name
1229 // Query the Cloudify Deployment object and populate a VduInstance
1230 DeploymentInfo deployment = createAndInstallDeployment (cloudSiteId,
1235 true, // (poll for completion)
1236 vduModel.getTimeoutMinutes(),
1239 return deploymentInfoToVduInstance(deployment);
1241 catch (Exception e) {
1242 throw new VduException ("CloudifyUtils (instantiateVDU): Create-and-install-deployment Exception", e);
1248 * VduPlugin interface for query function.
1251 public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
1254 String cloudSiteId = cloudInfo.getCloudSiteId();
1255 String tenantId = cloudInfo.getTenantId();
1258 // Query the Cloudify Deployment object and populate a VduInstance
1259 DeploymentInfo deployment = queryDeployment (cloudSiteId, tenantId, instanceId);
1261 return deploymentInfoToVduInstance(deployment);
1263 catch (Exception e) {
1264 throw new VduException ("Query VDU Exception", e);
1270 * VduPlugin interface for delete function.
1273 public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
1276 String cloudSiteId = cloudInfo.getCloudSiteId();
1277 String tenantId = cloudInfo.getTenantId();
1280 // Uninstall and delete the Cloudify Deployment
1281 DeploymentInfo deployment = uninstallAndDeleteDeployment (cloudSiteId, tenantId, instanceId, timeoutMinutes);
1283 // Populate a VduInstance based on the deleted Cloudify Deployment object
1284 return deploymentInfoToVduInstance(deployment);
1286 catch (Exception e) {
1287 throw new VduException ("Delete VDU Exception", e);
1293 * VduPlugin interface for update function.
1295 * Update is currently not supported in the MsoCloudifyUtils implementation.
1296 * Just return a VduException.
1300 public VduInstance updateVdu (
1301 CloudInfo cloudInfo,
1303 Map<String,Object> inputs,
1304 VduModelInfo vduModel,
1305 boolean rollbackOnFailure)
1308 throw new VduException ("CloudifyUtils: updateVDU interface not supported");
1313 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1315 protected VduInstance deploymentInfoToVduInstance (DeploymentInfo deployment)
1317 VduInstance vduInstance = new VduInstance();
1319 // only one ID in Cloudify, use for both VDU name and ID
1320 vduInstance.setVduInstanceId(deployment.getId());
1321 vduInstance.setVduInstanceName(deployment.getId());
1323 // Copy inputs and outputs
1324 vduInstance.setInputs(deployment.getInputs());
1325 vduInstance.setOutputs(deployment.getOutputs());
1327 // Translate the status elements
1328 vduInstance.setStatus(deploymentStatusToVduStatus (deployment));
1333 protected VduStatus deploymentStatusToVduStatus (DeploymentInfo deployment)
1335 VduStatus vduStatus = new VduStatus();
1337 // Determine the status based on last action & status
1338 // DeploymentInfo object should be enhanced to report a better status internally.
1339 DeploymentStatus status = deployment.getStatus();
1341 if (status == null) {
1342 vduStatus.setState(VduStateType.UNKNOWN);
1344 else if (status == DeploymentStatus.NOTFOUND) {
1345 vduStatus.setState(VduStateType.NOTFOUND);
1347 else if (status == DeploymentStatus.INSTALLED) {
1348 vduStatus.setState(VduStateType.INSTANTIATED);
1350 else if (status == DeploymentStatus.CREATED) {
1351 // Deployment exists but is not installed. This shouldn't really happen,
1352 // since create + install or uninstall + delete are always done together.
1353 // But account for it anyway, assuming the operation is still in progress.
1354 String lastAction = deployment.getLastAction();
1355 if (lastAction == null)
1356 vduStatus.setState(VduStateType.INSTANTIATING);
1358 vduStatus.setState(VduStateType.DELETING);
1360 else if (status == DeploymentStatus.FAILED) {
1361 vduStatus.setState(VduStateType.FAILED);
1363 vduStatus.setState(VduStateType.UNKNOWN);
1366 vduStatus.setErrorMessage(deployment.getErrorMessage());
1367 vduStatus.setLastAction(new PluginAction(deployment.getLastAction(), deployment.getActionStatus(), deployment.getErrorMessage()));
1373 * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in.
1374 * Base the values on the CloudSite definition.
1376 protected OpenstackConfig getOpenstackConfig (CloudSite cloudSite, String tenantId) {
1377 OpenstackConfig openstackConfig = new OpenstackConfig();
1378 openstackConfig.setRegion (cloudSite.getRegionId());
1379 openstackConfig.setAuthUrl (cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getIdentityUrl());
1380 openstackConfig.setUsername (cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getMsoId());
1381 openstackConfig.setPassword (CryptoUtils.decryptCloudConfigPassword(cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getMsoPass()));
1382 openstackConfig.setTenantName (tenantId);
1383 return openstackConfig;
1387 * Return an Azure object as expected by Cloudify Azure Plug-in.
1388 * Base the values on the CloudSite definition.
1390 protected AzureConfig getAzureConfig (CloudSite cloudSite, String tenantId) {
1391 AzureConfig azureConfig = new AzureConfig();
1392 // TODO: Use adminTenant for now, instead of adding another element
1393 azureConfig.setSubscriptionId (cloudSite.getIdentityService().getAdminTenant());
1394 azureConfig.setTenantId (tenantId);
1395 azureConfig.setClientId (cloudSite.getIdentityService().getMsoId());
1396 azureConfig.setClientSecret (cloudSite.getIdentityService().getMsoPass());
1400 private void sleep(long time) {
1403 } catch (InterruptedException e) {
1404 LOGGER.debug("Thread interrupted while sleeping!", e);
1405 Thread.currentThread().interrupt();