2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2018 Nokia.
8 * ================================================================================
9 * Modifications Copyright (c) 2019 Samsung
10 * ================================================================================
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
15 * http://www.apache.org/licenses/LICENSE-2.0
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 * ============LICENSE_END=========================================================
25 package org.onap.so.cloudify.utils;
27 import com.fasterxml.jackson.core.JsonParseException;
28 import com.fasterxml.jackson.databind.JsonNode;
29 import com.fasterxml.jackson.databind.ObjectMapper;
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.util.HashMap;
35 import java.util.List;
37 import java.util.Optional;
38 import java.util.zip.ZipEntry;
39 import java.util.zip.ZipOutputStream;
40 import org.onap.so.adapters.vdu.CloudInfo;
41 import org.onap.so.adapters.vdu.PluginAction;
42 import org.onap.so.adapters.vdu.VduArtifact;
43 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
44 import org.onap.so.adapters.vdu.VduException;
45 import org.onap.so.adapters.vdu.VduInstance;
46 import org.onap.so.adapters.vdu.VduModelInfo;
47 import org.onap.so.adapters.vdu.VduPlugin;
48 import org.onap.so.adapters.vdu.VduStateType;
49 import org.onap.so.adapters.vdu.VduStatus;
50 import org.onap.so.cloud.CloudConfig;
51 import org.onap.so.cloudify.base.client.CloudifyBaseException;
52 import org.onap.so.cloudify.base.client.CloudifyClientTokenProvider;
53 import org.onap.so.cloudify.base.client.CloudifyConnectException;
54 import org.onap.so.cloudify.base.client.CloudifyRequest;
55 import org.onap.so.cloudify.base.client.CloudifyResponseException;
56 import org.onap.so.cloudify.beans.DeploymentInfo;
57 import org.onap.so.cloudify.beans.DeploymentInfoBuilder;
58 import org.onap.so.cloudify.beans.DeploymentStatus;
59 import org.onap.so.cloudify.exceptions.MsoCloudifyException;
60 import org.onap.so.cloudify.exceptions.MsoCloudifyManagerNotFound;
61 import org.onap.so.cloudify.exceptions.MsoDeploymentAlreadyExists;
62 import org.onap.so.cloudify.v3.client.BlueprintsResource.GetBlueprint;
63 import org.onap.so.cloudify.v3.client.BlueprintsResource.UploadBlueprint;
64 import org.onap.so.cloudify.v3.client.Cloudify;
65 import org.onap.so.cloudify.v3.client.DeploymentsResource.CreateDeployment;
66 import org.onap.so.cloudify.v3.client.DeploymentsResource.DeleteDeployment;
67 import org.onap.so.cloudify.v3.client.DeploymentsResource.GetDeployment;
68 import org.onap.so.cloudify.v3.client.DeploymentsResource.GetDeploymentOutputs;
69 import org.onap.so.cloudify.v3.client.ExecutionsResource.CancelExecution;
70 import org.onap.so.cloudify.v3.client.ExecutionsResource.GetExecution;
71 import org.onap.so.cloudify.v3.client.ExecutionsResource.ListExecutions;
72 import org.onap.so.cloudify.v3.client.ExecutionsResource.StartExecution;
73 import org.onap.so.cloudify.v3.model.AzureConfig;
74 import org.onap.so.cloudify.v3.model.Blueprint;
75 import org.onap.so.cloudify.v3.model.CancelExecutionParams;
76 import org.onap.so.cloudify.v3.model.CloudifyError;
77 import org.onap.so.cloudify.v3.model.CreateDeploymentParams;
78 import org.onap.so.cloudify.v3.model.Deployment;
79 import org.onap.so.cloudify.v3.model.DeploymentOutputs;
80 import org.onap.so.cloudify.v3.model.Execution;
81 import org.onap.so.cloudify.v3.model.Executions;
82 import org.onap.so.cloudify.v3.model.OpenstackConfig;
83 import org.onap.so.cloudify.v3.model.StartExecutionParams;
84 import org.onap.so.config.beans.PoConfig;
85 import org.onap.so.db.catalog.beans.CloudSite;
86 import org.onap.so.db.catalog.beans.CloudifyManager;
87 import org.onap.so.db.catalog.beans.HeatTemplateParam;
88 import org.onap.so.logger.ErrorCode;
89 import org.onap.so.logger.MessageEnum;
90 import org.onap.so.openstack.exceptions.MsoAdapterException;
91 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
92 import org.onap.so.openstack.exceptions.MsoException;
93 import org.onap.so.openstack.exceptions.MsoExceptionCategory;
94 import org.onap.so.openstack.exceptions.MsoIOException;
95 import org.onap.so.openstack.exceptions.MsoOpenstackException;
96 import org.onap.so.openstack.utils.MsoCommonUtils;
97 import org.onap.so.utils.CryptoUtils;
98 import org.slf4j.Logger;
99 import org.slf4j.LoggerFactory;
100 import org.springframework.beans.factory.annotation.Autowired;
101 import org.springframework.core.env.Environment;
102 import org.springframework.stereotype.Component;
105 public class MsoCloudifyUtils extends MsoCommonUtils implements VduPlugin{
107 private static final String CLOUDIFY_ERROR = "CloudifyError";
108 private static final String CLOUDIFY = "Cloudify";
109 private static final String CREATE_DEPLOYMENT = "CreateDeployment";
110 private static final String DELETE_DEPLOYMENT = "DeleteDeployment";
111 private static final String TERMINATED = "terminated";
112 private static final String CANCELLED = "cancelled";
114 // Fetch cloud configuration each time (may be cached in CloudConfig class)
116 protected CloudConfig cloudConfig;
119 private Environment environment;
122 private PoConfig poConfig;
124 private static final Logger logger = LoggerFactory.getLogger(MsoCloudifyUtils.class);
126 // Properties names and variables (with default values)
127 protected String createPollIntervalProp = "org.onap.so.adapters.po.pollInterval";
128 private String deletePollIntervalProp = "org.onap.so.adapters.po.pollInterval";
130 protected String createPollIntervalDefault = "15";
131 private String deletePollIntervalDefault = "15";
133 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
136 * Create a new Deployment from a specified blueprint, and install it in the specified
137 * cloud location and tenant. The blueprint identifier and parameter map are passed in
138 * as arguments, along with the cloud access credentials. The blueprint should have been
139 * previously uploaded to Cloudify.
141 * It is expected that parameters have been validated and contain at minimum the required
142 * parameters for the given template with no extra (undefined) parameters..
144 * The deployment ID supplied by the caller must be unique in the scope of the Cloudify
145 * tenant (not the Openstack tenant). However, it should also be globally unique, as it
146 * will be the identifier for the resource going forward in Inventory. This latter is
147 * managed by the higher levels invoking this function.
149 * This function executes the "install" workflow on the newly created workflow. Cloudify
150 * will be polled for completion unless the client requests otherwise.
152 * An error will be thrown if the requested Deployment already exists in the specified
155 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
156 * @param tenantId The Openstack ID of the tenant in which to create the Stack
157 * @param deploymentId The identifier (name) of the deployment to create
158 * @param blueprintId The blueprint from which to create the deployment.
159 * @param inputs A map of key/value inputs
160 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
161 * @param timeoutMinutes Timeout after which the "install" will be cancelled
162 * @param backout Flag to delete deployment on install Failure - defaulted to True
163 * @return A DeploymentInfo object
164 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
165 * @throws MsoIOException Thrown on Cloudify connection errors.
168 public DeploymentInfo createAndInstallDeployment (String cloudSiteId,
172 Map <String, ? extends Object> inputs,
173 boolean pollForCompletion,
175 boolean backout) throws MsoException
177 // Obtain the cloud site information where we will create the stack
178 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
179 if (!cloudSite.isPresent()) {
180 throw new MsoCloudSiteNotFound (cloudSiteId);
183 Cloudify cloudify = getCloudifyClient (cloudSite.get());
185 logger.debug("Ready to Create Deployment ({}) with input params: {}", deploymentId, inputs);
187 // Build up the inputs, including:
188 // - from provided "environment" file
189 // - passed in by caller
190 // - special input for cloud-specific Credentials
191 Map<String,Object> expandedInputs = new HashMap<> (inputs);
193 String platform = cloudSite.get().getPlatform();
194 if (platform == null || platform.equals("") || platform.equalsIgnoreCase("OPENSTACK")) {
195 // Create the Cloudify OpenstackConfig with the credentials
196 OpenstackConfig openstackConfig = getOpenstackConfig (cloudSite.get(), tenantId);
197 expandedInputs.put("openstack_config", openstackConfig);
198 } else if (platform.equalsIgnoreCase("AZURE")) {
199 // Create Cloudify AzureConfig with the credentials
200 AzureConfig azureConfig = getAzureConfig (cloudSite.get(), tenantId);
201 expandedInputs.put("azure_config", azureConfig);
204 // Build up the parameters to create a new deployment
205 CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
206 deploymentParams.setBlueprintId(blueprintId);
207 deploymentParams.setInputs(expandedInputs);
209 Deployment deployment = null;
211 CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
212 logger.debug(createDeploymentRequest.toString());
214 deployment = executeAndRecordCloudifyRequest (createDeploymentRequest);
216 catch (CloudifyResponseException e) {
217 // Since this came on the 'Create Deployment' command, nothing was changed
218 // in the cloud. Return the error as an exception.
219 if (e.getStatus () == 409) {
220 // Deployment already exists. Return a specific error for this case
221 MsoException me = new MsoDeploymentAlreadyExists (deploymentId, cloudSiteId);
222 me.addContext (CREATE_DEPLOYMENT);
225 // Convert the CloudifyResponseException to an MsoException
226 logger.debug("ERROR STATUS = {},\n{}\n{}", e.getStatus(), e.getMessage(), e.getLocalizedMessage());
227 MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
228 me.setCategory (MsoExceptionCategory.OPENSTACK);
231 } catch (CloudifyConnectException e) {
232 // Error connecting to Cloudify instance. Convert to an MsoException
233 throw cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
234 } catch (RuntimeException e) {
236 throw runtimeExceptionToMsoException (e, CREATE_DEPLOYMENT);
240 * It can take some time for Cloudify to be ready to execute a workflow
241 * on the deployment. Sleep 30 seconds based on observation of behavior
242 * in a Cloudify VM instance (delay due to "create_deployment_environment").
247 * Next execute the "install" workflow.
248 * Note - this assumes there are no additional parameters required for the workflow.
250 int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
251 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
253 Execution installWorkflow = null;
256 installWorkflow = executeWorkflow (cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout, createPollInterval);
258 if (installWorkflow.getStatus().equals(TERMINATED)) {
260 // Create and return a DeploymentInfo structure. Include the Runtime outputs
261 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
262 return new DeploymentInfoBuilder()
263 .withId(deployment.getId())
264 .withDeploymentInputs(deployment.getInputs())
265 .withDeploymentOutputs(outputs.getOutputs())
266 .fromExecution(installWorkflow)
270 // The workflow completed with errors. Must try to back it out.
273 logger.warn("{} Deployment installation failed, backout deletion suppressed {} {}",
274 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(),
275 "Exception in Deployment Installation, backout suppressed");
278 // Poll on delete if we rollback - use same values for now
279 int deletePollInterval = createPollInterval;
280 int deletePollTimeout = pollTimeout;
283 // Run the uninstall to undo the install
284 Execution uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
286 if (uninstallWorkflow.getStatus().equals(TERMINATED))
288 // The uninstall completed. Delete the deployment itself
289 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
290 executeAndRecordCloudifyRequest (deleteRequest);
293 // Didn't uninstall successfully. Log this error
294 logger.error("{} Create Deployment: Cloudify error rolling back deployment install: {} {}",
295 MessageEnum.RA_CREATE_STACK_ERR, installWorkflow.getError(),
296 ErrorCode.BusinessProcesssError.getValue());
299 catch (Exception e) {
300 // Catch-all for backout errors trying to uninstall/delete
301 // Log this error, and return the original exception
302 logger.error("{} Create Stack: Nested exception rolling back deployment install: {}",
303 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(), e);
307 MsoCloudifyException me = new MsoCloudifyException (0, "Workflow Execution Failed", installWorkflow.getError());
308 me.addContext (CREATE_DEPLOYMENT);
313 catch (MsoException me) {
314 // Install failed. Unless requested otherwise, back out the deployment
318 logger.warn("{} Deployment installation failed, backout deletion suppressed {}",
319 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue());
322 // Poll on delete if we rollback - use same values for now
323 int deletePollInterval = createPollInterval;
324 int deletePollTimeout = pollTimeout;
327 // Run the uninstall to undo the install.
328 // Always try to run it, as it should be idempotent
329 executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
331 // Delete the deployment itself
332 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
333 executeAndRecordCloudifyRequest (deleteRequest);
335 catch (Exception e) {
336 // Catch-all for backout errors trying to uninstall/delete
337 // Log this error, and return the original exception
338 logger.error("{} Create Stack: Nested exception rolling back deployment install: {} ",
339 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(), e);
343 // Propagate the original exception from Stack Query.
344 me.addContext (CREATE_DEPLOYMENT);
352 * Get the runtime Outputs of a deployment.
353 * Return the Map of tag/value outputs.
355 private DeploymentOutputs getDeploymentOutputs (Cloudify cloudify, String deploymentId)
358 // Build and send the Cloudify request
359 DeploymentOutputs deploymentOutputs = null;
361 GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
362 logger.debug(queryDeploymentOutputs.toString());
364 deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs);
366 catch (CloudifyConnectException ce) {
367 // Couldn't connect to Cloudify
368 logger.error("{} QueryDeploymentOutputs: Cloudify connection failure: {} ",
369 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(), ce);
370 throw new MsoIOException (ce.getMessage(), ce);
372 catch (CloudifyResponseException re) {
373 if (re.getStatus () == 404) {
377 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
379 catch (Exception e) {
381 throw new MsoAdapterException (e.getMessage(), e);
384 return deploymentOutputs;
388 * Execute a workflow on a deployment. Handle polling for completion with timeout.
389 * Return the final Execution object with status.
390 * Throw an exception on Errors.
391 * Question - how does the client know whether rollback needs to be done?
393 private Execution executeWorkflow (Cloudify cloudify, String deploymentId, String workflowId, Map<String,Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
394 throws MsoCloudifyException
396 logger.debug("Executing '{}' workflow on deployment '{}'", workflowId, deploymentId);
398 StartExecutionParams executeParams = new StartExecutionParams();
399 executeParams.setWorkflowId(workflowId);
400 executeParams.setDeploymentId(deploymentId);
401 executeParams.setParameters(workflowParams);
403 Execution execution = null;
404 String executionId = null;
405 String command = "start";
406 Exception savedException = null;
409 StartExecution executionRequest = cloudify.executions().start(executeParams);
410 logger.debug(executionRequest.toString());
411 execution = executeAndRecordCloudifyRequest (executionRequest);
412 executionId = execution.getId();
414 if (!pollForCompletion) {
415 // Client did not request polling, so just return the Execution object
419 // Enter polling loop
420 boolean timedOut = false;
421 int pollTimeout = timeout;
423 String status = execution.getStatus();
425 // Create a reusable cloudify query request
426 GetExecution queryExecution = cloudify.executions().byId(executionId);
429 while (!timedOut && !(status.equals(TERMINATED) || status.equals("failed") || status.equals(CANCELLED)))
431 // workflow is still running; check for timeout
432 if (pollTimeout <= 0) {
433 logger.debug("workflow {} timed out on deployment {}", execution.getWorkflowId(),
434 execution.getDeploymentId());
439 sleep(pollInterval * 1000L);
441 pollTimeout -= pollInterval;
442 logger.debug("pollTimeout remaining: " + pollTimeout);
444 execution = queryExecution.execute();
445 status = execution.getStatus();
448 // Broke the loop. Check again for a terminal state
449 if (status.equals(TERMINATED)){
451 logger.debug("Workflow '{}' completed successfully on deployment '{}'", workflowId, deploymentId);
454 else if (status.equals("failed")){
455 // Workflow failed. Log it and return the execution object (don't throw exception here)
456 logger.error("{} Cloudify workflow failure: {} {} Execute Workflow: Failed: {}",
457 MessageEnum.RA_CREATE_STACK_ERR, execution.getError(),
458 ErrorCode.BusinessProcesssError.getValue(), execution.getError());
461 else if (status.equals(CANCELLED)){
462 // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the execution object (don't throw exception here)
463 logger.error("{} Cloudify workflow cancelled. Deployment is in an indeterminate state {} {} {}",
464 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(),
465 "Execute Workflow cancelled: ", workflowId);
469 // Can only get here after a timeout
470 logger.error("{} Cloudify workflow timeout {} Execute Workflow: Timed Out", MessageEnum.RA_CREATE_STACK_ERR,
471 ErrorCode.BusinessProcesssError.getValue());
474 catch (CloudifyConnectException ce) {
475 logger.error("{} {} Execute Workflow ({} {}): Cloudify connection failure {} ", MessageEnum.RA_CREATE_STACK_ERR,
476 ErrorCode.BusinessProcesssError.getValue(), command, ce);
479 catch (CloudifyResponseException re) {
480 logger.error("{} {} Execute Workflow ({}): Cloudify response error {} ", MessageEnum.RA_CREATE_STACK_ERR,
481 ErrorCode.BusinessProcesssError.getValue(), command, re.getMessage(), re);
484 catch (RuntimeException e) {
486 logger.error("{} {} Execute Workflow ({}): Internal error {}", MessageEnum.RA_CREATE_STACK_ERR,
487 ErrorCode.BusinessProcesssError.getValue(), command, e.getMessage(), e);
491 // Get to this point ONLY on an error or timeout
492 // The cloudify execution is still running (we've not received a terminal status),
493 // so try to Cancel it.
494 CancelExecutionParams cancelParams = new CancelExecutionParams();
495 cancelParams.setAction("cancel");
496 // TODO: Use force_cancel?
498 Execution cancelExecution = null;
501 CancelExecution cancelRequest = cloudify.executions().cancel(executionId, cancelParams);
502 logger.debug(cancelRequest.toString());
503 cancelExecution = cancelRequest.execute();
505 // Enter polling loop
506 boolean timedOut = false;
507 int cancelTimeout = timeout; // TODO: For now, just use same timeout
509 String status = cancelExecution.getStatus();
511 // Poll for completion. Create a reusable cloudify query request
512 GetExecution queryExecution = cloudify.executions().byId(executionId);
514 while (!timedOut && !status.equals(CANCELLED))
516 // workflow is still running; check for timeout
517 if (cancelTimeout <= 0) {
518 logger.debug("Cancel timeout for workflow {} on deployment {}", workflowId, deploymentId);
523 sleep(pollInterval * 1000L);
525 cancelTimeout -= pollInterval;
526 logger.debug("pollTimeout remaining: {}", cancelTimeout);
528 execution = queryExecution.execute();
529 status = execution.getStatus();
532 // Broke the loop. Check again for a terminal state
533 if (status.equals(CANCELLED)){
534 // Finished cancelling. Return the original exception
535 logger.debug("Cancel workflow {} completed on deployment {}", workflowId, deploymentId);
536 throw new MsoCloudifyException (-1, "", "", savedException);
539 // Can only get here after a timeout
540 logger.debug("Cancel workflow {} timeout out on deployment {}", workflowId, deploymentId);
541 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
542 exception.setPendingWorkflow(true);
546 catch (Exception e) {
547 // Catch-all. Log the message and throw the original exception
548 logger.debug("Cancel workflow {} failed for deployment : {} {}", workflowId, deploymentId, e);
549 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
550 exception.setPendingWorkflow(true);
558 * Query for a Cloudify Deployment (by Name). This call will always return a
559 * DeploymentInfo object. If the deployment does not exist, an "empty" DeploymentInfo will be
560 * returned - containing only the deployment ID and a special status of NOTFOUND.
562 * @param tenantId The Openstack ID of the tenant in which to query
563 * @param cloudSiteId The cloud identifier (may be a region) in which to query
564 * @return A StackInfo object
565 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
567 public DeploymentInfo queryDeployment (String cloudSiteId, String tenantId, String deploymentId)
570 logger.debug ("Query Cloudify Deployment: {} in tenant {}", deploymentId, tenantId);
572 // Obtain the cloud site information where we will create the stack
573 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
574 if (!cloudSite.isPresent()) {
575 throw new MsoCloudSiteNotFound (cloudSiteId);
578 Cloudify cloudify = getCloudifyClient (cloudSite.get());
580 // Build and send the Cloudify request
581 Deployment deployment = new Deployment();
582 DeploymentOutputs outputs = null;
584 GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
585 logger.debug(queryDeployment.toString());
587 // deployment = queryDeployment.execute();
588 deployment = executeAndRecordCloudifyRequest(queryDeployment);
590 outputs = getDeploymentOutputs (cloudify, deploymentId);
592 // Next look for the latest execution
593 ListExecutions listExecutions = cloudify.executions().listFiltered ("deployment_id=" + deploymentId, "-created_at");
594 Executions executions = listExecutions.execute();
596 // If no executions, does this give NOT_FOUND or empty set?
597 if (executions.getItems().isEmpty()) {
598 return new DeploymentInfoBuilder()
599 .withId(deployment.getId())
600 .withDeploymentInputs(deployment.getInputs())
604 return new DeploymentInfoBuilder()
605 .withId(deployment.getId())
606 .withDeploymentInputs(deployment.getInputs())
607 .withDeploymentOutputs(outputs.getOutputs())
608 .fromExecution(executions.getItems().get(0))
612 catch (CloudifyConnectException ce) {
613 // Couldn't connect to Cloudify
614 logger.error("{} QueryDeployment: Cloudify connection failure: {} ", MessageEnum.RA_CREATE_STACK_ERR,
615 ErrorCode.BusinessProcesssError.getValue(), ce);
616 throw new MsoIOException (ce.getMessage(), ce);
618 catch (CloudifyResponseException re) {
619 if (re.getStatus () == 404) {
620 // Got a NOT FOUND error. React differently based on deployment vs. execution
621 if (deployment != null) {
622 // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
623 return new DeploymentInfoBuilder()
624 .withId(deployment.getId())
625 .withDeploymentInputs(deployment.getInputs())
626 .withDeploymentOutputs(outputs.getOutputs())
629 // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
630 return new DeploymentInfoBuilder().withId(deploymentId).build();
633 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
635 catch (Exception e) {
637 throw new MsoAdapterException (e.getMessage(), e);
643 * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be
644 * considered a successful deletion. The return value is a DeploymentInfo object which
645 * contains the last deployment status.
647 * There is no rollback from a successful deletion. A deletion failure will
648 * also result in an undefined deployment state - the components may or may not have been
649 * all or partially deleted, so the resulting deployment must be considered invalid.
651 * @param tenantId The Openstack ID of the tenant in which to perform the delete
652 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
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 DeploymentInfoBuilder()
688 .withId(deploymentId)
689 .withStatus(DeploymentStatus.NOTFOUND)
692 // Convert the CloudifyResponseException to an MsoOpenstackException
693 logger.debug("ERROR STATUS = {}, \n {}\n {}\n {}", e.getStatus(), e.getMessage(),
694 e.getLocalizedMessage(), e);
695 MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
696 me.setCategory (MsoExceptionCategory.INTERNAL);
699 } catch (CloudifyConnectException e) {
700 // Error connecting to Cloudify instance. Convert to an MsoException
701 throw cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
702 } catch (RuntimeException e) {
704 throw runtimeExceptionToMsoException (e, DELETE_DEPLOYMENT);
708 * Query the outputs before deleting so they can be returned as well
710 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
713 * Next execute the "uninstall" workflow.
714 * Note - this assumes there are no additional parameters required for the workflow.
716 // TODO: No deletePollInterval that I'm aware of. Use the create interval
717 int deletePollInterval = Integer.parseInt(this.environment.getProperty (deletePollIntervalProp, deletePollIntervalDefault));
718 int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
720 Execution uninstallWorkflow = null;
723 uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, true, pollTimeout, deletePollInterval);
725 if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
726 // Successful uninstall.
727 logger.debug("Uninstall successful for deployment {}", deploymentId);
730 // The uninstall workflow completed with an error. Must fail the request, but will
731 // leave the deployment in an indeterminate state, as cloud resources may still exist.
732 MsoCloudifyException me = new MsoCloudifyException (0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
733 me.addContext (DELETE_DEPLOYMENT);
738 catch (MsoException me) {
739 // Uninstall workflow has failed.
740 // Must fail the deletion... may leave the deployment in an inconclusive state
741 me.addContext (DELETE_DEPLOYMENT);
746 // At this point, the deployment has been successfully uninstalled.
747 // Next step is to delete the deployment itself
749 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
750 logger.debug(deleteRequest.toString());
752 // The delete request returns the deleted deployment
753 deployment = deleteRequest.execute();
756 catch (CloudifyConnectException ce) {
757 // Failed to delete. Must fail the request, but will leave the (uninstalled)
758 // deployment in Cloudify DB.
759 MsoCloudifyException me = new MsoCloudifyException (0, "Deployment Delete Failed", ce.getMessage(), ce);
760 me.addContext (DELETE_DEPLOYMENT);
764 catch (CloudifyResponseException re) {
765 // Failed to delete. Must fail the request, but will leave the (uninstalled)
766 // deployment in the Cloudify DB.
767 MsoCloudifyException me = new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getMessage(), re);
768 me.addContext (DELETE_DEPLOYMENT);
772 catch (Exception e) {
774 MsoAdapterException ae = new MsoAdapterException (e.getMessage(), e);
775 ae.addContext (DELETE_DEPLOYMENT);
780 // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
781 return new DeploymentInfoBuilder()
782 .withId(deployment.getId())
783 .withDeploymentInputs(deployment.getInputs())
784 .withDeploymentOutputs(outputs.getOutputs())
785 .fromExecution(uninstallWorkflow)
791 * Check if a blueprint is available for use at a targeted cloud site.
792 * This requires checking the Cloudify Manager which is servicing that
793 * cloud site to see if the specified blueprint has been loaded.
795 * @param cloudSiteId The cloud site where the blueprint is needed
796 * @param blueprintId The ID for the blueprint in Cloudify
798 public boolean isBlueprintLoaded (String cloudSiteId, String blueprintId)
801 // Obtain the cloud site information where we will load the blueprint
802 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
803 if (!cloudSite.isPresent()) {
804 throw new MsoCloudSiteNotFound (cloudSiteId);
807 Cloudify cloudify = getCloudifyClient (cloudSite.get());
809 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
811 Blueprint bp = getRequest.execute();
812 logger.debug("Blueprint exists: {}", bp.getId());
815 catch (CloudifyResponseException ce) {
816 if (ce.getStatus() == 404) {
821 } catch (Exception e) {
827 * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site.
828 * The blueprint currently must be structured as a single directory with all
829 * of the required files. One of those files is designated the "main file"
830 * for the blueprint. Files are provided as byte arrays, though expect only
831 * text files will be distributed from ASDC and stored by MSO.
833 * Cloudify requires a single root directory in its blueprint zip files.
834 * The requested blueprint ID will also be used as the directory.
835 * All of the files will be added to this directory in the zip file.
837 public void uploadBlueprint (String cloudSiteId,
840 Map<String,byte[]> blueprintFiles,
841 boolean failIfExists)
844 // Obtain the cloud site information where we will load the blueprint
845 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
846 if (!cloudSite.isPresent()) {
847 throw new MsoCloudSiteNotFound (cloudSiteId);
850 Cloudify cloudify = getCloudifyClient (cloudSite.get());
852 boolean blueprintUploaded = uploadBlueprint (cloudify, blueprintId, mainFileName, blueprintFiles);
854 if (!blueprintUploaded && failIfExists) {
855 throw new MsoAdapterException ("Blueprint already exists");
860 * Common method to load a blueprint. May be called from
862 protected boolean uploadBlueprint (Cloudify cloudify, String blueprintId, String mainFileName, Map<String,byte[]> blueprintFiles)
865 // Check if it already exists. If so, return false.
866 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
868 Blueprint bp = getRequest.execute();
869 logger.debug("Blueprint {} already exists.", bp.getId());
872 catch (CloudifyResponseException ce) {
873 if (ce.getStatus() == 404) {
874 // This is the expected result.
875 logger.debug("Verified that Blueprint doesn't exist yet");
879 } catch (Exception e) {
883 // Create a blueprint ZIP file in memory
884 ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
885 ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
888 // Put the root directory
889 String rootDir = blueprintId + ((blueprintId.endsWith("/") ? "" : "/"));
890 zipOut.putNextEntry(new ZipEntry (rootDir));
893 for (String fileName : blueprintFiles.keySet()) {
894 ZipEntry ze = new ZipEntry (rootDir + fileName);
895 zipOut.putNextEntry (ze);
896 zipOut.write (blueprintFiles.get(fileName));
901 catch (IOException e) {
902 // Since we're writing to a byte array, this should never happen
904 logger.debug("Blueprint zip file size: {}", zipBuffer.size());
906 // Ready to upload the blueprint zip
908 try (InputStream blueprintStream = new ByteArrayInputStream (zipBuffer.toByteArray())) {
909 UploadBlueprint uploadRequest = cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
910 Blueprint blueprint = uploadRequest.execute();
911 logger.debug("Successfully uploaded blueprint {}", blueprint.getId());
913 catch (CloudifyResponseException | CloudifyConnectException e) {
914 throw cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
916 catch (RuntimeException e) {
918 throw runtimeExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
919 } catch (IOException e) {
920 // for try-with-resources
921 throw ioExceptionToMsoException(e, "UPLOAD_BLUEPRINT");
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 = cloudConfig.getCloudifyManager(cloudSite.getCloudifyId());
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(), CryptoUtils.decryptCloudConfigPassword(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);
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)
998 for (Map.Entry<String, Object> entry : otherStackOutputs.entrySet()) {
999 String key = entry.getKey();
1000 Object value = entry.getValue();
1002 if (value 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 inputs.put(key, this.convertNode((JsonNode) value));
1007 } catch (Exception e) {
1008 logger.debug("WARNING: unable to convert JsonNode output value for {}", key);
1009 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1011 } else if (value instanceof java.util.LinkedHashMap) {
1012 logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
1014 inputs.put(key, JSON_MAPPER.writeValueAsString(value));
1015 } catch (Exception e) {
1016 logger.debug("WARNING: unable to convert LinkedHashMap output value for {}", key);
1019 // just try to cast it - could be an integer or some such
1021 inputs.put(key, (String) value);
1022 } catch (Exception e) {
1023 logger.debug("WARNING: unable to convert output value for {}", key);
1024 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1032 * Normalize an input value to an Object, based on the target parameter type.
1033 * If the type is not recognized, it will just be returned unchanged (as a string).
1035 public Object convertInputValue (Object inputValue, HeatTemplateParam templateParam)
1037 String type = templateParam.getParamType();
1038 logger.debug("Parameter: {} is of type {}", templateParam.getParamName(), type);
1040 if (type.equalsIgnoreCase("number")) {
1042 return Integer.valueOf(inputValue.toString());
1044 catch (Exception e) {
1045 logger.debug("Unable to convert {} to an integer!", inputValue);
1048 } else if (type.equalsIgnoreCase("json")) {
1050 if (inputValue instanceof String) {
1051 return JSON_MAPPER.readTree(inputValue.toString());
1053 //will already marshal to json without intervention
1056 catch (Exception e) {
1057 logger.debug("Unable to convert {} to a JsonNode!", inputValue);
1060 } else if (type.equalsIgnoreCase("boolean")) {
1061 return new Boolean(inputValue.toString());
1064 // Nothing else matched. Return the original string
1069 private String convertNode(final JsonNode node) {
1071 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
1072 return JSON_MAPPER.writeValueAsString(obj);
1073 } catch (JsonParseException jpe) {
1074 logger.debug("Error converting json to string {}", jpe);
1075 } catch (Exception e) {
1076 logger.debug("Error converting json to string {}", e);
1078 return "[Error converting json to string]";
1083 * Method to execute a Cloudify command and track its execution time.
1084 * For the metrics log, a category of "Cloudify" is used along with a
1085 * sub-category that identifies the specific call (using the real
1086 * cloudify-client classname of the CloudifyRequest<T> parameter).
1090 protected <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request) {
1093 if (request.getClass ().getEnclosingClass () != null) {
1094 requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
1095 + request.getClass ().getSimpleName ();
1097 requestType = request.getClass ().getSimpleName ();
1100 int retryDelay = poConfig.getRetryDelay();
1101 int retryCount = poConfig.getRetryCount();
1102 String retryCodes = poConfig.getRetryCodes();
1104 // Run the actual command. All exceptions will be propagated
1108 return request.execute ();
1110 catch (CloudifyResponseException e) {
1111 boolean retry = false;
1112 if (retryCodes != null ) {
1113 int code = e.getStatus();
1114 logger.debug("Config values RetryDelay: {} RetryCount:{} RetryCodes:{} ResponseCode:{}", retryDelay,
1115 retryCount, retryCodes, code);
1116 for (String rCode : retryCodes.split (",")) {
1118 if (retryCount > 0 && code == Integer.parseInt (rCode))
1123 "CloudifyResponseException ResponseCode:{} request:{} Retry indicated. Attempts remaining:{}",
1124 code, requestType, retryCount);
1127 } catch (NumberFormatException e1) {
1128 logger.error("{} No retries. Exception in parsing retry code in config:{} {}",
1129 MessageEnum.RA_CONFIG_EXC, rCode, ErrorCode.SchemaError.getValue());
1136 sleep(retryDelay * 1000L);
1139 throw e; // exceeded retryCount or code is not retryable
1141 catch (CloudifyConnectException e) {
1142 // Connection to Cloudify failed
1146 logger.debug(" request: {} Retry indicated. Attempts remaining:{}", requestType, retryCount);
1147 sleep(retryDelay * 1000L);
1156 * Convert an Exception on a Cloudify call to an MsoCloudifyException.
1157 * This method supports CloudifyResponseException and CloudifyConnectException.
1159 protected MsoException cloudifyExceptionToMsoException (CloudifyBaseException e, String context) {
1160 MsoException me = null;
1162 if (e instanceof CloudifyResponseException) {
1163 CloudifyResponseException re = (CloudifyResponseException) e;
1166 // Failed Cloudify calls return an error entity body.
1167 CloudifyError error = re.getResponse ().getErrorEntity (CloudifyError.class);
1168 logger.error("{} {} {} Exception - Cloudify Error on {}: {}", MessageEnum.RA_CONNECTION_EXCEPTION,
1169 CLOUDIFY, ErrorCode.DataError.getValue(), context, error.getErrorCode());
1170 String fullError = error.getErrorCode() + ": " + error.getMessage();
1171 logger.debug(fullError);
1172 me = new MsoCloudifyException (re.getStatus(),
1175 } catch (Exception e2) {
1176 // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
1177 logger.error("{} {} {} Exception - HTTP Error on {}: {}, {} ", MessageEnum.RA_CONNECTION_EXCEPTION,
1178 CLOUDIFY, ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
1179 me = new MsoCloudifyException (re.getStatus (), re.getMessage (), "");
1182 // Add the context of the error
1183 me.addContext (context);
1185 // Generate an alarm for 5XX and higher errors.
1186 if (re.getStatus () >= 500) {
1189 } else if (e instanceof CloudifyConnectException) {
1190 CloudifyConnectException ce = (CloudifyConnectException) e;
1192 me = new MsoIOException (ce.getMessage ());
1193 me.addContext (context);
1195 // Generate an alarm for all connection errors.
1197 logger.error("{} {} {} Cloudify connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY,
1198 ErrorCode.DataError.getValue(), context, e);
1206 /*******************************************************************************
1208 * Methods (and associated utilities) to implement the VduPlugin interface
1210 *******************************************************************************/
1213 * VduPlugin interface for instantiate function.
1215 * This one is a bit more complex, in that it will first upload the blueprint if needed,
1216 * then create the Cloudify deployment and execute the install workflow.
1218 * This implementation also merges any parameters defined in the ENV file with the other
1219 * other input parameters for any undefined parameters).
1220 * The basic MsoCloudifyUtils separates blueprint management from deploument actions,
1221 * but the VduPlugin does not declare blueprint management operations.
1224 public VduInstance instantiateVdu (
1225 CloudInfo cloudInfo,
1226 String instanceName,
1227 Map<String,Object> inputs,
1228 VduModelInfo vduModel,
1229 boolean rollbackOnFailure)
1232 String cloudSiteId = cloudInfo.getCloudSiteId();
1233 String tenantId = cloudInfo.getTenantId();
1235 // Translate the VDU ModelInformation structure to that which is needed for
1236 // creating and uploading a blueprint. Use the model customization UUID as
1237 // the blueprint identifier.
1239 String blueprintId = vduModel.getModelCustomizationUUID();
1243 if (! isBlueprintLoaded (cloudSiteId, blueprintId)) {
1244 logger.debug("Blueprint {} is not loaded. Will upload it now.", blueprintId);
1246 // Prepare the blueprint inputs. Need the set of blueprint templates and files,
1247 // plus the main blueprint name.
1248 Map<String,byte[]> blueprintFiles = new HashMap<>();
1249 String mainTemplate = "";
1251 // Add all of the blueprint artifacts from the VDU model
1252 List<VduArtifact> vduArtifacts = vduModel.getArtifacts();
1253 for (VduArtifact vduArtifact: vduArtifacts)
1255 // Add all artifacts to the blueprint, with one exception.
1256 // ENVIRONMENT files will be processed later as additional parameters.
1258 ArtifactType artifactType = vduArtifact.getType();
1259 if (artifactType != ArtifactType.ENVIRONMENT) {
1260 blueprintFiles.put(vduArtifact.getName(), vduArtifact.getContent());
1262 if (artifactType == ArtifactType.MAIN_TEMPLATE) {
1263 mainTemplate = vduArtifact.getName();
1268 // Upload the blueprint package
1269 uploadBlueprint(cloudSiteId, blueprintId, mainTemplate, blueprintFiles, false);
1272 catch (Exception e) {
1273 throw new VduException ("CloudifyUtils (instantiateVDU): blueprint Exception", e);
1277 // Next, create and install a new deployment based on the blueprint.
1278 // For Cloudify, the deploymentId is specified by the client. Just use the instance name
1282 // Query the Cloudify Deployment object and populate a VduInstance
1283 DeploymentInfo deployment = createAndInstallDeployment (cloudSiteId,
1288 true, // (poll for completion)
1289 vduModel.getTimeoutMinutes(),
1292 return deploymentInfoToVduInstance(deployment);
1294 catch (Exception e) {
1295 throw new VduException ("CloudifyUtils (instantiateVDU): Create-and-install-deployment Exception", e);
1301 * VduPlugin interface for query function.
1304 public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
1307 String cloudSiteId = cloudInfo.getCloudSiteId();
1308 String tenantId = cloudInfo.getTenantId();
1311 // Query the Cloudify Deployment object and populate a VduInstance
1312 DeploymentInfo deployment = queryDeployment (cloudSiteId, tenantId, instanceId);
1314 return deploymentInfoToVduInstance(deployment);
1316 catch (Exception e) {
1317 throw new VduException ("Query VDU Exception", e);
1323 * VduPlugin interface for delete function.
1326 public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
1329 String cloudSiteId = cloudInfo.getCloudSiteId();
1330 String tenantId = cloudInfo.getTenantId();
1333 // Uninstall and delete the Cloudify Deployment
1334 DeploymentInfo deployment = uninstallAndDeleteDeployment (cloudSiteId, tenantId, instanceId, timeoutMinutes);
1336 // Populate a VduInstance based on the deleted Cloudify Deployment object
1337 return deploymentInfoToVduInstance(deployment);
1339 catch (Exception e) {
1340 throw new VduException ("Delete VDU Exception", e);
1346 * VduPlugin interface for update function.
1348 * Update is currently not supported in the MsoCloudifyUtils implementation.
1349 * Just return a VduException.
1353 public VduInstance updateVdu (
1354 CloudInfo cloudInfo,
1356 Map<String,Object> inputs,
1357 VduModelInfo vduModel,
1358 boolean rollbackOnFailure)
1361 throw new VduException ("CloudifyUtils: updateVDU interface not supported");
1366 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1368 protected VduInstance deploymentInfoToVduInstance (DeploymentInfo deployment)
1370 VduInstance vduInstance = new VduInstance();
1372 // only one ID in Cloudify, use for both VDU name and ID
1373 vduInstance.setVduInstanceId(deployment.getId());
1374 vduInstance.setVduInstanceName(deployment.getId());
1376 // Copy inputs and outputs
1377 vduInstance.setInputs(deployment.getInputs());
1378 vduInstance.setOutputs(deployment.getOutputs());
1380 // Translate the status elements
1381 vduInstance.setStatus(deploymentStatusToVduStatus (deployment));
1386 protected VduStatus deploymentStatusToVduStatus (DeploymentInfo deployment)
1388 VduStatus vduStatus = new VduStatus();
1390 // Determine the status based on last action & status
1391 // DeploymentInfo object should be enhanced to report a better status internally.
1392 DeploymentStatus status = deployment.getStatus();
1394 if (status == null) {
1395 vduStatus.setState(VduStateType.UNKNOWN);
1397 else if (status == DeploymentStatus.NOTFOUND) {
1398 vduStatus.setState(VduStateType.NOTFOUND);
1400 else if (status == DeploymentStatus.INSTALLED) {
1401 vduStatus.setState(VduStateType.INSTANTIATED);
1403 else if (status == DeploymentStatus.CREATED) {
1404 // Deployment exists but is not installed. This shouldn't really happen,
1405 // since create + install or uninstall + delete are always done together.
1406 // But account for it anyway, assuming the operation is still in progress.
1407 String lastAction = deployment.getLastAction();
1408 if (lastAction == null)
1409 vduStatus.setState(VduStateType.INSTANTIATING);
1411 vduStatus.setState(VduStateType.DELETING);
1413 else if (status == DeploymentStatus.FAILED) {
1414 vduStatus.setState(VduStateType.FAILED);
1416 vduStatus.setState(VduStateType.UNKNOWN);
1419 vduStatus.setErrorMessage(deployment.getErrorMessage());
1420 vduStatus.setLastAction(new PluginAction(deployment.getLastAction(), deployment.getActionStatus(), deployment.getErrorMessage()));
1426 * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in.
1427 * Base the values on the CloudSite definition.
1429 protected OpenstackConfig getOpenstackConfig (CloudSite cloudSite, String tenantId) {
1430 OpenstackConfig openstackConfig = new OpenstackConfig();
1431 openstackConfig.setRegion (cloudSite.getRegionId());
1432 openstackConfig.setAuthUrl (cloudSite.getIdentityService().getIdentityUrl());
1433 openstackConfig.setUsername (cloudSite.getIdentityService().getMsoId());
1434 openstackConfig.setPassword (CryptoUtils.decryptCloudConfigPassword(cloudSite.getIdentityService().getMsoPass()));
1435 openstackConfig.setTenantName (tenantId);
1436 return openstackConfig;
1440 * Return an Azure object as expected by Cloudify Azure Plug-in.
1441 * Base the values on the CloudSite definition.
1443 protected AzureConfig getAzureConfig (CloudSite cloudSite, String tenantId) {
1444 AzureConfig azureConfig = new AzureConfig();
1445 // TODO: Use adminTenant for now, instead of adding another element
1446 azureConfig.setSubscriptionId (cloudSite.getIdentityService().getAdminTenant());
1447 azureConfig.setTenantId (tenantId);
1448 azureConfig.setClientId (cloudSite.getIdentityService().getMsoId());
1449 azureConfig.setClientSecret (cloudSite.getIdentityService().getMsoPass());
1453 private void sleep(long time) {
1456 } catch (InterruptedException e) {
1457 logger.debug("Thread interrupted while sleeping!", e);
1458 Thread.currentThread().interrupt();