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.logging.filter.base.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 = "Cloudify";
108 private static final String CREATE_DEPLOYMENT = "CreateDeployment";
109 private static final String DELETE_DEPLOYMENT = "DeleteDeployment";
110 private static final String TERMINATED = "terminated";
111 private static final String CANCELLED = "cancelled";
112 private static final String UNINSTALL = "uninstall";
113 private static final String UPLOAD_BLUEPRINT = "UPLOAD_BLUEPRINT";
115 // Fetch cloud configuration each time (may be cached in CloudConfig class)
117 protected CloudConfig cloudConfig;
120 private Environment environment;
123 private PoConfig poConfig;
125 private static final Logger logger = LoggerFactory.getLogger(MsoCloudifyUtils.class);
127 // Properties names and variables (with default values)
128 protected String createPollIntervalProp = "org.onap.so.adapters.po.pollInterval";
129 private String deletePollIntervalProp = "org.onap.so.adapters.po.pollInterval";
131 protected String createPollIntervalDefault = "15";
132 private String deletePollIntervalDefault = "15";
134 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
137 * Create a new Deployment from a specified blueprint, and install it in the specified cloud location and tenant.
138 * The blueprint identifier and parameter map are passed in as arguments, along with the cloud access credentials.
139 * The blueprint should have been previously uploaded to Cloudify.
141 * It is expected that parameters have been validated and contain at minimum the required parameters for the given
142 * template with no extra (undefined) parameters..
144 * The deployment ID supplied by the caller must be unique in the scope of the Cloudify tenant (not the Openstack
145 * tenant). However, it should also be globally unique, as it will be the identifier for the resource going forward
146 * in Inventory. This latter is managed by the higher levels invoking this function.
148 * This function executes the "install" workflow on the newly created workflow. Cloudify will be polled for
149 * completion unless the client requests otherwise.
151 * An error will be thrown if the requested Deployment already exists in the specified Cloudify instance.
153 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
154 * @param tenantId The Openstack ID of the tenant in which to create the Stack
155 * @param deploymentId The identifier (name) of the deployment to create
156 * @param blueprintId The blueprint from which to create the deployment.
157 * @param inputs A map of key/value inputs
158 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
159 * @param timeoutMinutes Timeout after which the "install" will be cancelled
160 * @param backout Flag to delete deployment on install Failure - defaulted to True
161 * @return A DeploymentInfo object
162 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
163 * @throws MsoIOException Thrown on Cloudify connection errors.
166 public DeploymentInfo createAndInstallDeployment(String cloudSiteId, String tenantId, String deploymentId,
167 String blueprintId, Map<String, ? extends Object> inputs, boolean pollForCompletion, int timeoutMinutes,
168 boolean backout) throws MsoException {
169 // Obtain the cloud site information where we will create the stack
170 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
171 if (!cloudSite.isPresent()) {
172 throw new MsoCloudSiteNotFound(cloudSiteId);
175 Cloudify cloudify = getCloudifyClient(cloudSite.get());
177 logger.debug("Ready to Create Deployment ({}) with input params: {}", deploymentId, inputs);
179 // Build up the inputs, including:
180 // - from provided "environment" file
181 // - passed in by caller
182 // - special input for cloud-specific Credentials
183 Map<String, Object> expandedInputs = new HashMap<>(inputs);
185 String platform = cloudSite.get().getPlatform();
186 if (platform == null || platform.isEmpty() || ("OPENSTACK").equalsIgnoreCase(platform)) {
187 // Create the Cloudify OpenstackConfig with the credentials
188 OpenstackConfig openstackConfig = getOpenstackConfig(cloudSite.get(), tenantId);
189 expandedInputs.put("openstack_config", openstackConfig);
190 } else if (("AZURE").equalsIgnoreCase(platform)) {
191 // Create Cloudify AzureConfig with the credentials
192 AzureConfig azureConfig = getAzureConfig(cloudSite.get(), tenantId);
193 expandedInputs.put("azure_config", azureConfig);
196 // Build up the parameters to create a new deployment
197 CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
198 deploymentParams.setBlueprintId(blueprintId);
199 deploymentParams.setInputs(expandedInputs);
201 Deployment deployment = null;
203 CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
204 logger.debug(createDeploymentRequest.toString());
206 deployment = executeAndRecordCloudifyRequest(createDeploymentRequest);
207 } catch (CloudifyResponseException e) {
208 // Since this came on the 'Create Deployment' command, nothing was changed
209 // in the cloud. Return the error as an exception.
210 if (e.getStatus() == 409) {
211 // Deployment already exists. Return a specific error for this case
212 MsoException me = new MsoDeploymentAlreadyExists(deploymentId, cloudSiteId);
213 me.addContext(CREATE_DEPLOYMENT);
216 // Convert the CloudifyResponseException to an MsoException
217 logger.debug("ERROR STATUS = {},\n{}\n{}", e.getStatus(), e.getMessage(), e.getLocalizedMessage());
218 MsoException me = cloudifyExceptionToMsoException(e, CREATE_DEPLOYMENT);
219 me.setCategory(MsoExceptionCategory.OPENSTACK);
222 } catch (CloudifyConnectException e) {
223 // Error connecting to Cloudify instance. Convert to an MsoException
224 throw cloudifyExceptionToMsoException(e, CREATE_DEPLOYMENT);
225 } catch (RuntimeException e) {
227 throw runtimeExceptionToMsoException(e, CREATE_DEPLOYMENT);
231 * It can take some time for Cloudify to be ready to execute a workflow on the deployment. Sleep 30 seconds
232 * based on observation of behavior in a Cloudify VM instance (delay due to "create_deployment_environment").
237 * Next execute the "install" workflow. Note - this assumes there are no additional parameters required for the
240 int createPollInterval =
241 Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
242 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
244 Execution installWorkflow = null;
247 installWorkflow = executeWorkflow(cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout,
250 if (installWorkflow.getStatus().equals(TERMINATED)) {
252 // Create and return a DeploymentInfo structure. Include the Runtime outputs
253 return new DeploymentInfoBuilder().withId(deployment.getId())
254 .withDeploymentInputs(deployment.getInputs())
255 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get())
256 .fromExecution(installWorkflow).build();
258 // The workflow completed with errors. Must try to back it out.
260 logger.warn("{} Deployment installation failed, backout deletion suppressed {} {}",
261 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue(),
262 "Exception in Deployment Installation, backout suppressed");
264 // Poll on delete if we rollback - use same values for now
265 int deletePollInterval = createPollInterval;
266 int deletePollTimeout = pollTimeout;
269 // Run the uninstall to undo the install
270 Execution uninstallWorkflow = executeWorkflow(cloudify, deploymentId, UNINSTALL, null,
271 pollForCompletion, deletePollTimeout, deletePollInterval);
273 if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
274 // The uninstall completed. Delete the deployment itself
275 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
276 executeAndRecordCloudifyRequest(deleteRequest);
278 // Didn't uninstall successfully. Log this error
279 logger.error("{} Create Deployment: Cloudify error rolling back deployment install: {} {}",
280 MessageEnum.RA_CREATE_STACK_ERR, installWorkflow.getError(),
281 ErrorCode.BusinessProcessError.getValue());
283 } catch (Exception e) {
284 // Catch-all for backout errors trying to uninstall/delete
285 // Log this error, and return the original exception
286 logger.error("{} Create Stack: Nested exception rolling back deployment install: {}",
287 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue(), e);
291 MsoCloudifyException me =
292 new MsoCloudifyException(0, "Workflow Execution Failed", installWorkflow.getError());
293 me.addContext(CREATE_DEPLOYMENT);
297 } catch (MsoException me) {
298 // Install failed. Unless requested otherwise, back out the deployment
301 logger.warn("{} Deployment installation failed, backout deletion suppressed {}",
302 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue());
304 // Poll on delete if we rollback - use same values for now
305 int deletePollInterval = createPollInterval;
306 int deletePollTimeout = pollTimeout;
309 // Run the uninstall to undo the install.
310 // Always try to run it, as it should be idempotent
311 executeWorkflow(cloudify, deploymentId, UNINSTALL, null, pollForCompletion, deletePollTimeout,
314 // Delete the deployment itself
315 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
316 executeAndRecordCloudifyRequest(deleteRequest);
317 } catch (Exception e) {
318 // Catch-all for backout errors trying to uninstall/delete
319 // Log this error, and return the original exception
320 logger.error("{} Create Stack: Nested exception rolling back deployment install: {} ",
321 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue(), e);
325 // Propagate the original exception from Stack Query.
326 me.addContext(CREATE_DEPLOYMENT);
334 * Get the runtime Outputs of a deployment. Return the Map of tag/value outputs.
336 private Optional<Map<String, Object>> getDeploymentOutputs(Cloudify cloudify, String deploymentId)
337 throws MsoException {
338 // Build and send the Cloudify request
339 DeploymentOutputs deploymentOutputs;
341 GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
342 logger.debug(queryDeploymentOutputs.toString());
344 deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs);
345 if (deploymentOutputs != null) {
346 return Optional.ofNullable(deploymentOutputs.getOutputs());
348 return Optional.empty();
350 } catch (CloudifyConnectException ce) {
351 // Couldn't connect to Cloudify
352 logger.error("{} QueryDeploymentOutputs: Cloudify connection failure: {} ", MessageEnum.RA_CREATE_STACK_ERR,
353 ErrorCode.BusinessProcessError.getValue(), ce);
354 throw new MsoIOException(ce.getMessage(), ce);
355 } catch (CloudifyResponseException re) {
356 if (re.getStatus() == 404) {
358 return Optional.empty();
360 throw new MsoCloudifyException(re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
361 } catch (Exception e) {
363 throw new MsoAdapterException(e.getMessage(), e);
368 * Execute a workflow on a deployment. Handle polling for completion with timeout. Return the final Execution object
369 * with status. Throw an exception on Errors. Question - how does the client know whether rollback needs to be done?
371 private Execution executeWorkflow(Cloudify cloudify, String deploymentId, String workflowId,
372 Map<String, Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
373 throws MsoCloudifyException {
374 logger.debug("Executing '{}' workflow on deployment '{}'", workflowId, deploymentId);
376 StartExecutionParams executeParams = new StartExecutionParams();
377 executeParams.setWorkflowId(workflowId);
378 executeParams.setDeploymentId(deploymentId);
379 executeParams.setParameters(workflowParams);
381 Execution execution = null;
382 String executionId = null;
383 String command = "start";
384 Exception savedException = null;
387 StartExecution executionRequest = cloudify.executions().start(executeParams);
388 logger.debug(executionRequest.toString());
389 execution = executeAndRecordCloudifyRequest(executionRequest);
390 executionId = execution.getId();
392 if (!pollForCompletion) {
393 // Client did not request polling, so just return the Execution object
397 // Enter polling loop
398 boolean timedOut = false;
399 int pollTimeout = timeout;
401 String status = execution.getStatus();
403 // Create a reusable cloudify query request
404 GetExecution queryExecution = cloudify.executions().byId(executionId);
407 while (!timedOut && !(status.equals(TERMINATED) || ("failed").equals(status) || status.equals(CANCELLED))) {
408 // workflow is still running; check for timeout
409 if (pollTimeout <= 0) {
410 logger.debug("workflow {} timed out on deployment {}", execution.getWorkflowId(),
411 execution.getDeploymentId());
416 sleep(pollInterval * 1000L);
418 pollTimeout -= pollInterval;
419 logger.debug("pollTimeout remaining: " + pollTimeout);
421 execution = queryExecution.execute();
422 if (execution != null) {
423 status = execution.getStatus();
429 // Broke the loop. Check again for a terminal state
430 if (status.equals(TERMINATED)) {
432 logger.debug("Workflow '{}' completed successfully on deployment '{}'", workflowId, deploymentId);
434 } else if (("failed").equals(status)) {
435 // Workflow failed. Log it and return the execution object (don't throw exception here)
436 logger.error("{} Cloudify workflow failure: {} {} Execute Workflow: Failed: {}",
437 MessageEnum.RA_CREATE_STACK_ERR, execution.getError(),
438 ErrorCode.BusinessProcessError.getValue(), execution.getError());
440 } else if (status.equals(CANCELLED)) {
441 // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the
442 // execution object (don't throw exception here)
443 logger.error("{} Cloudify workflow cancelled. Deployment is in an indeterminate state {} {} {}",
444 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue(),
445 "Execute Workflow cancelled: ", workflowId);
448 // Can only get here after a timeout
449 logger.error("{} Cloudify workflow timeout {} Execute Workflow: Timed Out",
450 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue());
452 } catch (CloudifyConnectException ce) {
453 logger.error("{} {} Execute Workflow ({} {}): Cloudify connection failure {} ",
454 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcessError.getValue(), command, ce);
456 } catch (CloudifyResponseException re) {
457 logger.error("{} {} Execute Workflow ({}): Cloudify response error {} ", MessageEnum.RA_CREATE_STACK_ERR,
458 ErrorCode.BusinessProcessError.getValue(), command, re.getMessage(), re);
460 } catch (RuntimeException e) {
462 logger.error("{} {} Execute Workflow ({}): Internal error {}", MessageEnum.RA_CREATE_STACK_ERR,
463 ErrorCode.BusinessProcessError.getValue(), command, e.getMessage(), e);
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 = null;
486 if (cancelExecution != null) {
487 status = cancelExecution.getStatus();
489 // Poll for completion. Create a reusable cloudify query request
490 GetExecution queryExecution = cloudify.executions().byId(executionId);
492 while (!timedOut && !CANCELLED.equals(status)) {
493 // workflow is still running; check for timeout
494 if (cancelTimeout <= 0) {
495 logger.debug("Cancel timeout for workflow {} on deployment {}", workflowId, deploymentId);
500 sleep(pollInterval * 1000L);
502 cancelTimeout -= pollInterval;
503 logger.debug("pollTimeout remaining: {}", cancelTimeout);
505 execution = queryExecution.execute();
506 if (execution != null) {
507 status = execution.getStatus();
511 // Broke the loop. Check again for a terminal state
512 if (CANCELLED.equals(status)) {
513 // Finished cancelling. Return the original exception
514 logger.debug("Cancel workflow {} completed on deployment {}", workflowId, deploymentId);
515 throw new MsoCloudifyException(-1, "", "", savedException);
517 // Can only get here after a timeout
518 logger.debug("Cancel workflow {} timeout out on deployment {}", workflowId, deploymentId);
519 MsoCloudifyException exception = new MsoCloudifyException(-1, "", "", savedException);
520 exception.setPendingWorkflow(true);
523 } catch (Exception e) {
524 // Catch-all. Log the message and throw the original exception
525 logger.debug("Cancel workflow {} failed for deployment {} :", workflowId, deploymentId, e);
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 DeploymentInfo object. If the
536 * deployment does not exist, an "empty" DeploymentInfo will be returned - containing only the deployment ID and a
537 * 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 * @return A StackInfo object
542 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
544 public DeploymentInfo queryDeployment(String cloudSiteId, String tenantId, String deploymentId)
545 throws MsoException {
546 logger.debug("Query Cloudify Deployment: {} in tenant {}", deploymentId, tenantId);
548 // Obtain the cloud site information where we will create the stack
549 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
550 if (!cloudSite.isPresent()) {
551 throw new MsoCloudSiteNotFound(cloudSiteId);
554 Cloudify cloudify = getCloudifyClient(cloudSite.get());
556 // Build and send the Cloudify request
557 Deployment deployment = new Deployment();
559 GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
560 logger.debug(queryDeployment.toString());
561 deployment = executeAndRecordCloudifyRequest(queryDeployment);
563 // Next look for the latest execution
564 ListExecutions listExecutions =
565 cloudify.executions().listFiltered("deployment_id=" + deploymentId, "-created_at");
566 Executions executions = listExecutions.execute();
568 // If no executions, does this give NOT_FOUND or empty set?
569 if (executions == null || executions.getItems().isEmpty()) {
570 return new DeploymentInfoBuilder().withId(deployment.getId())
571 .withDeploymentInputs(deployment.getInputs()).build();
573 return new DeploymentInfoBuilder().withId(deployment.getId())
574 .withDeploymentInputs(deployment.getInputs())
575 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get())
576 .fromExecution(executions.getItems().get(0)).build();
578 } catch (CloudifyConnectException ce) {
579 // Couldn't connect to Cloudify
580 logger.error("{} QueryDeployment: Cloudify connection failure: {} ", MessageEnum.RA_CREATE_STACK_ERR,
581 ErrorCode.BusinessProcessError.getValue(), ce);
582 throw new MsoIOException(ce.getMessage(), ce);
583 } catch (CloudifyResponseException re) {
584 if (re.getStatus() == 404) {
585 // Got a NOT FOUND error. React differently based on deployment vs. execution
586 if (deployment != null) {
587 // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
588 return new DeploymentInfoBuilder().withId(deployment.getId())
589 .withDeploymentInputs(deployment.getInputs())
590 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get()).build();
592 // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
593 return new DeploymentInfoBuilder().withId(deploymentId).build();
596 throw new MsoCloudifyException(re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
597 } catch (Exception e) {
599 throw new MsoAdapterException(e.getMessage(), e);
605 * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be considered a successful
606 * deletion. The return value is a DeploymentInfo object which contains the last deployment status.
608 * There is no rollback from a successful deletion. A deletion failure will also result in an undefined deployment
609 * state - the components may or may not have been all or partially deleted, so the resulting deployment must be
610 * considered invalid.
612 * @param tenantId The Openstack ID of the tenant in which to perform the delete
613 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
614 * @return A StackInfo object
615 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
616 * @throws MsoCloudSiteNotFound
618 public DeploymentInfo uninstallAndDeleteDeployment(String cloudSiteId, String tenantId, String deploymentId,
619 int timeoutMinutes) throws MsoException {
620 // Obtain the cloud site information where we will create the stack
621 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
622 if (!cloudSite.isPresent()) {
623 throw new MsoCloudSiteNotFound(cloudSiteId);
626 Cloudify cloudify = getCloudifyClient(cloudSite.get());
628 logger.debug("Ready to Uninstall/Delete Deployment ({})", deploymentId);
630 // Query first to save the trouble if deployment not found
632 GetDeployment queryDeploymentRequest = cloudify.deployments().byId(deploymentId);
633 logger.debug(queryDeploymentRequest.toString());
635 // deployment = executeAndRecordCloudifyRequest (queryDeploymentRequest);
636 } catch (CloudifyResponseException e) {
637 // Since this came on the 'Create Deployment' command, nothing was changed
638 // in the cloud. Return the error as an exception.
639 if (e.getStatus() == 404) {
640 // Deployment doesn't exist. Return a "NOTFOUND" DeploymentInfo object
641 // TODO: Should return NULL?
642 logger.debug("Deployment requested for deletion does not exist: {}", deploymentId);
643 return new DeploymentInfoBuilder().withId(deploymentId).withStatus(DeploymentStatus.NOTFOUND).build();
645 // Convert the CloudifyResponseException to an MsoOpenstackException
646 logger.debug("ERROR STATUS = {}, \n {}\n {}\n {}", e.getStatus(), e.getMessage(),
647 e.getLocalizedMessage(), e);
648 MsoException me = cloudifyExceptionToMsoException(e, DELETE_DEPLOYMENT);
649 me.setCategory(MsoExceptionCategory.INTERNAL);
652 } catch (CloudifyConnectException e) {
653 // Error connecting to Cloudify instance. Convert to an MsoException
654 throw cloudifyExceptionToMsoException(e, DELETE_DEPLOYMENT);
655 } catch (RuntimeException e) {
657 throw runtimeExceptionToMsoException(e, DELETE_DEPLOYMENT);
661 * Query the outputs before deleting so they can be returned as well
663 // DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
666 * Next execute the "uninstall" workflow. Note - this assumes there are no additional parameters required for
669 // TODO: No deletePollInterval that I'm aware of. Use the create interval
670 int deletePollInterval =
671 Integer.parseInt(this.environment.getProperty(deletePollIntervalProp, deletePollIntervalDefault));
672 int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
674 Execution uninstallWorkflow = null;
678 executeWorkflow(cloudify, deploymentId, UNINSTALL, null, true, pollTimeout, deletePollInterval);
680 if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
681 // Successful uninstall.
682 logger.debug("Uninstall successful for deployment {}", deploymentId);
684 // The uninstall workflow completed with an error. Must fail the request, but will
685 // leave the deployment in an indeterminate state, as cloud resources may still exist.
686 MsoCloudifyException me =
687 new MsoCloudifyException(0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
688 me.addContext(DELETE_DEPLOYMENT);
692 } catch (MsoException me) {
693 // Uninstall workflow has failed.
694 // Must fail the deletion... may leave the deployment in an inconclusive state
695 me.addContext(DELETE_DEPLOYMENT);
700 // At this point, the deployment has been successfully uninstalled.
701 // Next step is to delete the deployment itself
702 Deployment deployment;
704 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
705 logger.debug(deleteRequest.toString());
707 // The delete request returns the deleted deployment
708 deployment = deleteRequest.execute();
710 } catch (CloudifyConnectException ce) {
711 // Failed to delete. Must fail the request, but will leave the (uninstalled)
712 // deployment in Cloudify DB.
713 MsoCloudifyException me = new MsoCloudifyException(0, "Deployment Delete Failed", ce.getMessage(), ce);
714 me.addContext(DELETE_DEPLOYMENT);
717 } catch (CloudifyResponseException re) {
718 // Failed to delete. Must fail the request, but will leave the (uninstalled)
719 // deployment in the Cloudify DB.
720 MsoCloudifyException me = new MsoCloudifyException(re.getStatus(), re.getMessage(), re.getMessage(), re);
721 me.addContext(DELETE_DEPLOYMENT);
724 } catch (Exception e) {
726 MsoAdapterException ae = new MsoAdapterException(e.getMessage(), e);
727 ae.addContext(DELETE_DEPLOYMENT);
732 // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
733 return new DeploymentInfoBuilder().withId(deployment.getId()).withDeploymentInputs(deployment.getInputs())
734 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get())
735 .fromExecution(uninstallWorkflow).build();
740 * Check if a blueprint is available for use at a targeted cloud site. This requires checking the Cloudify Manager
741 * which is servicing that cloud site to see if the specified blueprint has been loaded.
743 * @param cloudSiteId The cloud site where the blueprint is needed
744 * @param blueprintId The ID for the blueprint in Cloudify
746 public boolean isBlueprintLoaded(String cloudSiteId, String blueprintId) throws MsoException {
747 // Obtain the cloud site information where we will load the blueprint
748 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
749 if (!cloudSite.isPresent()) {
750 throw new MsoCloudSiteNotFound(cloudSiteId);
753 Cloudify cloudify = getCloudifyClient(cloudSite.get());
755 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
757 Blueprint bp = getRequest.execute();
759 logger.debug("Blueprint exists: {}", bp.getId());
762 logger.debug("Null blueprint!");
765 } catch (CloudifyResponseException ce) {
766 if (ce.getStatus() == 404) {
775 * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site. The blueprint currently must be
776 * structured as a single directory with all of the required files. One of those files is designated the "main file"
777 * for the blueprint. Files are provided as byte arrays, though expect only text files will be distributed from ASDC
780 * Cloudify requires a single root directory in its blueprint zip files. The requested blueprint ID will also be
781 * used as the directory. All of the files will be added to this directory in the zip file.
783 public void uploadBlueprint(String cloudSiteId, String blueprintId, String mainFileName,
784 Map<String, byte[]> blueprintFiles, boolean failIfExists) throws MsoException {
785 // Obtain the cloud site information where we will load the blueprint
786 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
787 if (!cloudSite.isPresent()) {
788 throw new MsoCloudSiteNotFound(cloudSiteId);
791 Cloudify cloudify = getCloudifyClient(cloudSite.get());
793 boolean blueprintUploaded = uploadBlueprint(cloudify, blueprintId, mainFileName, blueprintFiles);
795 if (!blueprintUploaded && failIfExists) {
796 throw new MsoAdapterException("Blueprint already exists");
801 * Common method to load a blueprint. May be called from
803 protected boolean uploadBlueprint(Cloudify cloudify, String blueprintId, String mainFileName,
804 Map<String, byte[]> blueprintFiles) throws MsoException {
805 // Check if it already exists. If so, return false.
806 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
808 Blueprint bp = getRequest.execute();
810 logger.debug("Blueprint {} already exists.", bp.getId());
813 logger.debug("Null blueprint!");
815 } catch (CloudifyResponseException ce) {
816 if (ce.getStatus() == 404) {
817 // This is the expected result.
818 logger.debug("Verified that Blueprint doesn't exist yet");
824 // Create a blueprint ZIP file in memory
825 ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
826 ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
829 // Put the root directory
830 String rootDir = blueprintId + (blueprintId.endsWith("/") ? "" : "/");
831 zipOut.putNextEntry(new ZipEntry(rootDir));
834 for (String fileName : blueprintFiles.keySet()) {
835 ZipEntry ze = new ZipEntry(rootDir + fileName);
836 zipOut.putNextEntry(ze);
837 zipOut.write(blueprintFiles.get(fileName));
841 } catch (IOException e) {
842 // Since we're writing to a byte array, this should never happen
844 logger.debug("Blueprint zip file size: {}", zipBuffer.size());
846 // Ready to upload the blueprint zip
848 try (InputStream blueprintStream = new ByteArrayInputStream(zipBuffer.toByteArray())) {
849 UploadBlueprint uploadRequest =
850 cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
851 Blueprint blueprint = uploadRequest.execute();
852 logger.debug("Successfully uploaded blueprint {}", blueprint.getId());
853 } catch (CloudifyResponseException | CloudifyConnectException e) {
854 throw cloudifyExceptionToMsoException(e, UPLOAD_BLUEPRINT);
855 } catch (RuntimeException e) {
857 throw runtimeExceptionToMsoException(e, UPLOAD_BLUEPRINT);
858 } catch (IOException e) {
859 // for try-with-resources
860 throw ioExceptionToMsoException(e, UPLOAD_BLUEPRINT);
868 // ---------------------------------------------------------------
869 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
872 * Get a Cloudify client for the specified cloud site. Everything that is required can be found in the Cloud Config.
875 * @return a Cloudify object
877 public Cloudify getCloudifyClient(CloudSite cloudSite) throws MsoException {
878 CloudifyManager cloudifyConfig = cloudConfig.getCloudifyManager(cloudSite.getCloudifyId());
879 if (cloudifyConfig == null) {
880 throw new MsoCloudifyManagerNotFound(cloudSite.getId());
883 // Get a Cloudify client
884 // Set a Token Provider to fetch tokens from Cloudify itself.
885 String cloudifyUrl = cloudifyConfig.getCloudifyUrl();
886 Cloudify cloudify = new Cloudify(cloudifyUrl);
887 cloudify.setTokenProvider(new CloudifyClientTokenProvider(cloudifyUrl, cloudifyConfig.getUsername(),
888 CryptoUtils.decryptCloudConfigPassword(cloudifyConfig.getPassword())));
895 * Query for a Cloudify Deployment. This function is needed in several places, so a common method is useful. This
896 * method takes an authenticated CloudifyClient (which internally identifies the cloud & tenant to search), and
897 * returns a Deployment object if found, Null if not found, or an MsoCloudifyException if the Cloudify API call
900 * @param cloudifyClient an authenticated Cloudify client
902 * @param deploymentId the deployment to query
904 * @return a Deployment object or null if the requested deployment doesn't exist.
906 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception
908 protected Deployment queryDeployment(Cloudify cloudify, String deploymentId) throws MsoException {
909 if (deploymentId == null) {
913 GetDeployment request = cloudify.deployments().byId(deploymentId);
914 return executeAndRecordCloudifyRequest(request);
915 } catch (CloudifyResponseException e) {
916 if (e.getStatus() == 404) {
917 logger.debug("queryDeployment - not found: {}", deploymentId);
920 // Convert the CloudifyResponseException to an MsoCloudifyException
921 throw cloudifyExceptionToMsoException(e, "QueryDeployment");
923 } catch (CloudifyConnectException e) {
924 // Connection to Openstack failed
925 throw cloudifyExceptionToMsoException(e, "QueryDeployment");
930 public void copyStringOutputsToInputs(Map<String, String> inputs, Map<String, Object> otherStackOutputs,
932 if (inputs == null || otherStackOutputs == null)
935 for (Map.Entry<String, Object> entry : otherStackOutputs.entrySet()) {
936 String key = entry.getKey();
937 Object value = entry.getValue();
939 if (value instanceof JsonNode) {
940 // This is a bit of mess - but I think it's the least impacting
941 // let's convert it BACK to a string - then it will get converted back later
943 inputs.put(key, this.convertNode((JsonNode) value));
944 } catch (Exception e) {
945 logger.debug("WARNING: unable to convert JsonNode output value for {}", key);
946 // effect here is this value will not have been copied to the inputs - and therefore will error out
949 } else if (value instanceof java.util.LinkedHashMap) {
950 logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
952 inputs.put(key, JSON_MAPPER.writeValueAsString(value));
953 } catch (Exception e) {
954 logger.debug("WARNING: unable to convert LinkedHashMap output value for {}", key);
957 // just try to cast it - could be an integer or some such
959 inputs.put(key, (String) value);
960 } catch (Exception e) {
961 logger.debug("WARNING: unable to convert output value for {}", key);
962 // effect here is this value will not have been copied to the inputs - and therefore will error out
971 * Normalize an input value to an Object, based on the target parameter type. If the type is not recognized, it will
972 * just be returned unchanged (as a string).
974 public Object convertInputValue(Object inputValue, HeatTemplateParam templateParam) {
975 String type = templateParam.getParamType();
976 logger.debug("Parameter: {} is of type {}", templateParam.getParamName(), type);
978 if (("number").equalsIgnoreCase(type)) {
980 return Integer.valueOf(inputValue.toString());
981 } catch (Exception e) {
982 logger.debug("Unable to convert {} to an integer!", inputValue);
985 } else if (("json").equalsIgnoreCase(type)) {
987 if (inputValue instanceof String) {
988 return JSON_MAPPER.readTree(inputValue.toString());
990 // will already marshal to json without intervention
992 } catch (Exception e) {
993 logger.debug("Unable to convert {} to a JsonNode!", inputValue);
996 } else if (("boolean").equalsIgnoreCase(type)) {
997 return Boolean.valueOf(inputValue.toString());
1000 // Nothing else matched. Return the original string
1005 private String convertNode(final JsonNode node) {
1007 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
1008 return JSON_MAPPER.writeValueAsString(obj);
1009 } catch (JsonParseException jpe) {
1010 logger.debug("Error converting json to string {}", jpe);
1011 } catch (Exception e) {
1012 logger.debug("Error converting json to string {}", e);
1014 return "[Error converting json to string]";
1019 * Method to execute a Cloudify command and track its execution time. For the metrics log, a category of "Cloudify"
1020 * is used along with a sub-category that identifies the specific call (using the real cloudify-client classname of
1021 * the CloudifyRequest<T> parameter).
1025 protected <T> T executeAndRecordCloudifyRequest(CloudifyRequest<T> request) {
1028 if (request.getClass().getEnclosingClass() != null) {
1030 request.getClass().getEnclosingClass().getSimpleName() + "." + request.getClass().getSimpleName();
1032 requestType = request.getClass().getSimpleName();
1035 int retryDelay = poConfig.getRetryDelay();
1036 int retryCount = poConfig.getRetryCount();
1037 String retryCodes = poConfig.getRetryCodes();
1039 // Run the actual command. All exceptions will be propagated
1042 return request.execute();
1043 } catch (CloudifyResponseException e) {
1044 boolean retry = false;
1045 if (retryCodes != null) {
1046 int code = e.getStatus();
1047 logger.debug("Config values RetryDelay: {} RetryCount:{} RetryCodes:{} ResponseCode:{}", retryDelay,
1048 retryCount, retryCodes, code);
1049 for (String rCode : retryCodes.split(",")) {
1051 if (retryCount > 0 && code == Integer.parseInt(rCode)) {
1055 "CloudifyResponseException ResponseCode:{} request:{} Retry indicated. Attempts remaining:{}",
1056 code, requestType, retryCount);
1059 } catch (NumberFormatException e1) {
1060 logger.error("{} No retries. Exception in parsing retry code in config:{} {}",
1061 MessageEnum.RA_CONFIG_EXC, rCode, ErrorCode.SchemaError.getValue());
1067 sleep(retryDelay * 1000L);
1069 throw e; // exceeded retryCount or code is not retryable
1070 } catch (CloudifyConnectException e) {
1071 // Connection to Cloudify failed
1072 if (retryCount > 0) {
1074 logger.debug(" request: {} Retry indicated. Attempts remaining:{}", requestType, retryCount);
1075 sleep(retryDelay * 1000L);
1084 * Convert an Exception on a Cloudify call to an MsoCloudifyException. This method supports
1085 * CloudifyResponseException and CloudifyConnectException.
1087 protected MsoException cloudifyExceptionToMsoException(CloudifyBaseException e, String context) {
1088 MsoException me = null;
1090 if (e instanceof CloudifyResponseException) {
1091 CloudifyResponseException re = (CloudifyResponseException) e;
1094 // Failed Cloudify calls return an error entity body.
1095 CloudifyError error = re.getResponse().getErrorEntity(CloudifyError.class);
1096 logger.error("{} {} {} Exception - Cloudify Error on {}: {}", MessageEnum.RA_CONNECTION_EXCEPTION,
1097 CLOUDIFY, ErrorCode.DataError.getValue(), context, error.getErrorCode());
1098 String fullError = error.getErrorCode() + ": " + error.getMessage();
1099 logger.debug(fullError);
1100 me = new MsoCloudifyException(re.getStatus(), re.getMessage(), fullError);
1101 } catch (Exception e2) {
1102 // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
1103 logger.error("{} {} {} Exception - HTTP Error on {}: {}, {} ", MessageEnum.RA_CONNECTION_EXCEPTION,
1104 CLOUDIFY, ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
1105 me = new MsoCloudifyException(re.getStatus(), re.getMessage(), "");
1108 // Add the context of the error
1109 me.addContext(context);
1111 // Generate an alarm for 5XX and higher errors.
1112 if (re.getStatus() >= 500) {
1115 } else if (e instanceof CloudifyConnectException) {
1116 CloudifyConnectException ce = (CloudifyConnectException) e;
1118 me = new MsoIOException(ce.getMessage());
1119 me.addContext(context);
1121 // Generate an alarm for all connection errors.
1123 logger.error("{} {} {} Cloudify connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY,
1124 ErrorCode.DataError.getValue(), context, e);
1132 /*******************************************************************************
1134 * Methods (and associated utilities) to implement the VduPlugin interface
1136 *******************************************************************************/
1139 * VduPlugin interface for instantiate function.
1141 * This one is a bit more complex, in that it will first upload the blueprint if needed, then create the Cloudify
1142 * deployment and execute the install workflow.
1144 * This implementation also merges any parameters defined in the ENV file with the other other input parameters for
1145 * any undefined parameters). The basic MsoCloudifyUtils separates blueprint management from deploument actions, but
1146 * the VduPlugin does not declare blueprint management operations.
1149 public VduInstance instantiateVdu(CloudInfo cloudInfo, String instanceName, Map<String, Object> inputs,
1150 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
1151 String cloudSiteId = cloudInfo.getCloudSiteId();
1152 String tenantId = cloudInfo.getTenantId();
1154 // Translate the VDU ModelInformation structure to that which is needed for
1155 // creating and uploading a blueprint. Use the model customization UUID as
1156 // the blueprint identifier.
1158 String blueprintId = vduModel.getModelCustomizationUUID();
1162 if (!isBlueprintLoaded(cloudSiteId, blueprintId)) {
1163 logger.debug("Blueprint {} is not loaded. Will upload it now.", blueprintId);
1165 // Prepare the blueprint inputs. Need the set of blueprint templates and files,
1166 // plus the main blueprint name.
1167 Map<String, byte[]> blueprintFiles = new HashMap<>();
1168 String mainTemplate = "";
1170 // Add all of the blueprint artifacts from the VDU model
1171 List<VduArtifact> vduArtifacts = vduModel.getArtifacts();
1172 for (VduArtifact vduArtifact : vduArtifacts) {
1173 // Add all artifacts to the blueprint, with one exception.
1174 // ENVIRONMENT files will be processed later as additional parameters.
1176 ArtifactType artifactType = vduArtifact.getType();
1177 if (artifactType != ArtifactType.ENVIRONMENT) {
1178 blueprintFiles.put(vduArtifact.getName(), vduArtifact.getContent());
1180 if (artifactType == ArtifactType.MAIN_TEMPLATE) {
1181 mainTemplate = vduArtifact.getName();
1186 // Upload the blueprint package
1187 uploadBlueprint(cloudSiteId, blueprintId, mainTemplate, blueprintFiles, false);
1189 } catch (Exception e) {
1190 throw new VduException("CloudifyUtils (instantiateVDU): blueprint Exception", e);
1194 // Next, create and install a new deployment based on the blueprint.
1195 // For Cloudify, the deploymentId is specified by the client. Just use the instance name
1199 // Query the Cloudify Deployment object and populate a VduInstance
1200 DeploymentInfo deployment =
1201 createAndInstallDeployment(cloudSiteId, tenantId, instanceName, blueprintId, inputs, true, // (poll
1204 vduModel.getTimeoutMinutes(), rollbackOnFailure);
1206 return deploymentInfoToVduInstance(deployment);
1207 } catch (Exception e) {
1208 throw new VduException("CloudifyUtils (instantiateVDU): Create-and-install-deployment Exception", e);
1214 * VduPlugin interface for query function.
1217 public VduInstance queryVdu(CloudInfo cloudInfo, String instanceId) throws VduException {
1218 String cloudSiteId = cloudInfo.getCloudSiteId();
1219 String tenantId = cloudInfo.getTenantId();
1222 // Query the Cloudify Deployment object and populate a VduInstance
1223 DeploymentInfo deployment = queryDeployment(cloudSiteId, tenantId, instanceId);
1225 return deploymentInfoToVduInstance(deployment);
1226 } catch (Exception e) {
1227 throw new VduException("Query VDU Exception", e);
1233 * VduPlugin interface for delete function.
1236 public VduInstance deleteVdu(CloudInfo cloudInfo, String instanceId, int timeoutMinutes) throws VduException {
1237 String cloudSiteId = cloudInfo.getCloudSiteId();
1238 String tenantId = cloudInfo.getTenantId();
1241 // Uninstall and delete the Cloudify Deployment
1242 DeploymentInfo deployment = uninstallAndDeleteDeployment(cloudSiteId, tenantId, instanceId, timeoutMinutes);
1244 // Populate a VduInstance based on the deleted Cloudify Deployment object
1245 return deploymentInfoToVduInstance(deployment);
1246 } catch (Exception e) {
1247 throw new VduException("Delete VDU Exception", e);
1253 * VduPlugin interface for update function.
1255 * Update is currently not supported in the MsoCloudifyUtils implementation. Just return a VduException.
1259 public VduInstance updateVdu(CloudInfo cloudInfo, String instanceId, Map<String, Object> inputs,
1260 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
1261 throw new VduException("CloudifyUtils: updateVDU interface not supported");
1266 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1268 protected VduInstance deploymentInfoToVduInstance(DeploymentInfo deployment) {
1269 VduInstance vduInstance = new VduInstance();
1271 // only one ID in Cloudify, use for both VDU name and ID
1272 vduInstance.setVduInstanceId(deployment.getId());
1273 vduInstance.setVduInstanceName(deployment.getId());
1275 // Copy inputs and outputs
1276 vduInstance.setInputs(deployment.getInputs());
1277 vduInstance.setOutputs(deployment.getOutputs());
1279 // Translate the status elements
1280 vduInstance.setStatus(deploymentStatusToVduStatus(deployment));
1285 protected VduStatus deploymentStatusToVduStatus(DeploymentInfo deployment) {
1286 VduStatus vduStatus = new VduStatus();
1288 // Determine the status based on last action & status
1289 // DeploymentInfo object should be enhanced to report a better status internally.
1290 DeploymentStatus status = deployment.getStatus();
1292 if (status == null) {
1293 vduStatus.setState(VduStateType.UNKNOWN);
1294 } else if (status == DeploymentStatus.NOTFOUND) {
1295 vduStatus.setState(VduStateType.NOTFOUND);
1296 } else if (status == DeploymentStatus.INSTALLED) {
1297 vduStatus.setState(VduStateType.INSTANTIATED);
1298 } else if (status == DeploymentStatus.CREATED) {
1299 // Deployment exists but is not installed. This shouldn't really happen,
1300 // since create + install or uninstall + delete are always done together.
1301 // But account for it anyway, assuming the operation is still in progress.
1302 String lastAction = deployment.getLastAction();
1303 if (lastAction == null)
1304 vduStatus.setState(VduStateType.INSTANTIATING);
1306 vduStatus.setState(VduStateType.DELETING);
1307 } else if (status == DeploymentStatus.FAILED) {
1308 vduStatus.setState(VduStateType.FAILED);
1310 vduStatus.setState(VduStateType.UNKNOWN);
1313 vduStatus.setErrorMessage(deployment.getErrorMessage());
1314 vduStatus.setLastAction(new PluginAction(deployment.getLastAction(), deployment.getActionStatus(),
1315 deployment.getErrorMessage()));
1321 * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in. Base the values on the CloudSite
1324 protected OpenstackConfig getOpenstackConfig(CloudSite cloudSite, String tenantId) {
1325 OpenstackConfig openstackConfig = new OpenstackConfig();
1326 openstackConfig.setRegion(cloudSite.getRegionId());
1327 openstackConfig.setAuthUrl(cloudSite.getIdentityService().getIdentityUrl());
1328 openstackConfig.setUsername(cloudSite.getIdentityService().getMsoId());
1330 .setPassword(CryptoUtils.decryptCloudConfigPassword(cloudSite.getIdentityService().getMsoPass()));
1331 openstackConfig.setTenantName(tenantId);
1332 return openstackConfig;
1336 * Return an Azure object as expected by Cloudify Azure Plug-in. Base the values on the CloudSite definition.
1338 protected AzureConfig getAzureConfig(CloudSite cloudSite, String tenantId) {
1339 AzureConfig azureConfig = new AzureConfig();
1340 // TODO: Use adminTenant for now, instead of adding another element
1341 azureConfig.setSubscriptionId(cloudSite.getIdentityService().getAdminTenant());
1342 azureConfig.setTenantId(tenantId);
1343 azureConfig.setClientId(cloudSite.getIdentityService().getMsoId());
1344 azureConfig.setClientSecret(cloudSite.getIdentityService().getMsoPass());
1348 private void sleep(long time) {
1351 } catch (InterruptedException e) {
1352 logger.debug("Thread interrupted while sleeping!", e);
1353 Thread.currentThread().interrupt();