2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
22 package org.openecomp.mso.cloudify.utils;
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.HashMap;
30 import java.util.Optional;
31 import java.util.zip.ZipEntry;
32 import java.util.zip.ZipOutputStream;
34 import org.openecomp.mso.cloud.CloudConfig;
35 import org.openecomp.mso.cloud.CloudConfigFactory;
36 import org.openecomp.mso.cloud.CloudSite;
37 import org.openecomp.mso.cloud.CloudifyManager;
38 import org.openecomp.mso.cloudify.base.client.CloudifyBaseException;
39 import org.openecomp.mso.cloudify.base.client.CloudifyClientTokenProvider;
40 import org.openecomp.mso.cloudify.base.client.CloudifyConnectException;
41 import org.openecomp.mso.cloudify.base.client.CloudifyRequest;
42 import org.openecomp.mso.cloudify.base.client.CloudifyResponseException;
43 import org.openecomp.mso.cloudify.beans.DeploymentInfo;
44 import org.openecomp.mso.cloudify.beans.DeploymentStatus;
45 import org.openecomp.mso.cloudify.exceptions.MsoCloudifyException;
46 import org.openecomp.mso.cloudify.exceptions.MsoCloudifyManagerNotFound;
47 import org.openecomp.mso.cloudify.exceptions.MsoDeploymentAlreadyExists;
48 import org.openecomp.mso.cloudify.v3.client.BlueprintsResource.GetBlueprint;
49 import org.openecomp.mso.cloudify.v3.client.BlueprintsResource.UploadBlueprint;
50 import org.openecomp.mso.cloudify.v3.client.Cloudify;
51 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.CreateDeployment;
52 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.DeleteDeployment;
53 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.GetDeployment;
54 import org.openecomp.mso.cloudify.v3.client.DeploymentsResource.GetDeploymentOutputs;
55 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.CancelExecution;
56 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.GetExecution;
57 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.ListExecutions;
58 import org.openecomp.mso.cloudify.v3.client.ExecutionsResource.StartExecution;
59 import org.openecomp.mso.cloudify.v3.model.Blueprint;
60 import org.openecomp.mso.cloudify.v3.model.CancelExecutionParams;
61 import org.openecomp.mso.cloudify.v3.model.CloudifyError;
62 import org.openecomp.mso.cloudify.v3.model.CreateDeploymentParams;
63 import org.openecomp.mso.cloudify.v3.model.Deployment;
64 import org.openecomp.mso.cloudify.v3.model.DeploymentOutputs;
65 import org.openecomp.mso.cloudify.v3.model.Execution;
66 import org.openecomp.mso.cloudify.v3.model.Executions;
67 import org.openecomp.mso.cloudify.v3.model.OpenstackConfig;
68 import org.openecomp.mso.cloudify.v3.model.StartExecutionParams;
69 import org.openecomp.mso.db.catalog.beans.HeatTemplateParam;
70 import org.openecomp.mso.logger.MessageEnum;
71 import org.openecomp.mso.logger.MsoAlarmLogger;
72 import org.openecomp.mso.logger.MsoLogger;
73 import org.openecomp.mso.openstack.exceptions.MsoAdapterException;
74 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
75 import org.openecomp.mso.openstack.exceptions.MsoException;
76 import org.openecomp.mso.openstack.exceptions.MsoExceptionCategory;
77 import org.openecomp.mso.openstack.exceptions.MsoIOException;
78 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
79 import org.openecomp.mso.openstack.utils.MsoCommonUtils;
80 import org.openecomp.mso.properties.MsoJavaProperties;
81 import org.openecomp.mso.properties.MsoPropertiesException;
82 import org.openecomp.mso.properties.MsoPropertiesFactory;
84 import com.fasterxml.jackson.core.JsonParseException;
85 import com.fasterxml.jackson.databind.JsonNode;
86 import com.fasterxml.jackson.databind.ObjectMapper;
88 public class MsoCloudifyUtils extends MsoCommonUtils {
90 private MsoPropertiesFactory msoPropertiesFactory;
91 private CloudConfigFactory cloudConfigFactory;
93 private static final String CLOUDIFY_ERROR = "CloudifyError";
95 private static final String CREATE_DEPLOYMENT = "CreateDeployment";
96 private static final String DELETE_DEPLOYMENT = "DeleteDeployment";
98 // Fetch cloud configuration each time (may be cached in CloudConfig class)
99 protected CloudConfig cloudConfig;
101 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
103 protected MsoJavaProperties msoProps = null;
105 // Properties names and variables (with default values)
106 protected String createPollIntervalProp = "ecomp.mso.adapters.heat.create.pollInterval";
107 private String deletePollIntervalProp = "ecomp.mso.adapters.heat.delete.pollInterval";
109 protected int createPollIntervalDefault = 15;
110 private int deletePollIntervalDefault = 15;
112 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
115 * This constructor MUST be used ONLY in the JUNIT tests, not for real code.
116 * The MsoPropertiesFactory will be added by EJB injection.
118 * @param msoPropID ID of the mso pro config as defined in web.xml
119 * @param msoPropFactory The mso properties factory instanciated by EJB injection
120 * @param cloudConfFactory the Cloud Config instantiated by EJB injection
122 public MsoCloudifyUtils (String msoPropID, MsoPropertiesFactory msoPropFactory, CloudConfigFactory cloudConfFactory) {
123 msoPropertiesFactory = msoPropFactory;
124 cloudConfigFactory = cloudConfFactory;
125 // Dynamically get properties each time (in case reloaded).
128 msoProps = msoPropertiesFactory.getMsoJavaProperties (msoPropID);
129 } catch (MsoPropertiesException e) {
130 LOGGER.error (MessageEnum.LOAD_PROPERTIES_FAIL, "Unknown. Mso Properties ID not found in cache: " + msoPropID, "", "", MsoLogger.ErrorCode.DataError, "Exception - Mso Properties ID not found in cache", e);
132 cloudConfig = cloudConfigFactory.getCloudConfig ();
133 LOGGER.debug("MsoCloudifyUtils:" + msoPropID);
139 * Create a new Deployment from a specified blueprint, and install it in the specified
140 * cloud location and tenant. The blueprint identifier and parameter map are passed in
141 * as arguments, along with the cloud access credentials. The blueprint should have been
142 * previously uploaded to Cloudify.
144 * It is expected that parameters have been validated and contain at minimum the required
145 * parameters for the given template with no extra (undefined) parameters..
147 * The deployment ID supplied by the caller must be unique in the scope of the Cloudify
148 * tenant (not the Openstack tenant). However, it should also be globally unique, as it
149 * will be the identifier for the resource going forward in Inventory. This latter is
150 * managed by the higher levels invoking this function.
152 * This function executes the "install" workflow on the newly created workflow. Cloudify
153 * will be polled for completion unless the client requests otherwise.
155 * An error will be thrown if the requested Deployment already exists in the specified
158 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
159 * @param tenantId The Openstack ID of the tenant in which to create the Stack
160 * @param deploymentId The identifier (name) of the deployment to create
161 * @param blueprintId The blueprint from which to create the deployment.
162 * @param inputs A map of key/value inputs
163 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
164 * @param timeoutMinutes Timeout after which the "install" will be cancelled
165 * @param environment An optional yaml-format string to specify environmental parameters
166 * @param backout Flag to delete deployment on install Failure - defaulted to True
167 * @return A DeploymentInfo object
168 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
169 * @throws MsoIOException Thrown on Cloudify connection errors.
172 public DeploymentInfo createAndInstallDeployment (String cloudSiteId,
176 Map <String, ? extends Object> inputs,
177 boolean pollForCompletion,
179 boolean backout) throws MsoException
181 // Obtain the cloud site information where we will create the stack
182 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
183 if (!cloudSite.isPresent()) {
184 throw new MsoCloudSiteNotFound (cloudSiteId);
187 Cloudify cloudify = getCloudifyClient (cloudSite.get());
189 // Create the Cloudify OpenstackConfig with the credentials
190 OpenstackConfig openstackConfig = getOpenstackConfig (cloudSite.get(), tenantId);
192 LOGGER.debug ("Ready to Create Deployment (" + deploymentId + ") with input params: " + inputs);
194 // Build up the inputs, including:
195 // - from provided "environment" file
196 // - passed in by caller
197 // - special input for Openstack Credentials
198 Map<String,Object> expandedInputs = new HashMap<String,Object> (inputs);
199 expandedInputs.put("openstack_config", openstackConfig);
201 // Build up the parameters to create a new deployment
202 CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
203 deploymentParams.setBlueprintId(blueprintId);
204 deploymentParams.setInputs((Map<String,Object>)expandedInputs);
206 Deployment deployment = null;
208 CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
209 LOGGER.debug (createDeploymentRequest.toString());
211 deployment = executeAndRecordCloudifyRequest (createDeploymentRequest);
213 catch (CloudifyResponseException e) {
214 // Since this came on the 'Create Deployment' command, nothing was changed
215 // in the cloud. Return the error as an exception.
216 if (e.getStatus () == 409) {
217 // Deployment already exists. Return a specific error for this case
218 MsoException me = new MsoDeploymentAlreadyExists (deploymentId, cloudSiteId);
219 me.addContext (CREATE_DEPLOYMENT);
222 // Convert the CloudifyResponseException to an MsoException
223 LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
224 MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
225 me.setCategory (MsoExceptionCategory.OPENSTACK);
228 } catch (CloudifyConnectException e) {
229 // Error connecting to Cloudify instance. Convert to an MsoException
230 MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
232 } catch (RuntimeException e) {
234 throw runtimeExceptionToMsoException (e, CREATE_DEPLOYMENT);
238 * It can take some time for Cloudify to be ready to execute a workflow
239 * on the deployment. Sleep 10 seconds.
243 } catch (InterruptedException e) {}
246 * Next execute the "install" workflow.
247 * Note - this assumes there are no additional parameters required for the workflow.
249 int createPollInterval = msoProps.getIntProperty (createPollIntervalProp, createPollIntervalDefault);
250 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
252 Execution installWorkflow = null;
255 installWorkflow = executeWorkflow (cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout, createPollInterval);
257 if (installWorkflow.getStatus().equals("terminated")) {
259 // Create and return a DeploymentInfo structure. Include the Runtime outputs
260 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
261 DeploymentInfo deploymentInfo = new DeploymentInfo (deployment, outputs, installWorkflow);
262 return deploymentInfo;
265 // The workflow completed with errors. Must try to back it out.
268 LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
271 // Poll on delete if we rollback - use same values for now
272 int deletePollInterval = createPollInterval;
273 int deletePollTimeout = pollTimeout;
276 // Run the uninstall to undo the install
277 Execution uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
279 if (uninstallWorkflow.getStatus().equals("terminated"))
281 // The uninstall completed. Delete the deployment itself
282 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
283 executeAndRecordCloudifyRequest (deleteRequest);
286 // Didn't uninstall successfully. Log this error
287 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Deployment: Cloudify error rolling back deployment install: " + installWorkflow.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Cloudify error rolling back deployment installation");
290 catch (Exception e) {
291 // Catch-all for backout errors trying to uninstall/delete
292 // Log this error, and return the original exception
293 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back deployment install: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back deployment installation");
297 MsoCloudifyException me = new MsoCloudifyException (0, "Workflow Execution Failed", installWorkflow.getError());
298 me.addContext (CREATE_DEPLOYMENT);
299 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
303 catch (MsoException me) {
304 // Install failed. Unless requested otherwise, back out the deployment
308 LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
311 // Poll on delete if we rollback - use same values for now
312 int deletePollInterval = createPollInterval;
313 int deletePollTimeout = pollTimeout;
316 // Run the uninstall to undo the install.
317 // Always try to run it, as it should be idempotent
318 executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
320 // Delete the deployment itself
321 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
322 executeAndRecordCloudifyRequest (deleteRequest);
324 catch (Exception e) {
325 // Catch-all for backout errors trying to uninstall/delete
326 // Log this error, and return the original exception
327 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back deployment install: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back deployment installation");
332 // Propagate the original exception from Stack Query.
333 me.addContext (CREATE_DEPLOYMENT);
334 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
341 * Get the runtime Outputs of a deployment.
342 * Return the Map of tag/value outputs.
344 private DeploymentOutputs getDeploymentOutputs (Cloudify cloudify, String deploymentId)
347 // Build and send the Cloudify request
348 DeploymentOutputs deploymentOutputs = null;
350 GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
351 LOGGER.debug (queryDeploymentOutputs.toString());
353 deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs, msoProps);
355 catch (CloudifyConnectException ce) {
356 // Couldn't connect to Cloudify
357 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeploymentOutputs: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeploymentOutputs: Cloudify connection failure");
358 throw new MsoIOException (ce.getMessage(), ce);
360 catch (CloudifyResponseException re) {
361 if (re.getStatus () == 404) {
365 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
367 catch (Exception e) {
369 throw new MsoAdapterException (e.getMessage(), e);
372 return deploymentOutputs;
376 * Execute a workflow on a deployment. Handle polling for completion with timeout.
377 * Return the final Execution object with status.
378 * Throw an exception on Errors.
379 * Question - how does the client know whether rollback needs to be done?
381 private Execution executeWorkflow (Cloudify cloudify, String deploymentId, String workflowId, Map<String,Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
382 throws MsoCloudifyException
384 LOGGER.debug("Executing '" + workflowId + "' workflow on deployment '" + deploymentId + "'");
386 StartExecutionParams executeParams = new StartExecutionParams();
387 executeParams.setWorkflowId(workflowId);
388 executeParams.setDeploymentId(deploymentId);
389 executeParams.setParameters(workflowParams);
391 Execution execution = null;
392 String executionId = null;
393 String command = "start";
394 Exception savedException = null;
397 StartExecution executionRequest = cloudify.executions().start(executeParams);
398 LOGGER.debug (executionRequest.toString());
399 execution = executeAndRecordCloudifyRequest (executionRequest);
400 executionId = execution.getId();
402 if (!pollForCompletion) {
403 // Client did not request polling, so just return the Execution object
407 // Enter polling loop
408 boolean timedOut = false;
409 int pollTimeout = timeout;
411 String status = execution.getStatus();
413 // Create a reusable cloudify query request
414 GetExecution queryExecution = cloudify.executions().byId(executionId);
417 while (!timedOut && !(status.equals("terminated") || status.equals("failed") || status.equals("cancelled")))
419 // workflow is still running; check for timeout
420 if (pollTimeout <= 0) {
421 LOGGER.debug ("workflow " + execution.getWorkflowId() + " timed out on deployment " + execution.getDeploymentId());
427 Thread.sleep (pollInterval * 1000L);
428 } catch (InterruptedException e) {}
430 pollTimeout -= pollInterval;
431 LOGGER.debug("pollTimeout remaining: " + pollTimeout);
433 execution = queryExecution.execute();
434 status = execution.getStatus();
437 // Broke the loop. Check again for a terminal state
438 if (status.equals("terminated")){
440 LOGGER.debug ("Workflow '" + workflowId + "' completed successfully on deployment '" + deploymentId + "'");
443 else if (status.equals("failed")){
444 // Workflow failed. Log it and return the execution object (don't throw exception here)
445 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow failure: " + execution.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Failed: " + execution.getError());
448 else if (status.equals("cancelled")){
449 // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the execution object (don't throw exception here)
450 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow cancelled. Deployment is in an indeterminate state", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow cancelled: " + workflowId);
454 // Can only get here after a timeout
455 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow timeout", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Timed Out");
458 catch (CloudifyConnectException ce) {
459 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify connection failure");
462 catch (CloudifyResponseException re) {
463 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify response error: " + re, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify error" + re.getMessage());
466 catch (RuntimeException e) {
468 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
472 // Get to this point ONLY on an error or timeout
473 // The cloudify execution is still running (we've not received a terminal status),
474 // so try to Cancel it.
475 CancelExecutionParams cancelParams = new CancelExecutionParams();
476 cancelParams.setAction("cancel");
477 // TODO: Use force_cancel?
479 Execution cancelExecution = null;
482 CancelExecution cancelRequest = cloudify.executions().cancel(executionId, cancelParams);
483 LOGGER.debug (cancelRequest.toString());
484 cancelExecution = cancelRequest.execute();
486 // Enter polling loop
487 boolean timedOut = false;
488 int cancelTimeout = timeout; // TODO: For now, just use same timeout
490 String status = cancelExecution.getStatus();
492 // Poll for completion. Create a reusable cloudify query request
493 GetExecution queryExecution = cloudify.executions().byId(executionId);
495 while (!timedOut && !status.equals("cancelled"))
497 // workflow is still running; check for timeout
498 if (cancelTimeout <= 0) {
499 LOGGER.debug ("Cancel timeout for workflow " + workflowId + " on deployment " + deploymentId);
505 Thread.sleep (pollInterval * 1000L);
506 } catch (InterruptedException e) {}
508 cancelTimeout -= pollInterval;
509 LOGGER.debug("pollTimeout remaining: " + cancelTimeout);
511 execution = queryExecution.execute();
512 status = execution.getStatus();
515 // Broke the loop. Check again for a terminal state
516 if (status.equals("cancelled")){
517 // Finished cancelling. Return the original exception
518 LOGGER.debug ("Cancel workflow " + workflowId + " completed on deployment " + deploymentId);
519 throw new MsoCloudifyException (-1, "", "", savedException);
522 // Can only get here after a timeout
523 LOGGER.debug ("Cancel workflow " + workflowId + " timeout out on deployment " + deploymentId);
524 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
525 exception.setPendingWorkflow(true);
529 catch (Exception e) {
530 // Catch-all. Log the message and throw the original exception
531 // LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
532 LOGGER.debug ("Cancel workflow " + workflowId + " failed for deployment " + deploymentId + ": " + e.getMessage());
533 MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
534 exception.setPendingWorkflow(true);
542 * Query for a Cloudify Deployment (by Name). This call will always return a
543 * DeploymentInfo object. If the deployment does not exist, an "empty" DeploymentInfo will be
544 * returned - containing only the deployment ID and a special status of NOTFOUND.
546 * @param tenantId The Openstack ID of the tenant in which to query
547 * @param cloudSiteId The cloud identifier (may be a region) in which to query
548 * @param stackName The name of the stack to query (may be simple or canonical)
549 * @return A StackInfo object
550 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
552 public DeploymentInfo queryDeployment (String cloudSiteId, String tenantId, String deploymentId)
555 LOGGER.debug ("Query Cloudify Deployment: " + deploymentId + " in tenant " + tenantId);
557 // Obtain the cloud site information where we will create the stack
558 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
559 if (!cloudSite.isPresent()) {
560 throw new MsoCloudSiteNotFound (cloudSiteId);
563 Cloudify cloudify = getCloudifyClient (cloudSite.get());
565 // Build and send the Cloudify request
566 Deployment deployment = null;
567 DeploymentOutputs outputs = null;
569 GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
570 LOGGER.debug (queryDeployment.toString());
572 // deployment = queryDeployment.execute();
573 deployment = executeAndRecordCloudifyRequest(queryDeployment, msoProps);
575 outputs = getDeploymentOutputs (cloudify, deploymentId);
577 // Next look for the latest execution
578 ListExecutions listExecutions = cloudify.executions().listFiltered ("deployment_id=" + deploymentId, "-created_at");
579 Executions executions = listExecutions.execute();
581 // If no executions, does this give NOT_FOUND or empty set?
582 if (executions.getItems().isEmpty()) {
583 return new DeploymentInfo (deployment);
586 return new DeploymentInfo (deployment, outputs, executions.getItems().get(0));
589 catch (CloudifyConnectException ce) {
590 // Couldn't connect to Cloudify
591 LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeployment: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeployment: Cloudify connection failure");
592 throw new MsoIOException (ce.getMessage(), ce);
594 catch (CloudifyResponseException re) {
595 if (re.getStatus () == 404) {
596 // Got a NOT FOUND error. React differently based on deployment vs. execution
597 if (deployment != null) {
598 // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
599 return new DeploymentInfo (deployment, outputs, null);
601 // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
602 return new DeploymentInfo (deploymentId);
605 throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
607 catch (Exception e) {
609 throw new MsoAdapterException (e.getMessage(), e);
615 * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be
616 * considered a successful deletion. The return value is a DeploymentInfo object which
617 * contains the last deployment status.
619 * There is no rollback from a successful deletion. A deletion failure will
620 * also result in an undefined deployment state - the components may or may not have been
621 * all or partially deleted, so the resulting deployment must be considered invalid.
623 * @param tenantId The Openstack ID of the tenant in which to perform the delete
624 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
625 * @param stackName The name/id of the stack to delete. May be simple or canonical
626 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
627 * @return A StackInfo object
628 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
629 * @throws MsoCloudSiteNotFound
631 public DeploymentInfo uninstallAndDeleteDeployment (String cloudSiteId,
634 int timeoutMinutes) throws MsoException
636 // Obtain the cloud site information where we will create the stack
637 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
638 if (!cloudSite.isPresent()) {
639 throw new MsoCloudSiteNotFound (cloudSiteId);
642 Cloudify cloudify = getCloudifyClient (cloudSite.get());
644 LOGGER.debug ("Ready to Uninstall/Delete Deployment (" + deploymentId + ")");
646 // Query first to save the trouble if deployment not found
647 Deployment deployment = null;
649 GetDeployment queryDeploymentRequest = cloudify.deployments().byId(deploymentId);
650 LOGGER.debug (queryDeploymentRequest.toString());
652 deployment = executeAndRecordCloudifyRequest (queryDeploymentRequest);
654 catch (CloudifyResponseException e) {
655 // Since this came on the 'Create Deployment' command, nothing was changed
656 // in the cloud. Return the error as an exception.
657 if (e.getStatus () == 404) {
658 // Deployment doesn't exist. Return a "NOTFOUND" DeploymentInfo object
659 // TODO: Should return NULL?
660 LOGGER.debug("Deployment requested for deletion does not exist: " + deploymentId);
661 return new DeploymentInfo (deploymentId, DeploymentStatus.NOTFOUND);
663 // Convert the CloudifyResponseException to an MsoOpenstackException
664 LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
665 MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
666 me.setCategory (MsoExceptionCategory.INTERNAL);
669 } catch (CloudifyConnectException e) {
670 // Error connecting to Cloudify instance. Convert to an MsoException
671 MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
673 } catch (RuntimeException e) {
675 throw runtimeExceptionToMsoException (e, DELETE_DEPLOYMENT);
679 * Query the outputs before deleting so they can be returned as well
681 DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
684 * Next execute the "uninstall" workflow.
685 * Note - this assumes there are no additional parameters required for the workflow.
687 // TODO: No deletePollInterval that I'm aware of. Use the create interval
688 int deletePollInterval = msoProps.getIntProperty (deletePollIntervalProp, deletePollIntervalDefault);
689 int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
691 Execution uninstallWorkflow = null;
694 uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, true, pollTimeout, deletePollInterval);
696 if (uninstallWorkflow.getStatus().equals("terminated")) {
697 // Successful uninstall.
698 LOGGER.debug("Uninstall successful for deployment " + deploymentId);
701 // The uninstall workflow completed with an error. Must fail the request, but will
702 // leave the deployment in an indeterminate state, as cloud resources may still exist.
703 MsoCloudifyException me = new MsoCloudifyException (0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
704 me.addContext (DELETE_DEPLOYMENT);
705 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
709 catch (MsoException me) {
710 // Uninstall workflow has failed.
711 // Must fail the deletion... may leave the deployment in an inconclusive state
712 me.addContext (DELETE_DEPLOYMENT);
713 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
717 // At this point, the deployment has been successfully uninstalled.
718 // Next step is to delete the deployment itself
720 DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
721 LOGGER.debug(deleteRequest.toString());
723 // The delete request returns the deleted deployment
724 deployment = deleteRequest.execute();
727 catch (CloudifyConnectException ce) {
728 // Failed to delete. Must fail the request, but will leave the (uninstalled)
729 // deployment in Cloudify DB.
730 MsoCloudifyException me = new MsoCloudifyException (0, "Deployment Delete Failed", ce.getMessage(), ce);
731 me.addContext (DELETE_DEPLOYMENT);
732 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
735 catch (CloudifyResponseException re) {
736 // Failed to delete. Must fail the request, but will leave the (uninstalled)
737 // deployment in the Cloudify DB.
738 MsoCloudifyException me = new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getMessage(), re);
739 me.addContext (DELETE_DEPLOYMENT);
740 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
743 catch (Exception e) {
745 MsoAdapterException ae = new MsoAdapterException (e.getMessage(), e);
746 ae.addContext (DELETE_DEPLOYMENT);
747 alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, ae.getContextMessage());
751 // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
752 return new DeploymentInfo (deployment, outputs, uninstallWorkflow);
757 * Check if a blueprint is available for use at a targeted cloud site.
758 * This requires checking the Cloudify Manager which is servicing that
759 * cloud site to see if the specified blueprint has been loaded.
761 * @param cloudSiteId The cloud site where the blueprint is needed
762 * @param blueprintId The ID for the blueprint in Cloudify
764 public boolean isBlueprintLoaded (String cloudSiteId, String blueprintId)
767 // Obtain the cloud site information where we will load the blueprint
768 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
769 if (!cloudSite.isPresent()) {
770 throw new MsoCloudSiteNotFound (cloudSiteId);
773 Cloudify cloudify = getCloudifyClient (cloudSite.get());
775 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
777 Blueprint bp = getRequest.execute();
778 LOGGER.debug("Blueprint exists: " + bp.getId());
781 catch (CloudifyResponseException ce) {
782 if (ce.getStatus() == 404) {
787 } catch (Exception e) {
793 * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site.
794 * The blueprint currently must be structured as a single directory with all
795 * of the required files. One of those files is designated the "main file"
796 * for the blueprint. Files are provided as byte arrays, though expect only
797 * text files will be distributed from ASDC and stored by MSO.
799 * Cloudify requires a single root directory in its blueprint zip files.
800 * The requested blueprint ID will also be used as the directory.
801 * All of the files will be added to this directory in the zip file.
803 public void uploadBlueprint (String cloudSiteId,
806 Map<String,byte[]> blueprintFiles,
807 boolean failIfExists)
810 // Obtain the cloud site information where we will load the blueprint
811 Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
812 if (!cloudSite.isPresent()) {
813 throw new MsoCloudSiteNotFound (cloudSiteId);
816 Cloudify cloudify = getCloudifyClient (cloudSite.get());
818 boolean blueprintUploaded = uploadBlueprint (cloudify, blueprintId, mainFileName, blueprintFiles);
820 if (!blueprintUploaded && failIfExists) {
821 throw new MsoAdapterException ("Blueprint already exists");
826 * Common method to load a blueprint. May be called from
828 private boolean uploadBlueprint (Cloudify cloudify, String blueprintId, String mainFileName, Map<String,byte[]> blueprintFiles)
831 // Check if it already exists. If so, return false.
832 GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
834 Blueprint bp = getRequest.execute();
835 LOGGER.debug("Blueprint " + bp.getId() + " already exists.");
838 catch (CloudifyResponseException ce) {
839 if (ce.getStatus() == 404) {
840 // This is the expected result.
841 LOGGER.debug("Verified that Blueprint doesn't exist yet");
845 } catch (Exception e) {
849 // Create a blueprint ZIP file in memory
850 ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
851 ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
854 // Put the root directory
855 String rootDir = blueprintId + ((blueprintId.endsWith("/") ? "" : "/"));
856 zipOut.putNextEntry(new ZipEntry (rootDir));
859 for (String fileName : blueprintFiles.keySet()) {
860 ZipEntry ze = new ZipEntry (rootDir + fileName);
861 zipOut.putNextEntry (ze);
862 zipOut.write (blueprintFiles.get(fileName));
867 catch (IOException e) {
868 // Since we're writing to a byte array, this should never happen
870 LOGGER.debug ("Blueprint zip file size: " + zipBuffer.size());
872 // Ready to upload the blueprint zip
873 InputStream blueprintStream = new ByteArrayInputStream (zipBuffer.toByteArray());
875 UploadBlueprint uploadRequest = cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
876 Blueprint blueprint = uploadRequest.execute();
877 System.out.println("Successfully uploaded blueprint " + blueprint.getId());
879 catch (CloudifyResponseException e) {
880 MsoException me = cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
883 catch (CloudifyConnectException e) {
884 MsoException me = cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
887 catch (RuntimeException e) {
889 MsoException me = runtimeExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
894 blueprintStream.close();
895 } catch (IOException e) {}
903 // ---------------------------------------------------------------
904 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
907 * Get a Cloudify client for the specified cloud site.
908 * Everything that is required can be found in the Cloud Config.
911 * @return a Cloudify object
913 public Cloudify getCloudifyClient (CloudSite cloudSite) throws MsoException
915 CloudifyManager cloudifyConfig = cloudSite.getCloudifyManager();
916 if (cloudifyConfig == null) {
917 throw new MsoCloudifyManagerNotFound (cloudSite.getId());
920 // Get a Cloudify client
921 // Set a Token Provider to fetch tokens from Cloudify itself.
922 String cloudifyUrl = cloudifyConfig.getCloudifyUrl();
923 Cloudify cloudify = new Cloudify (cloudifyUrl);
924 cloudify.setTokenProvider(new CloudifyClientTokenProvider(cloudifyUrl, cloudifyConfig.getUsername(), cloudifyConfig.getPassword()));
931 * Query for a Cloudify Deployment. This function is needed in several places, so
932 * a common method is useful. This method takes an authenticated CloudifyClient
933 * (which internally identifies the cloud & tenant to search), and returns
934 * a Deployment object if found, Null if not found, or an MsoCloudifyException
935 * if the Cloudify API call fails.
937 * @param cloudifyClient an authenticated Cloudify client
939 * @param deploymentId the deployment to query
941 * @return a Deployment object or null if the requested deployment doesn't exist.
943 * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception
945 protected Deployment queryDeployment (Cloudify cloudify, String deploymentId) throws MsoException {
946 if (deploymentId == null) {
950 GetDeployment request = cloudify.deployments().byId (deploymentId);
951 return executeAndRecordCloudifyRequest (request, msoProps);
952 } catch (CloudifyResponseException e) {
953 if (e.getStatus () == 404) {
954 LOGGER.debug ("queryDeployment - not found: " + deploymentId);
957 // Convert the CloudifyResponseException to an MsoCloudifyException
958 throw cloudifyExceptionToMsoException (e, "QueryDeployment");
960 } catch (CloudifyConnectException e) {
961 // Connection to Openstack failed
962 throw cloudifyExceptionToMsoException (e, "QueryDeployment");
967 public void copyStringOutputsToInputs(Map<String, String> inputs,
968 Map<String, Object> otherStackOutputs, boolean overWrite) {
969 if (inputs == null || otherStackOutputs == null)
971 for (String key : otherStackOutputs.keySet()) {
972 if (!inputs.containsKey(key)) {
973 Object obj = otherStackOutputs.get(key);
974 if (obj instanceof String) {
975 inputs.put(key, (String) otherStackOutputs.get(key));
976 } else if (obj instanceof JsonNode ){
977 // This is a bit of mess - but I think it's the least impacting
978 // let's convert it BACK to a string - then it will get converted back later
980 String str = this.convertNode((JsonNode) obj);
981 inputs.put(key, str);
982 } catch (Exception e) {
983 LOGGER.debug("WARNING: unable to convert JsonNode output value for "+ key);
984 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
986 } else if (obj instanceof java.util.LinkedHashMap) {
987 LOGGER.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
989 String str = JSON_MAPPER.writeValueAsString(obj);
990 inputs.put(key, str);
991 } catch (Exception e) {
992 LOGGER.debug("WARNING: unable to convert LinkedHashMap output value for "+ key);
995 // just try to cast it - could be an integer or some such
997 String str = (String) obj;
998 inputs.put(key, str);
999 } catch (Exception e) {
1000 LOGGER.debug("WARNING: unable to convert output value for "+ key);
1001 //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1010 * Normalize an input value to an Object, based on the target parameter type.
1011 * If the type is not recognized, it will just be returned unchanged (as a string).
1013 public Object convertInputValue (String inputValue, HeatTemplateParam templateParam)
1015 String type = templateParam.getParamType();
1016 LOGGER.debug("Parameter: " + templateParam.getParamName() + " is of type " + type);
1018 if (type.equalsIgnoreCase("number")) {
1020 return Integer.valueOf(inputValue);
1022 catch (Exception e) {
1023 LOGGER.debug("Unable to convert " + inputValue + " to an integer!");
1026 } else if (type.equalsIgnoreCase("json")) {
1028 JsonNode jsonNode = new ObjectMapper().readTree(inputValue);
1031 catch (Exception e) {
1032 LOGGER.debug("Unable to convert " + inputValue + " to a JsonNode!");
1035 } else if (type.equalsIgnoreCase("boolean")) {
1036 return new Boolean(inputValue);
1039 // Nothing else matched. Return the original string
1044 private String convertNode(final JsonNode node) {
1046 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
1047 final String json = JSON_MAPPER.writeValueAsString(obj);
1049 } catch (JsonParseException jpe) {
1050 LOGGER.debug("Error converting json to string " + jpe.getMessage());
1051 } catch (Exception e) {
1052 LOGGER.debug("Error converting json to string " + e.getMessage());
1054 return "[Error converting json to string]";
1059 * Method to execute a Cloudify command and track its execution time.
1060 * For the metrics log, a category of "Cloudify" is used along with a
1061 * sub-category that identifies the specific call (using the real
1062 * cloudify-client classname of the CloudifyRequest<T> parameter).
1065 protected static <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request)
1067 return executeAndRecordCloudifyRequest (request, null);
1069 protected static <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request, MsoJavaProperties msoProps) {
1072 // Get the name and method name of the parent class, which triggered this method
1073 StackTraceElement[] classArr = new Exception ().getStackTrace ();
1074 if (classArr.length >=2) {
1077 limit = classArr.length;
1079 String parentServiceMethodName = classArr[0].getClassName () + "." + classArr[0].getMethodName ();
1080 for (int i = 1; i < limit; i++) {
1081 String className = classArr[i].getClassName ();
1082 if (!className.equals (MsoCommonUtils.class.getName ())) {
1083 parentServiceMethodName = className + "." + classArr[i].getMethodName ();
1089 if (request.getClass ().getEnclosingClass () != null) {
1090 requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
1091 + request.getClass ().getSimpleName ();
1093 requestType = request.getClass ().getSimpleName ();
1096 int retryDelay = retryDelayDefault;
1097 int retryCount = retryCountDefault;
1098 String retryCodes = retryCodesDefault;
1099 if (msoProps != null) //extra check to avoid NPE
1101 retryDelay = msoProps.getIntProperty (retryDelayProp, retryDelayDefault);
1102 retryCount = msoProps.getIntProperty (retryCountProp, retryCountDefault);
1103 retryCodes = msoProps.getProperty (retryCodesProp, retryCodesDefault);
1106 // Run the actual command. All exceptions will be propagated
1110 return request.execute ();
1112 catch (CloudifyResponseException e) {
1113 boolean retry = false;
1114 if (retryCodes != null ) {
1115 int code = e.getStatus();
1116 LOGGER.debug ("Config values RetryDelay:" + retryDelay + " RetryCount:" + retryCount + " RetryCodes:" + retryCodes + " ResponseCode:" + code);
1117 for (String rCode : retryCodes.split (",")) {
1119 if (retryCount > 0 && code == Integer.parseInt (rCode))
1123 LOGGER.debug ("CloudifyResponseException ResponseCode:" + code + " at:" + parentServiceMethodName + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
1126 } catch (NumberFormatException e1) {
1127 LOGGER.error (MessageEnum.RA_CONFIG_EXC, "No retries. Exception in parsing retry code in config:" + rCode, "", "", MsoLogger.ErrorCode.SchemaError, "Exception in parsing retry code in config");
1135 Thread.sleep (retryDelay * 1000L);
1136 } catch (InterruptedException e1) {
1137 LOGGER.debug ("Thread interrupted while sleeping", e1);
1141 throw e; // exceeded retryCount or code is not retryable
1143 catch (CloudifyConnectException e) {
1144 // Connection to Cloudify failed
1148 LOGGER.debug ("CloudifyConnectException at:" + parentServiceMethodName + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
1150 Thread.sleep (retryDelay * 1000L);
1151 } catch (InterruptedException e1) {
1152 LOGGER.debug ("Thread interrupted while sleeping", e1);
1162 * Convert an Exception on a Cloudify call to an MsoCloudifyException.
1163 * This method supports CloudifyResponseException and CloudifyConnectException.
1165 protected MsoException cloudifyExceptionToMsoException (CloudifyBaseException e, String context) {
1166 MsoException me = null;
1168 if (e instanceof CloudifyResponseException) {
1169 CloudifyResponseException re = (CloudifyResponseException) e;
1172 // Failed Cloudify calls return an error entity body.
1173 CloudifyError error = re.getResponse ().getErrorEntity (CloudifyError.class);
1174 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Cloudify", "Cloudify Error on " + context + ": " + error.getErrorCode(), "Cloudify", "", MsoLogger.ErrorCode.DataError, "Exception - Cloudify Error on " + context);
1175 String fullError = error.getErrorCode() + ": " + error.getMessage();
1176 LOGGER.debug(fullError);
1177 me = new MsoCloudifyException (re.getStatus(),
1180 } catch (Exception e2) {
1181 // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
1182 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Cloudify", "HTTP Error on " + context + ": " + re.getStatus() + "," + e.getMessage(), "Cloudify", "", MsoLogger.ErrorCode.DataError, "Exception - HTTP Error on " + context, e2);
1183 me = new MsoCloudifyException (re.getStatus (), re.getMessage (), "");
1186 // Add the context of the error
1187 me.addContext (context);
1189 // Generate an alarm for 5XX and higher errors.
1190 if (re.getStatus () >= 500) {
1191 alarmLogger.sendAlarm ("CloudifyError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
1193 } else if (e instanceof CloudifyConnectException) {
1194 CloudifyConnectException ce = (CloudifyConnectException) e;
1196 me = new MsoIOException (ce.getMessage ());
1197 me.addContext (context);
1199 // Generate an alarm for all connection errors.
1200 alarmLogger.sendAlarm ("CloudifyIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
1201 LOGGER.error(MessageEnum.RA_CONNECTION_EXCEPTION, "Cloudify", "Cloudify connection error on " + context + ": " + e, "Cloudify", "", MsoLogger.ErrorCode.DataError, "Cloudify connection error on " + context);
1208 * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in.
1209 * Base the values on the CloudSite definition.
1211 private OpenstackConfig getOpenstackConfig (CloudSite cloudSite, String tenantId) {
1212 OpenstackConfig openstackConfig = new OpenstackConfig();
1213 openstackConfig.setRegion (cloudSite.getRegionId());
1214 openstackConfig.setAuthUrl (cloudSite.getIdentityService().getIdentityUrl());
1215 openstackConfig.setUsername (cloudSite.getIdentityService().getMsoId());
1216 openstackConfig.setPassword (cloudSite.getIdentityService().getMsoPass());
1217 openstackConfig.setTenantName (tenantId);
1218 return openstackConfig;