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 = "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";
113 // Fetch cloud configuration each time (may be cached in CloudConfig class)
115 protected CloudConfig cloudConfig;
118 private Environment environment;
121 private PoConfig poConfig;
123 private static final Logger logger = LoggerFactory.getLogger(MsoCloudifyUtils.class);
125 // Properties names and variables (with default values)
126 protected String createPollIntervalProp = "org.onap.so.adapters.po.pollInterval";
127 private String deletePollIntervalProp = "org.onap.so.adapters.po.pollInterval";
129 protected String createPollIntervalDefault = "15";
130 private String deletePollIntervalDefault = "15";
132 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
135 * Create a new Deployment from a specified blueprint, and install it in the specified cloud location and tenant.
136 * The blueprint identifier and parameter map are passed in as arguments, along with the cloud access credentials.
137 * The blueprint should have been previously uploaded to Cloudify.
139 * It is expected that parameters have been validated and contain at minimum the required parameters for the given
140 * template with no extra (undefined) parameters..
142 * The deployment ID supplied by the caller must be unique in the scope of the Cloudify tenant (not the Openstack
143 * tenant). However, it should also be globally unique, as it will be the identifier for the resource going forward
144 * in Inventory. This latter is managed by the higher levels invoking this function.
146 * This function executes the "install" workflow on the newly created workflow. Cloudify will be polled for
147 * completion unless the client requests otherwise.
149 * An error will be thrown if the requested Deployment already exists in the specified Cloudify instance.
151 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
152 * @param tenantId The Openstack ID of the tenant in which to create the Stack
153 * @param deploymentId The identifier (name) of the deployment to create
154 * @param blueprintId The blueprint from which to create the deployment.
155 * @param inputs A map of key/value inputs
156 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
157 * @param timeoutMinutes Timeout after which the "install" will be cancelled
158 * @param backout Flag to delete deployment on install Failure - defaulted to True
159 * @return A DeploymentInfo object
160 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
161 * @throws MsoIOException Thrown on Cloudify connection errors.
164 public DeploymentInfo createAndInstallDeployment(String cloudSiteId, String tenantId, String deploymentId,
165 String blueprintId, Map<String, ? extends Object> inputs, boolean pollForCompletion, int timeoutMinutes,
166 boolean backout) throws MsoException {
167 // Obtain the cloud site information where we will create the stack
168 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
169 if (!cloudSite.isPresent()) {
170 throw new MsoCloudSiteNotFound(cloudSiteId);
173 Cloudify cloudify = getCloudifyClient(cloudSite.get());
175 logger.debug("Ready to Create Deployment ({}) with input params: {}", deploymentId, inputs);
177 // Build up the inputs, including:
178 // - from provided "environment" file
179 // - passed in by caller
180 // - special input for cloud-specific Credentials
181 Map<String, Object> expandedInputs = new HashMap<>(inputs);
183 String platform = cloudSite.get().getPlatform();
184 if (platform == null || platform.equals("") || platform.equalsIgnoreCase("OPENSTACK")) {
185 // Create the Cloudify OpenstackConfig with the credentials
186 OpenstackConfig openstackConfig = getOpenstackConfig(cloudSite.get(), tenantId);
187 expandedInputs.put("openstack_config", openstackConfig);
188 } else if (platform.equalsIgnoreCase("AZURE")) {
189 // Create Cloudify AzureConfig with the credentials
190 AzureConfig azureConfig = getAzureConfig(cloudSite.get(), tenantId);
191 expandedInputs.put("azure_config", azureConfig);
194 // Build up the parameters to create a new deployment
195 CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
196 deploymentParams.setBlueprintId(blueprintId);
197 deploymentParams.setInputs(expandedInputs);
199 Deployment deployment = null;
201 CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
202 logger.debug(createDeploymentRequest.toString());
204 deployment = executeAndRecordCloudifyRequest(createDeploymentRequest);
205 } catch (CloudifyResponseException e) {
206 // Since this came on the 'Create Deployment' command, nothing was changed
207 // in the cloud. Return the error as an exception.
208 if (e.getStatus() == 409) {
209 // Deployment already exists. Return a specific error for this case
210 MsoException me = new MsoDeploymentAlreadyExists(deploymentId, cloudSiteId);
211 me.addContext(CREATE_DEPLOYMENT);
214 // Convert the CloudifyResponseException to an MsoException
215 logger.debug("ERROR STATUS = {},\n{}\n{}", e.getStatus(), e.getMessage(), e.getLocalizedMessage());
216 MsoException me = cloudifyExceptionToMsoException(e, CREATE_DEPLOYMENT);
217 me.setCategory(MsoExceptionCategory.OPENSTACK);
220 } catch (CloudifyConnectException e) {
221 // Error connecting to Cloudify instance. Convert to an MsoException
222 throw cloudifyExceptionToMsoException(e, CREATE_DEPLOYMENT);
223 } catch (RuntimeException e) {
225 throw runtimeExceptionToMsoException(e, CREATE_DEPLOYMENT);
229 * It can take some time for Cloudify to be ready to execute a workflow on the deployment. Sleep 30 seconds
230 * based on observation of behavior in a Cloudify VM instance (delay due to "create_deployment_environment").
235 * Next execute the "install" workflow. Note - this assumes there are no additional parameters required for the
238 int createPollInterval =
239 Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
240 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
242 Execution installWorkflow = null;
245 installWorkflow = executeWorkflow(cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout,
248 if (installWorkflow.getStatus().equals(TERMINATED)) {
250 // Create and return a DeploymentInfo structure. Include the Runtime outputs
251 return new DeploymentInfoBuilder().withId(deployment.getId())
252 .withDeploymentInputs(deployment.getInputs())
253 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get())
254 .fromExecution(installWorkflow).build();
256 // The workflow completed with errors. Must try to back it out.
258 logger.warn("{} Deployment installation failed, backout deletion suppressed {} {}",
259 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(),
260 "Exception in Deployment Installation, backout suppressed");
262 // Poll on delete if we rollback - use same values for now
263 int deletePollInterval = createPollInterval;
264 int deletePollTimeout = pollTimeout;
267 // Run the uninstall to undo the install
268 Execution uninstallWorkflow = executeWorkflow(cloudify, deploymentId, "uninstall", null,
269 pollForCompletion, deletePollTimeout, deletePollInterval);
271 if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
272 // The uninstall completed. Delete the deployment itself
273 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
274 executeAndRecordCloudifyRequest(deleteRequest);
276 // Didn't uninstall successfully. Log this error
277 logger.error("{} Create Deployment: Cloudify error rolling back deployment install: {} {}",
278 MessageEnum.RA_CREATE_STACK_ERR, installWorkflow.getError(),
279 ErrorCode.BusinessProcesssError.getValue());
281 } catch (Exception e) {
282 // Catch-all for backout errors trying to uninstall/delete
283 // Log this error, and return the original exception
284 logger.error("{} Create Stack: Nested exception rolling back deployment install: {}",
285 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(), e);
289 MsoCloudifyException me =
290 new MsoCloudifyException(0, "Workflow Execution Failed", installWorkflow.getError());
291 me.addContext(CREATE_DEPLOYMENT);
295 } catch (MsoException me) {
296 // Install failed. Unless requested otherwise, back out the deployment
299 logger.warn("{} Deployment installation failed, backout deletion suppressed {}",
300 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue());
302 // Poll on delete if we rollback - use same values for now
303 int deletePollInterval = createPollInterval;
304 int deletePollTimeout = pollTimeout;
307 // Run the uninstall to undo the install.
308 // Always try to run it, as it should be idempotent
309 executeWorkflow(cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout,
312 // Delete the deployment itself
313 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
314 executeAndRecordCloudifyRequest(deleteRequest);
315 } catch (Exception e) {
316 // Catch-all for backout errors trying to uninstall/delete
317 // Log this error, and return the original exception
318 logger.error("{} Create Stack: Nested exception rolling back deployment install: {} ",
319 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(), e);
323 // Propagate the original exception from Stack Query.
324 me.addContext(CREATE_DEPLOYMENT);
332 * Get the runtime Outputs of a deployment. Return the Map of tag/value outputs.
334 private Optional<Map<String, Object>> getDeploymentOutputs(Cloudify cloudify, String deploymentId)
335 throws MsoException {
336 // Build and send the Cloudify request
337 DeploymentOutputs deploymentOutputs;
339 GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
340 logger.debug(queryDeploymentOutputs.toString());
342 deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs);
343 if (deploymentOutputs != null) {
344 return Optional.ofNullable(deploymentOutputs.getOutputs());
346 return Optional.empty();
348 } catch (CloudifyConnectException ce) {
349 // Couldn't connect to Cloudify
350 logger.error("{} QueryDeploymentOutputs: Cloudify connection failure: {} ", MessageEnum.RA_CREATE_STACK_ERR,
351 ErrorCode.BusinessProcesssError.getValue(), ce);
352 throw new MsoIOException(ce.getMessage(), ce);
353 } catch (CloudifyResponseException re) {
354 if (re.getStatus() == 404) {
356 return Optional.empty();
358 throw new MsoCloudifyException(re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
359 } catch (Exception e) {
361 throw new MsoAdapterException(e.getMessage(), e);
366 * Execute a workflow on a deployment. Handle polling for completion with timeout. Return the final Execution object
367 * with status. Throw an exception on Errors. Question - how does the client know whether rollback needs to be done?
369 private Execution executeWorkflow(Cloudify cloudify, String deploymentId, String workflowId,
370 Map<String, Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
371 throws MsoCloudifyException {
372 logger.debug("Executing '{}' workflow on deployment '{}'", workflowId, deploymentId);
374 StartExecutionParams executeParams = new StartExecutionParams();
375 executeParams.setWorkflowId(workflowId);
376 executeParams.setDeploymentId(deploymentId);
377 executeParams.setParameters(workflowParams);
379 Execution execution = null;
380 String executionId = null;
381 String command = "start";
382 Exception savedException = null;
385 StartExecution executionRequest = cloudify.executions().start(executeParams);
386 logger.debug(executionRequest.toString());
387 execution = executeAndRecordCloudifyRequest(executionRequest);
388 executionId = execution.getId();
390 if (!pollForCompletion) {
391 // Client did not request polling, so just return the Execution object
395 // Enter polling loop
396 boolean timedOut = false;
397 int pollTimeout = timeout;
399 String status = execution.getStatus();
401 // Create a reusable cloudify query request
402 GetExecution queryExecution = cloudify.executions().byId(executionId);
405 while (!timedOut && !(status.equals(TERMINATED) || status.equals("failed") || status.equals(CANCELLED))) {
406 // workflow is still running; check for timeout
407 if (pollTimeout <= 0) {
408 logger.debug("workflow {} timed out on deployment {}", execution.getWorkflowId(),
409 execution.getDeploymentId());
414 sleep(pollInterval * 1000L);
416 pollTimeout -= pollInterval;
417 logger.debug("pollTimeout remaining: " + pollTimeout);
419 execution = queryExecution.execute();
420 status = execution.getStatus();
423 // Broke the loop. Check again for a terminal state
424 if (status.equals(TERMINATED)) {
426 logger.debug("Workflow '{}' completed successfully on deployment '{}'", workflowId, deploymentId);
428 } else if (status.equals("failed")) {
429 // Workflow failed. Log it and return the execution object (don't throw exception here)
430 logger.error("{} Cloudify workflow failure: {} {} Execute Workflow: Failed: {}",
431 MessageEnum.RA_CREATE_STACK_ERR, execution.getError(),
432 ErrorCode.BusinessProcesssError.getValue(), execution.getError());
434 } else if (status.equals(CANCELLED)) {
435 // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the
436 // execution object (don't throw exception here)
437 logger.error("{} Cloudify workflow cancelled. Deployment is in an indeterminate state {} {} {}",
438 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(),
439 "Execute Workflow cancelled: ", workflowId);
442 // Can only get here after a timeout
443 logger.error("{} Cloudify workflow timeout {} Execute Workflow: Timed Out",
444 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue());
446 } catch (CloudifyConnectException ce) {
447 logger.error("{} {} Execute Workflow ({} {}): Cloudify connection failure {} ",
448 MessageEnum.RA_CREATE_STACK_ERR, ErrorCode.BusinessProcesssError.getValue(), command, ce);
450 } catch (CloudifyResponseException re) {
451 logger.error("{} {} Execute Workflow ({}): Cloudify response error {} ", MessageEnum.RA_CREATE_STACK_ERR,
452 ErrorCode.BusinessProcesssError.getValue(), command, re.getMessage(), re);
454 } catch (RuntimeException e) {
456 logger.error("{} {} Execute Workflow ({}): Internal error {}", MessageEnum.RA_CREATE_STACK_ERR,
457 ErrorCode.BusinessProcesssError.getValue(), command, e.getMessage(), e);
461 // Get to this point ONLY on an error or timeout
462 // The cloudify execution is still running (we've not received a terminal status),
463 // so try to Cancel it.
464 CancelExecutionParams cancelParams = new CancelExecutionParams();
465 cancelParams.setAction("cancel");
466 // TODO: Use force_cancel?
468 Execution cancelExecution = null;
471 CancelExecution cancelRequest = cloudify.executions().cancel(executionId, cancelParams);
472 logger.debug(cancelRequest.toString());
473 cancelExecution = cancelRequest.execute();
475 // Enter polling loop
476 boolean timedOut = false;
477 int cancelTimeout = timeout; // TODO: For now, just use same timeout
479 String status = cancelExecution.getStatus();
481 // Poll for completion. Create a reusable cloudify query request
482 GetExecution queryExecution = cloudify.executions().byId(executionId);
484 while (!timedOut && !status.equals(CANCELLED)) {
485 // workflow is still running; check for timeout
486 if (cancelTimeout <= 0) {
487 logger.debug("Cancel timeout for workflow {} on deployment {}", workflowId, deploymentId);
492 sleep(pollInterval * 1000L);
494 cancelTimeout -= pollInterval;
495 logger.debug("pollTimeout remaining: {}", cancelTimeout);
497 execution = queryExecution.execute();
498 status = execution.getStatus();
501 // Broke the loop. Check again for a terminal state
502 if (status.equals(CANCELLED)) {
503 // Finished cancelling. Return the original exception
504 logger.debug("Cancel workflow {} completed on deployment {}", workflowId, deploymentId);
505 throw new MsoCloudifyException(-1, "", "", savedException);
507 // Can only get here after a timeout
508 logger.debug("Cancel workflow {} timeout out on deployment {}", workflowId, deploymentId);
509 MsoCloudifyException exception = new MsoCloudifyException(-1, "", "", savedException);
510 exception.setPendingWorkflow(true);
513 } catch (Exception e) {
514 // Catch-all. Log the message and throw the original exception
515 logger.debug("Cancel workflow {} failed for deployment {} :", workflowId, deploymentId, e);
516 MsoCloudifyException exception = new MsoCloudifyException(-1, "", "", savedException);
517 exception.setPendingWorkflow(true);
525 * Query for a Cloudify Deployment (by Name). This call will always return a DeploymentInfo object. If the
526 * deployment does not exist, an "empty" DeploymentInfo will be returned - containing only the deployment ID and a
527 * special status of NOTFOUND.
529 * @param tenantId The Openstack ID of the tenant in which to query
530 * @param cloudSiteId The cloud identifier (may be a region) in which to query
531 * @return A StackInfo object
532 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
534 public DeploymentInfo queryDeployment(String cloudSiteId, String tenantId, String deploymentId)
535 throws MsoException {
536 logger.debug("Query Cloudify Deployment: {} in tenant {}", deploymentId, tenantId);
538 // Obtain the cloud site information where we will create the stack
539 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
540 if (!cloudSite.isPresent()) {
541 throw new MsoCloudSiteNotFound(cloudSiteId);
544 Cloudify cloudify = getCloudifyClient(cloudSite.get());
546 // Build and send the Cloudify request
547 Deployment deployment = new Deployment();
549 GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
550 logger.debug(queryDeployment.toString());
551 deployment = executeAndRecordCloudifyRequest(queryDeployment);
553 // Next look for the latest execution
554 ListExecutions listExecutions =
555 cloudify.executions().listFiltered("deployment_id=" + deploymentId, "-created_at");
556 Executions executions = listExecutions.execute();
558 // If no executions, does this give NOT_FOUND or empty set?
559 if (executions.getItems().isEmpty()) {
560 return new DeploymentInfoBuilder().withId(deployment.getId())
561 .withDeploymentInputs(deployment.getInputs()).build();
563 return new DeploymentInfoBuilder().withId(deployment.getId())
564 .withDeploymentInputs(deployment.getInputs())
565 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get())
566 .fromExecution(executions.getItems().get(0)).build();
568 } catch (CloudifyConnectException ce) {
569 // Couldn't connect to Cloudify
570 logger.error("{} QueryDeployment: Cloudify connection failure: {} ", MessageEnum.RA_CREATE_STACK_ERR,
571 ErrorCode.BusinessProcesssError.getValue(), ce);
572 throw new MsoIOException(ce.getMessage(), ce);
573 } catch (CloudifyResponseException re) {
574 if (re.getStatus() == 404) {
575 // Got a NOT FOUND error. React differently based on deployment vs. execution
576 if (deployment != null) {
577 // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
578 return new DeploymentInfoBuilder().withId(deployment.getId())
579 .withDeploymentInputs(deployment.getInputs())
580 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get()).build();
582 // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
583 return new DeploymentInfoBuilder().withId(deploymentId).build();
586 throw new MsoCloudifyException(re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
587 } catch (Exception e) {
589 throw new MsoAdapterException(e.getMessage(), e);
595 * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be considered a successful
596 * deletion. The return value is a DeploymentInfo object which contains the last deployment status.
598 * There is no rollback from a successful deletion. A deletion failure will also result in an undefined deployment
599 * state - the components may or may not have been all or partially deleted, so the resulting deployment must be
600 * considered invalid.
602 * @param tenantId The Openstack ID of the tenant in which to perform the delete
603 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
604 * @return A StackInfo object
605 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
606 * @throws MsoCloudSiteNotFound
608 public DeploymentInfo uninstallAndDeleteDeployment(String cloudSiteId, String tenantId, String deploymentId,
609 int timeoutMinutes) throws MsoException {
610 // Obtain the cloud site information where we will create the stack
611 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
612 if (!cloudSite.isPresent()) {
613 throw new MsoCloudSiteNotFound(cloudSiteId);
616 Cloudify cloudify = getCloudifyClient(cloudSite.get());
618 logger.debug("Ready to Uninstall/Delete Deployment ({})", deploymentId);
620 // Query first to save the trouble if deployment not found
622 GetDeployment queryDeploymentRequest = cloudify.deployments().byId(deploymentId);
623 logger.debug(queryDeploymentRequest.toString());
625 // deployment = executeAndRecordCloudifyRequest (queryDeploymentRequest);
626 } catch (CloudifyResponseException e) {
627 // Since this came on the 'Create Deployment' command, nothing was changed
628 // in the cloud. Return the error as an exception.
629 if (e.getStatus() == 404) {
630 // Deployment doesn't exist. Return a "NOTFOUND" DeploymentInfo object
631 // TODO: Should return NULL?
632 logger.debug("Deployment requested for deletion does not exist: {}", deploymentId);
633 return new DeploymentInfoBuilder().withId(deploymentId).withStatus(DeploymentStatus.NOTFOUND).build();
635 // Convert the CloudifyResponseException to an MsoOpenstackException
636 logger.debug("ERROR STATUS = {}, \n {}\n {}\n {}", e.getStatus(), e.getMessage(),
637 e.getLocalizedMessage(), e);
638 MsoException me = cloudifyExceptionToMsoException(e, DELETE_DEPLOYMENT);
639 me.setCategory(MsoExceptionCategory.INTERNAL);
642 } catch (CloudifyConnectException e) {
643 // Error connecting to Cloudify instance. Convert to an MsoException
644 throw cloudifyExceptionToMsoException(e, DELETE_DEPLOYMENT);
645 } catch (RuntimeException e) {
647 throw runtimeExceptionToMsoException(e, DELETE_DEPLOYMENT);
651 * Query the outputs before deleting so they can be returned as well
653 // DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
656 * Next execute the "uninstall" workflow. Note - this assumes there are no additional parameters required for
659 // TODO: No deletePollInterval that I'm aware of. Use the create interval
660 int deletePollInterval =
661 Integer.parseInt(this.environment.getProperty(deletePollIntervalProp, deletePollIntervalDefault));
662 int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
664 Execution uninstallWorkflow = null;
668 executeWorkflow(cloudify, deploymentId, "uninstall", null, true, pollTimeout, deletePollInterval);
670 if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
671 // Successful uninstall.
672 logger.debug("Uninstall successful for deployment {}", deploymentId);
674 // The uninstall workflow completed with an error. Must fail the request, but will
675 // leave the deployment in an indeterminate state, as cloud resources may still exist.
676 MsoCloudifyException me =
677 new MsoCloudifyException(0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
678 me.addContext(DELETE_DEPLOYMENT);
682 } catch (MsoException me) {
683 // Uninstall workflow has failed.
684 // Must fail the deletion... may leave the deployment in an inconclusive state
685 me.addContext(DELETE_DEPLOYMENT);
690 // At this point, the deployment has been successfully uninstalled.
691 // Next step is to delete the deployment itself
692 Deployment deployment;
694 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
695 logger.debug(deleteRequest.toString());
697 // The delete request returns the deleted deployment
698 deployment = deleteRequest.execute();
700 } catch (CloudifyConnectException ce) {
701 // Failed to delete. Must fail the request, but will leave the (uninstalled)
702 // deployment in Cloudify DB.
703 MsoCloudifyException me = new MsoCloudifyException(0, "Deployment Delete Failed", ce.getMessage(), ce);
704 me.addContext(DELETE_DEPLOYMENT);
707 } catch (CloudifyResponseException re) {
708 // Failed to delete. Must fail the request, but will leave the (uninstalled)
709 // deployment in the Cloudify DB.
710 MsoCloudifyException me = new MsoCloudifyException(re.getStatus(), re.getMessage(), re.getMessage(), re);
711 me.addContext(DELETE_DEPLOYMENT);
714 } catch (Exception e) {
716 MsoAdapterException ae = new MsoAdapterException(e.getMessage(), e);
717 ae.addContext(DELETE_DEPLOYMENT);
722 // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
723 return new DeploymentInfoBuilder().withId(deployment.getId()).withDeploymentInputs(deployment.getInputs())
724 .withDeploymentOutputs(getDeploymentOutputs(cloudify, deploymentId).get())
725 .fromExecution(uninstallWorkflow).build();
730 * Check if a blueprint is available for use at a targeted cloud site. This requires checking the Cloudify Manager
731 * which is servicing that cloud site to see if the specified blueprint has been loaded.
733 * @param cloudSiteId The cloud site where the blueprint is needed
734 * @param blueprintId The ID for the blueprint in Cloudify
736 public boolean isBlueprintLoaded(String cloudSiteId, String blueprintId) throws MsoException {
737 // Obtain the cloud site information where we will load the blueprint
738 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
739 if (!cloudSite.isPresent()) {
740 throw new MsoCloudSiteNotFound(cloudSiteId);
743 Cloudify cloudify = getCloudifyClient(cloudSite.get());
745 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
747 Blueprint bp = getRequest.execute();
748 logger.debug("Blueprint exists: {}", bp.getId());
750 } catch (CloudifyResponseException ce) {
751 if (ce.getStatus() == 404) {
756 } catch (Exception e) {
762 * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site. The blueprint currently must be
763 * structured as a single directory with all of the required files. One of those files is designated the "main file"
764 * for the blueprint. Files are provided as byte arrays, though expect only text files will be distributed from ASDC
767 * Cloudify requires a single root directory in its blueprint zip files. The requested blueprint ID will also be
768 * used as the directory. All of the files will be added to this directory in the zip file.
770 public void uploadBlueprint(String cloudSiteId, String blueprintId, String mainFileName,
771 Map<String, byte[]> blueprintFiles, boolean failIfExists) throws MsoException {
772 // Obtain the cloud site information where we will load the blueprint
773 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite(cloudSiteId);
774 if (!cloudSite.isPresent()) {
775 throw new MsoCloudSiteNotFound(cloudSiteId);
778 Cloudify cloudify = getCloudifyClient(cloudSite.get());
780 boolean blueprintUploaded = uploadBlueprint(cloudify, blueprintId, mainFileName, blueprintFiles);
782 if (!blueprintUploaded && failIfExists) {
783 throw new MsoAdapterException("Blueprint already exists");
788 * Common method to load a blueprint. May be called from
790 protected boolean uploadBlueprint(Cloudify cloudify, String blueprintId, String mainFileName,
791 Map<String, byte[]> blueprintFiles) throws MsoException {
792 // Check if it already exists. If so, return false.
793 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
795 Blueprint bp = getRequest.execute();
796 logger.debug("Blueprint {} already exists.", bp.getId());
798 } catch (CloudifyResponseException ce) {
799 if (ce.getStatus() == 404) {
800 // This is the expected result.
801 logger.debug("Verified that Blueprint doesn't exist yet");
805 } catch (Exception e) {
809 // Create a blueprint ZIP file in memory
810 ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
811 ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
814 // Put the root directory
815 String rootDir = blueprintId + ((blueprintId.endsWith("/") ? "" : "/"));
816 zipOut.putNextEntry(new ZipEntry(rootDir));
819 for (String fileName : blueprintFiles.keySet()) {
820 ZipEntry ze = new ZipEntry(rootDir + fileName);
821 zipOut.putNextEntry(ze);
822 zipOut.write(blueprintFiles.get(fileName));
826 } catch (IOException e) {
827 // Since we're writing to a byte array, this should never happen
829 logger.debug("Blueprint zip file size: {}", zipBuffer.size());
831 // Ready to upload the blueprint zip
833 try (InputStream blueprintStream = new ByteArrayInputStream(zipBuffer.toByteArray())) {
834 UploadBlueprint uploadRequest =
835 cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
836 Blueprint blueprint = uploadRequest.execute();
837 logger.debug("Successfully uploaded blueprint {}", blueprint.getId());
838 } catch (CloudifyResponseException | CloudifyConnectException e) {
839 throw cloudifyExceptionToMsoException(e, "UPLOAD_BLUEPRINT");
840 } catch (RuntimeException e) {
842 throw runtimeExceptionToMsoException(e, "UPLOAD_BLUEPRINT");
843 } catch (IOException e) {
844 // for try-with-resources
845 throw ioExceptionToMsoException(e, "UPLOAD_BLUEPRINT");
853 // ---------------------------------------------------------------
854 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
857 * Get a Cloudify client for the specified cloud site. Everything that is required can be found in the Cloud Config.
860 * @return a Cloudify object
862 public Cloudify getCloudifyClient(CloudSite cloudSite) throws MsoException {
863 CloudifyManager cloudifyConfig = cloudConfig.getCloudifyManager(cloudSite.getCloudifyId());
864 if (cloudifyConfig == null) {
865 throw new MsoCloudifyManagerNotFound(cloudSite.getId());
868 // Get a Cloudify client
869 // Set a Token Provider to fetch tokens from Cloudify itself.
870 String cloudifyUrl = cloudifyConfig.getCloudifyUrl();
871 Cloudify cloudify = new Cloudify(cloudifyUrl);
872 cloudify.setTokenProvider(new CloudifyClientTokenProvider(cloudifyUrl, cloudifyConfig.getUsername(),
873 CryptoUtils.decryptCloudConfigPassword(cloudifyConfig.getPassword())));
880 * Query for a Cloudify Deployment. This function is needed in several places, so a common method is useful. This
881 * method takes an authenticated CloudifyClient (which internally identifies the cloud & tenant to search), and
882 * returns a Deployment object if found, Null if not found, or an MsoCloudifyException if the Cloudify API call
885 * @param cloudifyClient an authenticated Cloudify client
887 * @param deploymentId the deployment to query
889 * @return a Deployment object or null if the requested deployment doesn't exist.
891 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception
893 protected Deployment queryDeployment(Cloudify cloudify, String deploymentId) throws MsoException {
894 if (deploymentId == null) {
898 GetDeployment request = cloudify.deployments().byId(deploymentId);
899 return executeAndRecordCloudifyRequest(request);
900 } catch (CloudifyResponseException e) {
901 if (e.getStatus() == 404) {
902 logger.debug("queryDeployment - not found: {}", deploymentId);
905 // Convert the CloudifyResponseException to an MsoCloudifyException
906 throw cloudifyExceptionToMsoException(e, "QueryDeployment");
908 } catch (CloudifyConnectException e) {
909 // Connection to Openstack failed
910 throw cloudifyExceptionToMsoException(e, "QueryDeployment");
915 public void copyStringOutputsToInputs(Map<String, String> inputs, Map<String, Object> otherStackOutputs,
917 if (inputs == null || otherStackOutputs == null)
920 for (Map.Entry<String, Object> entry : otherStackOutputs.entrySet()) {
921 String key = entry.getKey();
922 Object value = entry.getValue();
924 if (value instanceof JsonNode) {
925 // This is a bit of mess - but I think it's the least impacting
926 // let's convert it BACK to a string - then it will get converted back later
928 inputs.put(key, this.convertNode((JsonNode) value));
929 } catch (Exception e) {
930 logger.debug("WARNING: unable to convert JsonNode output value for {}", key);
931 // effect here is this value will not have been copied to the inputs - and therefore will error out
934 } else if (value instanceof java.util.LinkedHashMap) {
935 logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
937 inputs.put(key, JSON_MAPPER.writeValueAsString(value));
938 } catch (Exception e) {
939 logger.debug("WARNING: unable to convert LinkedHashMap output value for {}", key);
942 // just try to cast it - could be an integer or some such
944 inputs.put(key, (String) value);
945 } catch (Exception e) {
946 logger.debug("WARNING: unable to convert output value for {}", key);
947 // effect here is this value will not have been copied to the inputs - and therefore will error out
956 * Normalize an input value to an Object, based on the target parameter type. If the type is not recognized, it will
957 * just be returned unchanged (as a string).
959 public Object convertInputValue(Object inputValue, HeatTemplateParam templateParam) {
960 String type = templateParam.getParamType();
961 logger.debug("Parameter: {} is of type {}", templateParam.getParamName(), type);
963 if (type.equalsIgnoreCase("number")) {
965 return Integer.valueOf(inputValue.toString());
966 } catch (Exception e) {
967 logger.debug("Unable to convert {} to an integer!", inputValue);
970 } else if (type.equalsIgnoreCase("json")) {
972 if (inputValue instanceof String) {
973 return JSON_MAPPER.readTree(inputValue.toString());
975 // will already marshal to json without intervention
977 } catch (Exception e) {
978 logger.debug("Unable to convert {} to a JsonNode!", inputValue);
981 } else if (type.equalsIgnoreCase("boolean")) {
982 return new Boolean(inputValue.toString());
985 // Nothing else matched. Return the original string
990 private String convertNode(final JsonNode node) {
992 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
993 return JSON_MAPPER.writeValueAsString(obj);
994 } catch (JsonParseException jpe) {
995 logger.debug("Error converting json to string {}", jpe);
996 } catch (Exception e) {
997 logger.debug("Error converting json to string {}", e);
999 return "[Error converting json to string]";
1004 * Method to execute a Cloudify command and track its execution time. For the metrics log, a category of "Cloudify"
1005 * is used along with a sub-category that identifies the specific call (using the real cloudify-client classname of
1006 * the CloudifyRequest<T> parameter).
1010 protected <T> T executeAndRecordCloudifyRequest(CloudifyRequest<T> request) {
1013 if (request.getClass().getEnclosingClass() != null) {
1015 request.getClass().getEnclosingClass().getSimpleName() + "." + request.getClass().getSimpleName();
1017 requestType = request.getClass().getSimpleName();
1020 int retryDelay = poConfig.getRetryDelay();
1021 int retryCount = poConfig.getRetryCount();
1022 String retryCodes = poConfig.getRetryCodes();
1024 // Run the actual command. All exceptions will be propagated
1027 return request.execute();
1028 } catch (CloudifyResponseException e) {
1029 boolean retry = false;
1030 if (retryCodes != null) {
1031 int code = e.getStatus();
1032 logger.debug("Config values RetryDelay: {} RetryCount:{} RetryCodes:{} ResponseCode:{}", retryDelay,
1033 retryCount, retryCodes, code);
1034 for (String rCode : retryCodes.split(",")) {
1036 if (retryCount > 0 && code == Integer.parseInt(rCode)) {
1040 "CloudifyResponseException ResponseCode:{} request:{} Retry indicated. Attempts remaining:{}",
1041 code, requestType, retryCount);
1044 } catch (NumberFormatException e1) {
1045 logger.error("{} No retries. Exception in parsing retry code in config:{} {}",
1046 MessageEnum.RA_CONFIG_EXC, rCode, ErrorCode.SchemaError.getValue());
1052 sleep(retryDelay * 1000L);
1054 throw e; // exceeded retryCount or code is not retryable
1055 } catch (CloudifyConnectException e) {
1056 // Connection to Cloudify failed
1057 if (retryCount > 0) {
1059 logger.debug(" request: {} Retry indicated. Attempts remaining:{}", requestType, retryCount);
1060 sleep(retryDelay * 1000L);
1069 * Convert an Exception on a Cloudify call to an MsoCloudifyException. This method supports
1070 * CloudifyResponseException and CloudifyConnectException.
1072 protected MsoException cloudifyExceptionToMsoException(CloudifyBaseException e, String context) {
1073 MsoException me = null;
1075 if (e instanceof CloudifyResponseException) {
1076 CloudifyResponseException re = (CloudifyResponseException) e;
1079 // Failed Cloudify calls return an error entity body.
1080 CloudifyError error = re.getResponse().getErrorEntity(CloudifyError.class);
1081 logger.error("{} {} {} Exception - Cloudify Error on {}: {}", MessageEnum.RA_CONNECTION_EXCEPTION,
1082 CLOUDIFY, ErrorCode.DataError.getValue(), context, error.getErrorCode());
1083 String fullError = error.getErrorCode() + ": " + error.getMessage();
1084 logger.debug(fullError);
1085 me = new MsoCloudifyException(re.getStatus(), re.getMessage(), fullError);
1086 } catch (Exception e2) {
1087 // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
1088 logger.error("{} {} {} Exception - HTTP Error on {}: {}, {} ", MessageEnum.RA_CONNECTION_EXCEPTION,
1089 CLOUDIFY, ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
1090 me = new MsoCloudifyException(re.getStatus(), re.getMessage(), "");
1093 // Add the context of the error
1094 me.addContext(context);
1096 // Generate an alarm for 5XX and higher errors.
1097 if (re.getStatus() >= 500) {
1100 } else if (e instanceof CloudifyConnectException) {
1101 CloudifyConnectException ce = (CloudifyConnectException) e;
1103 me = new MsoIOException(ce.getMessage());
1104 me.addContext(context);
1106 // Generate an alarm for all connection errors.
1108 logger.error("{} {} {} Cloudify connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY,
1109 ErrorCode.DataError.getValue(), context, e);
1117 /*******************************************************************************
1119 * Methods (and associated utilities) to implement the VduPlugin interface
1121 *******************************************************************************/
1124 * VduPlugin interface for instantiate function.
1126 * This one is a bit more complex, in that it will first upload the blueprint if needed, then create the Cloudify
1127 * deployment and execute the install workflow.
1129 * This implementation also merges any parameters defined in the ENV file with the other other input parameters for
1130 * any undefined parameters). The basic MsoCloudifyUtils separates blueprint management from deploument actions, but
1131 * the VduPlugin does not declare blueprint management operations.
1134 public VduInstance instantiateVdu(CloudInfo cloudInfo, String instanceName, Map<String, Object> inputs,
1135 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
1136 String cloudSiteId = cloudInfo.getCloudSiteId();
1137 String tenantId = cloudInfo.getTenantId();
1139 // Translate the VDU ModelInformation structure to that which is needed for
1140 // creating and uploading a blueprint. Use the model customization UUID as
1141 // the blueprint identifier.
1143 String blueprintId = vduModel.getModelCustomizationUUID();
1147 if (!isBlueprintLoaded(cloudSiteId, blueprintId)) {
1148 logger.debug("Blueprint {} is not loaded. Will upload it now.", blueprintId);
1150 // Prepare the blueprint inputs. Need the set of blueprint templates and files,
1151 // plus the main blueprint name.
1152 Map<String, byte[]> blueprintFiles = new HashMap<>();
1153 String mainTemplate = "";
1155 // Add all of the blueprint artifacts from the VDU model
1156 List<VduArtifact> vduArtifacts = vduModel.getArtifacts();
1157 for (VduArtifact vduArtifact : vduArtifacts) {
1158 // Add all artifacts to the blueprint, with one exception.
1159 // ENVIRONMENT files will be processed later as additional parameters.
1161 ArtifactType artifactType = vduArtifact.getType();
1162 if (artifactType != ArtifactType.ENVIRONMENT) {
1163 blueprintFiles.put(vduArtifact.getName(), vduArtifact.getContent());
1165 if (artifactType == ArtifactType.MAIN_TEMPLATE) {
1166 mainTemplate = vduArtifact.getName();
1171 // Upload the blueprint package
1172 uploadBlueprint(cloudSiteId, blueprintId, mainTemplate, blueprintFiles, false);
1174 } catch (Exception e) {
1175 throw new VduException("CloudifyUtils (instantiateVDU): blueprint Exception", e);
1179 // Next, create and install a new deployment based on the blueprint.
1180 // For Cloudify, the deploymentId is specified by the client. Just use the instance name
1184 // Query the Cloudify Deployment object and populate a VduInstance
1185 DeploymentInfo deployment =
1186 createAndInstallDeployment(cloudSiteId, tenantId, instanceName, blueprintId, inputs, true, // (poll
1189 vduModel.getTimeoutMinutes(), rollbackOnFailure);
1191 return deploymentInfoToVduInstance(deployment);
1192 } catch (Exception e) {
1193 throw new VduException("CloudifyUtils (instantiateVDU): Create-and-install-deployment Exception", e);
1199 * VduPlugin interface for query function.
1202 public VduInstance queryVdu(CloudInfo cloudInfo, String instanceId) throws VduException {
1203 String cloudSiteId = cloudInfo.getCloudSiteId();
1204 String tenantId = cloudInfo.getTenantId();
1207 // Query the Cloudify Deployment object and populate a VduInstance
1208 DeploymentInfo deployment = queryDeployment(cloudSiteId, tenantId, instanceId);
1210 return deploymentInfoToVduInstance(deployment);
1211 } catch (Exception e) {
1212 throw new VduException("Query VDU Exception", e);
1218 * VduPlugin interface for delete function.
1221 public VduInstance deleteVdu(CloudInfo cloudInfo, String instanceId, int timeoutMinutes) throws VduException {
1222 String cloudSiteId = cloudInfo.getCloudSiteId();
1223 String tenantId = cloudInfo.getTenantId();
1226 // Uninstall and delete the Cloudify Deployment
1227 DeploymentInfo deployment = uninstallAndDeleteDeployment(cloudSiteId, tenantId, instanceId, timeoutMinutes);
1229 // Populate a VduInstance based on the deleted Cloudify Deployment object
1230 return deploymentInfoToVduInstance(deployment);
1231 } catch (Exception e) {
1232 throw new VduException("Delete VDU Exception", e);
1238 * VduPlugin interface for update function.
1240 * Update is currently not supported in the MsoCloudifyUtils implementation. Just return a VduException.
1244 public VduInstance updateVdu(CloudInfo cloudInfo, String instanceId, Map<String, Object> inputs,
1245 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
1246 throw new VduException("CloudifyUtils: updateVDU interface not supported");
1251 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1253 protected VduInstance deploymentInfoToVduInstance(DeploymentInfo deployment) {
1254 VduInstance vduInstance = new VduInstance();
1256 // only one ID in Cloudify, use for both VDU name and ID
1257 vduInstance.setVduInstanceId(deployment.getId());
1258 vduInstance.setVduInstanceName(deployment.getId());
1260 // Copy inputs and outputs
1261 vduInstance.setInputs(deployment.getInputs());
1262 vduInstance.setOutputs(deployment.getOutputs());
1264 // Translate the status elements
1265 vduInstance.setStatus(deploymentStatusToVduStatus(deployment));
1270 protected VduStatus deploymentStatusToVduStatus(DeploymentInfo deployment) {
1271 VduStatus vduStatus = new VduStatus();
1273 // Determine the status based on last action & status
1274 // DeploymentInfo object should be enhanced to report a better status internally.
1275 DeploymentStatus status = deployment.getStatus();
1277 if (status == null) {
1278 vduStatus.setState(VduStateType.UNKNOWN);
1279 } else if (status == DeploymentStatus.NOTFOUND) {
1280 vduStatus.setState(VduStateType.NOTFOUND);
1281 } else if (status == DeploymentStatus.INSTALLED) {
1282 vduStatus.setState(VduStateType.INSTANTIATED);
1283 } else if (status == DeploymentStatus.CREATED) {
1284 // Deployment exists but is not installed. This shouldn't really happen,
1285 // since create + install or uninstall + delete are always done together.
1286 // But account for it anyway, assuming the operation is still in progress.
1287 String lastAction = deployment.getLastAction();
1288 if (lastAction == null)
1289 vduStatus.setState(VduStateType.INSTANTIATING);
1291 vduStatus.setState(VduStateType.DELETING);
1292 } else if (status == DeploymentStatus.FAILED) {
1293 vduStatus.setState(VduStateType.FAILED);
1295 vduStatus.setState(VduStateType.UNKNOWN);
1298 vduStatus.setErrorMessage(deployment.getErrorMessage());
1299 vduStatus.setLastAction(new PluginAction(deployment.getLastAction(), deployment.getActionStatus(),
1300 deployment.getErrorMessage()));
1306 * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in. Base the values on the CloudSite
1309 protected OpenstackConfig getOpenstackConfig(CloudSite cloudSite, String tenantId) {
1310 OpenstackConfig openstackConfig = new OpenstackConfig();
1311 openstackConfig.setRegion(cloudSite.getRegionId());
1312 openstackConfig.setAuthUrl(cloudSite.getIdentityService().getIdentityUrl());
1313 openstackConfig.setUsername(cloudSite.getIdentityService().getMsoId());
1315 .setPassword(CryptoUtils.decryptCloudConfigPassword(cloudSite.getIdentityService().getMsoPass()));
1316 openstackConfig.setTenantName(tenantId);
1317 return openstackConfig;
1321 * Return an Azure object as expected by Cloudify Azure Plug-in. Base the values on the CloudSite definition.
1323 protected AzureConfig getAzureConfig(CloudSite cloudSite, String tenantId) {
1324 AzureConfig azureConfig = new AzureConfig();
1325 // TODO: Use adminTenant for now, instead of adding another element
1326 azureConfig.setSubscriptionId(cloudSite.getIdentityService().getAdminTenant());
1327 azureConfig.setTenantId(tenantId);
1328 azureConfig.setClientId(cloudSite.getIdentityService().getMsoId());
1329 azureConfig.setClientSecret(cloudSite.getIdentityService().getMsoPass());
1333 private void sleep(long time) {
1336 } catch (InterruptedException e) {
1337 logger.debug("Thread interrupted while sleeping!", e);
1338 Thread.currentThread().interrupt();