2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7 * ================================================================================
8 * Modifications Copyright (c) 2019 Samsung
9 * ================================================================================
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 * ============LICENSE_END=========================================================
24 package org.onap.so.openstack.utils;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.List;
32 import java.util.Map.Entry;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import org.onap.logging.ref.slf4j.ONAPLogConstants;
37 import org.onap.so.adapters.vdu.CloudInfo;
38 import org.onap.so.adapters.vdu.PluginAction;
39 import org.onap.so.adapters.vdu.VduArtifact;
40 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
41 import org.onap.so.adapters.vdu.VduException;
42 import org.onap.so.adapters.vdu.VduInstance;
43 import org.onap.so.adapters.vdu.VduModelInfo;
44 import org.onap.so.adapters.vdu.VduPlugin;
45 import org.onap.so.adapters.vdu.VduStateType;
46 import org.onap.so.adapters.vdu.VduStatus;
47 import org.onap.so.cloud.authentication.KeystoneAuthHolder;
48 import org.onap.so.db.catalog.beans.CloudIdentity;
49 import org.onap.so.db.catalog.beans.CloudSite;
50 import org.onap.so.db.catalog.beans.HeatTemplate;
51 import org.onap.so.db.catalog.beans.HeatTemplateParam;
52 import org.onap.so.db.request.beans.CloudApiRequests;
53 import org.onap.so.db.request.beans.InfraActiveRequests;
54 import org.onap.so.db.request.client.RequestsDbClient;
55 import org.onap.so.logger.ErrorCode;
56 import org.onap.so.logger.MessageEnum;
57 import org.onap.so.openstack.beans.CreateStackRequest;
58 import org.onap.so.openstack.beans.HeatStatus;
59 import org.onap.so.openstack.beans.StackInfo;
60 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
61 import org.onap.so.openstack.exceptions.MsoException;
62 import org.onap.so.openstack.exceptions.MsoOpenstackException;
63 import org.onap.so.openstack.exceptions.MsoStackAlreadyExists;
64 import org.onap.so.openstack.exceptions.MsoTenantNotFound;
65 import org.onap.so.openstack.mappers.StackInfoMapper;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
69 import org.springframework.beans.factory.annotation.Autowired;
70 import org.springframework.context.annotation.Primary;
71 import org.springframework.core.env.Environment;
72 import org.springframework.stereotype.Component;
73 import com.fasterxml.jackson.databind.JsonNode;
74 import com.fasterxml.jackson.databind.ObjectMapper;
75 import com.google.common.base.Strings;
76 import com.woorea.openstack.base.client.OpenStackConnectException;
77 import com.woorea.openstack.base.client.OpenStackRequest;
78 import com.woorea.openstack.base.client.OpenStackResponseException;
79 import com.woorea.openstack.heat.Heat;
80 import com.woorea.openstack.heat.model.CreateStackParam;
81 import com.woorea.openstack.heat.model.Events;
82 import com.woorea.openstack.heat.model.Resources;
83 import com.woorea.openstack.heat.model.Stack;
84 import com.woorea.openstack.heat.model.Stack.Output;
85 import com.woorea.openstack.heat.model.Stacks;
90 public class MsoHeatUtils extends MsoCommonUtils implements VduPlugin {
92 private static final String CREATE_COMPLETE = "CREATE_COMPLETE";
94 private static final String DELETE_COMPLETE = "DELETE_COMPLETE";
96 private static final String DELETE_IN_PROGRESS = "DELETE_IN_PROGRESS";
98 private static final String CREATE_IN_PROGRESS = "CREATE_IN_PROGRESS";
100 private static final String DELETE_STACK = "DeleteStack";
102 protected static final String HEAT_ERROR = "HeatError";
104 protected static final String CREATE_STACK = "CreateStack";
105 public static final String FOUND = "Found: {}";
106 public static final String EXCEPTION_ROLLING_BACK_STACK =
107 "{} Create Stack: Nested exception rolling back stack: {} ";
108 public static final String IN_PROGRESS = "in_progress";
111 private Environment environment;
114 RequestsDbClient requestDBClient;
117 StackStatusHandler statusHandler;
120 NovaClientImpl novaClient;
122 private static final Logger logger = LoggerFactory.getLogger(MsoHeatUtils.class);
124 // Properties names and variables (with default values)
125 protected String createPollIntervalProp = "org.onap.so.adapters.po.pollInterval";
126 private String deletePollIntervalProp = "org.onap.so.adapters.po.pollInterval";
127 private String deletePollTimeoutProp = "org.onap.so.adapters.po.pollTimeout";
128 private String pollingMultiplierProp = "org.onap.so.adapters.po.pollMultiplier";
130 protected static final String CREATE_POLL_INTERVAL_DEFAULT = "15";
131 private static final String DELETE_POLL_INTERVAL_DEFAULT = "15";
132 private static final String POLLING_MULTIPLIER_DEFAULT = "60";
134 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
137 * keep this old method signature here to maintain backwards compatibility. keep others as well. this method does
138 * not include environment, files, or heatFiles
140 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
141 String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion, int timeoutMinutes)
142 throws MsoException {
143 // Just call the new method with the environment & files variable set to null
144 return this.createStack(cloudSiteId, cloudOwner, tenantId, stackName, null, heatTemplate, stackInputs,
145 pollForCompletion, timeoutMinutes, null, null, null, true);
148 // This method has environment, but not files or heatFiles
149 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
150 String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion, int timeoutMinutes,
151 String environment) throws MsoException {
152 // Just call the new method with the files/heatFiles variables set to null
153 return this.createStack(cloudSiteId, cloudOwner, tenantId, stackName, null, heatTemplate, stackInputs,
154 pollForCompletion, timeoutMinutes, environment, null, null, true);
157 // This method has environment and files, but not heatFiles.
158 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
159 String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion, int timeoutMinutes,
160 String environment, Map<String, Object> files) throws MsoException {
161 return this.createStack(cloudSiteId, cloudOwner, tenantId, stackName, null, heatTemplate, stackInputs,
162 pollForCompletion, timeoutMinutes, environment, files, null, true);
165 // This method has environment, files, heatfiles
166 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
167 String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion, int timeoutMinutes,
168 String environment, Map<String, Object> files, Map<String, Object> heatFiles) throws MsoException {
169 return this.createStack(cloudSiteId, cloudOwner, tenantId, stackName, null, heatTemplate, stackInputs,
170 pollForCompletion, timeoutMinutes, environment, files, heatFiles, true);
174 * Create a new Stack in the specified cloud location and tenant. The Heat template and parameter map are passed in
175 * as arguments, along with the cloud access credentials. It is expected that parameters have been validated and
176 * contain at minimum the required parameters for the given template with no extra (undefined) parameters..
178 * The Stack name supplied by the caller must be unique in the scope of this tenant. However, it should also be
179 * globally unique, as it will be the identifier for the resource going forward in Inventory. This latter is managed
180 * by the higher levels invoking this function.
182 * The caller may choose to let this function poll Openstack for completion of the stack creation, or may handle
183 * polling itself via separate calls to query the status. In either case, a StackInfo object will be returned
184 * containing the current status. When polling is enabled, a status of CREATED is expected. When not polling, a
185 * status of BUILDING is expected.
187 * An error will be thrown if the requested Stack already exists in the specified Tenant and Cloud.
189 * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as parameters for
190 * createStack. If environment is non-null, it will be added to the stack. The nested templates and get_file entries
191 * both end up being added to the "files" on the stack. We must combine them before we add them to the stack if
192 * they're both non-null.
194 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
195 * @param cloudOwner the cloud owner of the cloud site in which to create the stack
196 * @param tenantId The Openstack ID of the tenant in which to create the Stack
197 * @param stackName The name of the stack to create
198 * @param vduModel contains information about the vdu model (added for plugin adapter)
199 * @param heatTemplate The Heat template
200 * @param stackInputs A map of key/value inputs
201 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
202 * @param environment An optional yaml-format string to specify environmental parameters
203 * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
205 * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
206 * @param backout Donot delete stack on create Failure - defaulted to True
207 * @return A StackInfo object
208 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
211 @SuppressWarnings("unchecked")
212 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
213 VduModelInfo vduModel, String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion,
214 int timeoutMinutes, String environment, Map<String, Object> files, Map<String, Object> heatFiles,
215 boolean backout) throws MsoException {
217 stripMultiCloudInputs(stackInputs);
218 CreateStackParam createStack =
219 createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
220 Stack currentStack = createStack(createStack, cloudSiteId, tenantId);
221 currentStack.setStackName(stackName);
222 if (pollForCompletion) {
224 processCreateStack(cloudSiteId, tenantId, timeoutMinutes, backout, currentStack, createStack, true);
227 queryHeatStack(currentStack.getStackName() + "/" + currentStack.getId(), cloudSiteId, tenantId);
229 return new StackInfoMapper(currentStack).map();
235 protected void stripMultiCloudInputs(Map<String, ?> stackInputs) {
236 // Take out the multicloud inputs, if present.
237 for (String key : MsoMulticloudUtils.MULTICLOUD_INPUTS) {
238 if (stackInputs.containsKey(key)) {
239 stackInputs.remove(key);
240 if (stackInputs.isEmpty()) {
247 protected Stack createStack(CreateStackParam stack, String cloudSiteId, String tenantId) throws MsoException {
249 OpenStackRequest<Stack> request = getHeatClient(cloudSiteId, tenantId).getStacks().create(stack);
250 saveStackRequest(stack, MDC.get(ONAPLogConstants.MDCs.REQUEST_ID), stack.getStackName());
251 return executeAndRecordOpenstackRequest(request);
252 } catch (OpenStackResponseException e) {
253 if (e.getStatus() == 409) {
254 MsoStackAlreadyExists me = new MsoStackAlreadyExists(stack.getStackName(), tenantId, cloudSiteId);
255 me.addContext(CREATE_STACK);
258 logger.error("ERROR STATUS = {},\n{}\n{}", e.getStatus(), e.getMessage(), e.getLocalizedMessage());
259 throw heatExceptionToMsoException(e, CREATE_STACK);
261 } catch (OpenStackConnectException e) {
262 throw heatExceptionToMsoException(e, CREATE_STACK);
263 } catch (RuntimeException e) {
264 throw runtimeExceptionToMsoException(e, CREATE_STACK);
269 protected Stack processCreateStack(String cloudSiteId, String tenantId, int timeoutMinutes, boolean backout,
270 Stack heatStack, CreateStackParam stackCreate, boolean keyPairCleanUp) throws MsoException {
271 Stack latestStack = null;
273 latestStack = pollStackForStatus(timeoutMinutes, heatStack, CREATE_IN_PROGRESS, cloudSiteId, tenantId);
274 } catch (MsoException me) {
275 logger.error("Exception in Create Stack", me);
277 return postProcessStackCreate(latestStack, backout, timeoutMinutes, keyPairCleanUp, cloudSiteId, tenantId,
281 protected Stack postProcessStackCreate(Stack stack, boolean backout, int timeoutMinutes, boolean cleanUpKeyPair,
282 String cloudSiteId, String tenantId, CreateStackParam stackCreate) throws MsoException {
284 throw new StackCreationException("Unknown Error in Stack Creation");
287 logger.info("Performing post processing backout: {} cleanUpKeyPair: {}, stack {}", backout, cleanUpKeyPair,
289 if (!CREATE_COMPLETE.equals(stack.getStackStatus())) {
290 if (cleanUpKeyPair && !Strings.isNullOrEmpty(stack.getStackStatusReason())
291 && isKeyPairFailure(stack.getStackStatusReason())) {
292 return handleKeyPairConflict(cloudSiteId, tenantId, stackCreate, timeoutMinutes, backout, stack);
295 logger.info("Status is not CREATE_COMPLETE, stack deletion suppressed");
296 throw new StackCreationException("Stack rollback suppressed, stack not deleted");
298 logger.info("Status is not CREATE_COMPLETE, stack deletion will be executed");
299 String errorMessage = "Stack Creation Failed Openstack Status: " + stack.getStackStatus()
300 + " Status Reason: " + stack.getStackStatusReason();
302 Stack deletedStack = handleUnknownCreateStackFailure(stack, timeoutMinutes, cloudSiteId, tenantId);
303 errorMessage = errorMessage + " , Rollback of Stack Creation completed with status: "
304 + deletedStack.getStackStatus() + " Status Reason: " + deletedStack.getStackStatusReason();
305 } catch (StackRollbackException se) {
306 logger.error("Sync Error Deleting Stack during rollback process", se);
307 errorMessage = errorMessage + se.getMessage();
308 } catch (MsoException e) {
309 logger.error("Sync Error Deleting Stack during rollback", e);
312 errorMessage + " , Rollback of Stack Creation failed with sync error: " + e.getMessage();
314 throw new StackCreationException(errorMessage);
322 protected Stack pollStackForStatus(int timeoutMinutes, Stack stack, String stackStatus, String cloudSiteId,
323 String tenantId) throws MsoException {
324 int pollingFrequency =
325 Integer.parseInt(this.environment.getProperty(createPollIntervalProp, CREATE_POLL_INTERVAL_DEFAULT));
326 int pollingMultiplier =
327 Integer.parseInt(this.environment.getProperty(pollingMultiplierProp, POLLING_MULTIPLIER_DEFAULT));
328 int numberOfPollingAttempts = Math.floorDiv((timeoutMinutes * pollingMultiplier), pollingFrequency);
329 Heat heatClient = getHeatClient(cloudSiteId, tenantId);
330 Stack latestStack = null;
332 latestStack = queryHeatStack(heatClient, stack.getStackName() + "/" + stack.getId());
333 statusHandler.updateStackStatus(latestStack);
334 logger.debug("Polling: {} ({})", latestStack.getStackStatus(), latestStack.getStackName());
335 if (stackStatus.equals(latestStack.getStackStatus())) {
336 if (numberOfPollingAttempts <= 0) {
337 logger.error("Polling of stack timed out with Status: {}", latestStack.getStackStatus());
340 sleep(pollingFrequency * 1000L);
341 numberOfPollingAttempts -= 1;
348 protected void saveStackRequest(CreateStackParam request, String requestId, String stackName) {
350 ObjectMapper mapper = new ObjectMapper();
351 InfraActiveRequests foundRequest = requestDBClient.getInfraActiveRequestbyRequestId(requestId);
352 CreateStackRequest createStackRequest = new CreateStackRequest();
353 createStackRequest.setEnvironment(request.getEnvironment());
354 createStackRequest.setParameters(request.getParameters());
355 String stackRequest = mapper.writeValueAsString(createStackRequest);
356 CloudApiRequests cloudReq = new CloudApiRequests();
357 cloudReq.setCloudIdentifier(stackName);
358 cloudReq.setRequestBody(stackRequest);
359 cloudReq.setRequestId(requestId);
360 CloudApiRequests foundCloudReq = foundRequest.getCloudApiRequests().stream()
361 .filter(cloudReqToFind -> stackName.equals(cloudReq.getCloudIdentifier())).findAny().orElse(null);
362 if (foundCloudReq != null) {
363 foundCloudReq.setRequestBody(stackRequest);
365 foundRequest.getCloudApiRequests().add(cloudReq);
367 requestDBClient.updateInfraActiveRequests(foundRequest);
368 } catch (Exception e) {
369 logger.error("Error updating in flight request with Openstack Create Request", e);
373 protected boolean isKeyPairFailure(String errorMessage) {
374 return Pattern.compile(".*Key pair.*already exists.*").matcher(errorMessage).matches();
377 protected Stack handleUnknownCreateStackFailure(Stack stack, int timeoutMinutes, String cloudSiteId,
378 String tenantId) throws MsoException {
379 if (stack != null && !Strings.isNullOrEmpty(stack.getStackName()) && !Strings.isNullOrEmpty(stack.getId())) {
380 OpenStackRequest<Void> request = getHeatClient(cloudSiteId, tenantId).getStacks()
381 .deleteByName(stack.getStackName() + "/" + stack.getId());
382 executeAndRecordOpenstackRequest(request);
383 Stack currentStack = pollStackForStatus(timeoutMinutes, stack, DELETE_IN_PROGRESS, cloudSiteId, tenantId);
384 postProcessStackDelete(currentStack);
387 throw new StackCreationException("Cannot Find Stack Name or Id");
391 protected void postProcessStackDelete(Stack stack) throws MsoException {
392 logger.info("Performing post processing on delete stack {}", stack);
393 if (stack != null && !Strings.isNullOrEmpty(stack.getStackStatus())) {
394 if (!DELETE_COMPLETE.equals(stack.getStackStatus()))
395 throw new StackRollbackException("Stack Deletion completed with status: " + stack.getStackStatus()
396 + " Status Reason: " + stack.getStackStatusReason());
398 throw new StackRollbackException("Cannot Find Stack Name or Id");
402 protected Stack handleKeyPairConflict(String cloudSiteId, String tenantId, CreateStackParam stackCreate,
403 int timeoutMinutes, boolean backout, Stack stack) throws MsoException {
404 logger.info("Keypair conflict found on stack, attempting to clean up");
406 Matcher m = Pattern.compile("'([^']+?)'").matcher(stack.getStackStatusReason());
408 novaClient.deleteKeyPair(cloudSiteId, tenantId, m.group(1));
410 } catch (NovaClientException e) {
411 logger.warn("Could not delete keypair", e);
414 handleUnknownCreateStackFailure(stack, timeoutMinutes, cloudSiteId, tenantId);
415 Stack newStack = createStack(stackCreate, cloudSiteId, tenantId);
416 newStack.setStackName(stackCreate.getStackName());
417 return processCreateStack(cloudSiteId, tenantId, timeoutMinutes, backout, newStack, stackCreate, false);
421 * Query for a single stack (by Name) in a tenant. This call will always return a StackInfo object. If the stack
422 * does not exist, an "empty" StackInfo will be returned - containing only the stack name and a status of NOTFOUND.
424 * @param tenantId The Openstack ID of the tenant in which to query
425 * @param cloudSiteId The cloud identifier (may be a region) in which to query
426 * @param cloudOwner the cloud owner of the cloud site in which to query
427 * @param stackName The name of the stack to query (may be simple or canonical)
428 * @return A StackInfo object
429 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
431 public StackInfo queryStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName)
432 throws MsoException {
433 logger.debug("Query HEAT stack: {} in tenant {}", stackName, tenantId);
434 Heat heatClient = null;
436 heatClient = getHeatClient(cloudSiteId, tenantId);
437 } catch (MsoTenantNotFound e) {
438 // Tenant doesn't exist, so stack doesn't either
439 logger.debug("Tenant with id " + tenantId + "not found.", e);
440 return new StackInfo(stackName, HeatStatus.NOTFOUND);
441 } catch (MsoException me) {
442 // Got an Openstack error. Propagate it
443 logger.error("{} {} Openstack Exception on Token request: ", MessageEnum.RA_CONNECTION_EXCEPTION,
444 ErrorCode.AvailabilityError.getValue(), me);
445 me.addContext("QueryStack");
450 // An MsoException will propagate transparently to the caller.
451 Stack heatStack = queryHeatStack(heatClient, stackName);
453 if (heatStack == null) {
454 // Stack does not exist. Return a StackInfo with status NOTFOUND
455 return new StackInfo(stackName, HeatStatus.NOTFOUND);
458 return new StackInfoMapper(heatStack).map();
462 * Delete a stack (by Name/ID) in a tenant. If the stack is not found, it will be considered a successful deletion.
463 * The return value is a StackInfo object which contains the current stack status.
465 * The client may choose to let the adapter poll Openstack for completion of the stack deletion, or may handle
466 * polling itself via separate query calls. In either case, a StackInfo object will be returned. When polling is
467 * enabled, a final status of NOTFOUND is expected. When not polling, a status of DELETING is expected.
469 * There is no rollback from a successful stack deletion. A deletion failure will also result in an undefined stack
470 * state - the components may or may not have been all or partially deleted, so the resulting stack must be
471 * considered invalid.
473 * @param tenantId The Openstack ID of the tenant in which to perform the delete
474 * @param cloudOwner the cloud owner of the cloud site in which to delete the stack
475 * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
476 * @param stackName The name/id of the stack to delete. May be simple or canonical
477 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
478 * @return A StackInfo object
479 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
480 * @throws MsoCloudSiteNotFound
482 public StackInfo deleteStack(String tenantId, String cloudOwner, String cloudSiteId, String stackName,
483 boolean pollForCompletion) throws MsoException {
484 Heat heatClient = null;
486 heatClient = getHeatClient(cloudSiteId, tenantId);
487 } catch (MsoTenantNotFound e) {
488 logger.debug("Tenant with id " + tenantId + "not found.", e);
489 return new StackInfo(stackName, HeatStatus.NOTFOUND);
490 } catch (MsoException me) {
491 logger.error("{} {} Openstack Exception on Token request: ", MessageEnum.RA_CONNECTION_EXCEPTION,
492 ErrorCode.AvailabilityError.getValue(), me);
493 me.addContext(DELETE_STACK);
497 // OK if stack not found, perform a query first
498 Stack heatStack = queryHeatStack(heatClient, stackName);
499 if (heatStack == null || DELETE_COMPLETE.equals(heatStack.getStackStatus())) {
500 // Not found. Return a StackInfo with status NOTFOUND
501 return new StackInfo(stackName, HeatStatus.NOTFOUND);
504 // Use canonical name "<stack name>/<stack-id>" to delete.
505 // Otherwise, deletion by name returns a 302 redirect.
506 // NOTE: This is specific to the v1 Orchestration API.
507 String canonicalName = heatStack.getStackName() + "/" + heatStack.getId();
510 OpenStackRequest<Void> request = null;
511 if (null != heatClient) {
512 request = heatClient.getStacks().deleteByName(canonicalName);
514 logger.debug("Heat Client is NULL");
516 executeAndRecordOpenstackRequest(request);
517 } catch (OpenStackResponseException e) {
518 if (e.getStatus() == 404) {
519 // Not found. We are OK with this. Return a StackInfo with status NOTFOUND
520 return new StackInfo(stackName, HeatStatus.NOTFOUND);
522 // Convert the OpenStackResponseException to an MsoOpenstackException
523 throw heatExceptionToMsoException(e, DELETE_STACK);
525 } catch (OpenStackConnectException e) {
526 // Error connecting to Openstack instance. Convert to an MsoException
527 throw heatExceptionToMsoException(e, DELETE_STACK);
528 } catch (RuntimeException e) {
530 throw runtimeExceptionToMsoException(e, DELETE_STACK);
533 // Requery the stack for current status.
534 // It will probably still exist with "DELETE_IN_PROGRESS" status.
535 heatStack = queryHeatStack(heatClient, canonicalName);
536 statusHandler.updateStackStatus(heatStack);
537 if (pollForCompletion) {
538 int pollInterval = Integer
539 .parseInt(this.environment.getProperty(deletePollIntervalProp, "" + DELETE_POLL_INTERVAL_DEFAULT));
540 int pollTimeout = Integer
541 .parseInt(this.environment.getProperty(deletePollTimeoutProp, "" + DELETE_POLL_INTERVAL_DEFAULT));
542 statusHandler.updateStackStatus(heatStack);
543 // When querying by canonical name, Openstack returns DELETE_COMPLETE status
544 // instead of "404" (which would result from query by stack name).
545 while (heatStack != null && !DELETE_COMPLETE.equals(heatStack.getStackStatus())) {
546 logger.debug("Stack status: {}", heatStack.getStackStatus());
548 if ("DELETE_FAILED".equals(heatStack.getStackStatus())) {
549 // Throw a 'special case' of MsoOpenstackException to report the Heat status
550 String error = "Stack delete error (" + heatStack.getStackStatus() + "): "
551 + heatStack.getStackStatusReason();
552 MsoOpenstackException me = new MsoOpenstackException(0, "", error);
553 me.addContext(DELETE_STACK);
555 // Alarm this condition, stack deletion failed
561 if (pollTimeout <= 0) {
562 logger.error("{} Cloud site: {} Tenant: {} Stack: {} Stack status: {} {} Delete Stack Timeout",
563 MessageEnum.RA_DELETE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
564 heatStack.getStackStatus(), ErrorCode.AvailabilityError.getValue());
566 // Throw a 'special case' of MsoOpenstackException to report the Heat status
567 MsoOpenstackException me = new MsoOpenstackException(0, "", "Stack Deletion Timeout");
568 me.addContext(DELETE_STACK);
570 // Alarm this condition, stack deletion failed
576 sleep(pollInterval * 1000L);
578 pollTimeout -= pollInterval;
579 logger.debug("pollTimeout remaining: {}", pollTimeout);
581 heatStack = queryHeatStack(heatClient, canonicalName);
584 // The stack is gone when this point is reached
585 return new StackInfo(stackName, HeatStatus.NOTFOUND);
587 // Return the current status (if not polling, the delete may still be in progress)
588 StackInfo stackInfo = new StackInfoMapper(heatStack).map();
589 stackInfo.setName(stackName);
594 * Validate parameters to be passed to Heat template. This method performs three functions: 1. Apply default values
595 * to parameters which have them defined 2. Report any required parameters that are missing. This will generate an
596 * exception in the caller, since stack create/update operations would fail. 3. Report and remove any extraneous
597 * parameters. This will allow clients to pass supersets of parameters and not get errors.
599 * These functions depend on the HeatTemplate definition from the MSO Catalog DB, along with the input parameter
600 * Map. The output is an updated parameter map. If the parameters are invalid for the template, an
601 * IllegalArgumentException is thrown.
603 public Map<String, Object> validateStackParams(Map<String, Object> inputParams, HeatTemplate heatTemplate) {
604 // Check that required parameters have been supplied for this template type
605 StringBuilder missingParams = null;
606 List<String> paramList = new ArrayList<>();
608 // TODO: Enhance DB to support defaults for Heat Template parameters
610 for (HeatTemplateParam parm : heatTemplate.getParameters()) {
611 if (parm.isRequired() && !inputParams.containsKey(parm.getParamName())) {
612 if (missingParams == null) {
613 missingParams = new StringBuilder(parm.getParamName());
615 missingParams.append("," + parm.getParamName());
618 paramList.add(parm.getParamName());
620 if (missingParams != null) {
621 // Problem - missing one or more required parameters
622 String error = "Missing Required inputs for HEAT Template: " + missingParams;
623 logger.error("{} for HEAT Template {} Missing Required inputs for HEAT Template: {}",
624 MessageEnum.RA_MISSING_PARAM, ErrorCode.SchemaError.getValue(), missingParams);
625 throw new IllegalArgumentException(error);
628 // Remove any extraneous parameters (don't throw an error)
629 Map<String, Object> updatedParams = new HashMap<>();
630 List<String> extraParams = new ArrayList<>();
632 for (Entry<String, Object> entry : inputParams.entrySet()) {
633 if (!paramList.contains(entry.getKey())) {
634 // This is not a valid parameter for this template
635 extraParams.add(entry.getKey());
637 updatedParams.put(entry.getKey(), entry.getValue());
641 if (!extraParams.isEmpty()) {
642 logger.warn("{} Heat Stack ({}) extra input params received: {} {}", MessageEnum.RA_GENERAL_WARNING,
643 heatTemplate.getTemplateName(), extraParams, ErrorCode.DataError.getValue());
646 return updatedParams;
651 * Get a Heat client for the Openstack Identity service. This requires a 'member'-level userId + password, which
652 * will be retrieved from properties based on the specified cloud Id. The tenant in which to operate must also be
655 * On successful authentication, the Heat object will be cached for the tenantID + cloudId so that it can be reused
656 * without reauthenticating with Openstack every time.
658 * @return an authenticated Heat object
660 public Heat getHeatClient(String cloudSiteId, String tenantId) throws MsoException {
661 KeystoneAuthHolder keystone = getKeystoneAuthHolder(cloudSiteId, tenantId, "orchestration");
662 Heat heatClient = new Heat(keystone.getServiceUrl());
663 heatClient.token(keystone.getId());
668 * Query for a Heat Stack. This function is needed in several places, so a common method is useful. This method
669 * takes an authenticated Heat Client (which internally identifies the cloud & tenant to search), and returns a
670 * Stack object if found, Null if not found, or an MsoOpenstackException if the Openstack API call fails.
672 * The stack name may be a simple name or a canonical name ("{name}/{id}"). When simple name is used, Openstack
673 * always returns a 302 redirect which results in a 2nd request (to the canonical name). Note that query by
674 * canonical name for a deleted stack returns a Stack object with status "DELETE_COMPLETE" while query by simple
675 * name for a deleted stack returns HTTP 404.
677 * @param heatClient an authenticated Heat client
679 * @param stackName the stack name to query
681 * @return a Stack object that describes the current stack or null if the requested stack doesn't exist.
683 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
685 public Stack queryHeatStack(Heat heatClient, String stackName) throws MsoException {
686 if (stackName == null) {
690 OpenStackRequest<Stack> request = heatClient.getStacks().byName(stackName);
691 return executeAndRecordOpenstackRequest(request);
692 } catch (OpenStackResponseException e) {
693 logger.error("Error in Query Stack", e);
694 if (e.getStatus() == 404) {
695 logger.debug("queryHeatStack - stack not found: {}", stackName);
698 // Convert the OpenStackResponseException to an MsoOpenstackException
699 throw heatExceptionToMsoException(e, "QueryStack");
701 } catch (OpenStackConnectException e) {
702 // Connection to Openstack failed
703 throw heatExceptionToMsoException(e, "QueryAllStack");
707 public Stack queryHeatStack(String stackName, String cloudSiteId, String tenantId) throws MsoException {
708 if (stackName == null) {
711 return queryHeatStack(getHeatClient(cloudSiteId, tenantId), stackName);
715 public Map<String, Object> queryStackForOutputs(String cloudSiteId, String cloudOwner, String tenantId,
716 String stackName) throws MsoException {
717 logger.debug("MsoHeatUtils.queryStackForOutputs)");
718 StackInfo heatStack = this.queryStack(cloudSiteId, cloudOwner, tenantId, stackName);
719 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
722 return heatStack.getOutputs();
725 public void copyStringOutputsToInputs(Map<String, Object> inputs, Map<String, Object> otherStackOutputs,
727 if (inputs == null || otherStackOutputs == null)
729 for (String key : otherStackOutputs.keySet()) {
730 if (!inputs.containsKey(key)) {
731 Object obj = otherStackOutputs.get(key);
732 if (obj instanceof String) {
733 inputs.put(key, (String) otherStackOutputs.get(key));
734 } else if (obj instanceof JsonNode) {
735 // This is a bit of mess - but I think it's the least impacting
736 // let's convert it BACK to a string - then it will get converted back later
738 String str = this.convertNode((JsonNode) obj);
739 inputs.put(key, str);
740 } catch (Exception e) {
741 logger.debug("DANGER WILL ROBINSON: unable to convert value for JsonNode {} ", key, e);
742 // effect here is this value will not have been copied to the inputs - and therefore will error
745 } else if (obj instanceof java.util.LinkedHashMap) {
746 logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
748 String str = JSON_MAPPER.writeValueAsString(obj);
749 inputs.put(key, str);
750 } catch (Exception e) {
751 logger.debug("DANGER WILL ROBINSON: unable to convert value for LinkedHashMap {} ", key, e);
753 } else if (obj instanceof Integer) {
755 String str = "" + obj;
756 inputs.put(key, str);
757 } catch (Exception e) {
758 logger.debug("DANGER WILL ROBINSON: unable to convert value for Integer {} ", key, e);
762 String str = obj.toString();
763 inputs.put(key, str);
764 } catch (Exception e) {
765 logger.debug("DANGER WILL ROBINSON: unable to convert value for Other {} ({}) ", key,
767 // effect here is this value will not have been copied to the inputs - and therefore will error
776 private String convertNode(final JsonNode node) {
778 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
779 return JSON_MAPPER.writeValueAsString(obj);
780 } catch (Exception e) {
781 logger.debug("Error converting json to string {} ", e.getMessage(), e);
783 return "[Error converting json to string]";
787 protected StringBuilder getOutputsAsStringBuilder(Stack heatStack) {
788 // This should only be used as a utility to print out the stack outputs
790 StringBuilder sb = new StringBuilder("");
791 if (heatStack == null) {
792 sb.append("(heatStack is null)");
795 List<Output> outputList = heatStack.getOutputs();
796 if (outputList == null || outputList.isEmpty()) {
797 sb.append("(outputs is empty)");
800 Map<String, Object> outputs = new HashMap<>();
801 for (Output outputItem : outputList) {
802 outputs.put(outputItem.getOutputKey(), outputItem.getOutputValue());
805 sb.append("OUTPUTS:\n");
806 for (String key : outputs.keySet()) {
807 sb.append("outputs[").append(counter++).append("]: ").append(key).append("=");
808 Object obj = outputs.get(key);
809 if (obj instanceof String) {
810 sb.append((String) obj).append(" (a string)");
811 } else if (obj instanceof JsonNode) {
812 sb.append(this.convertNode((JsonNode) obj)).append(" (a JsonNode)");
813 } else if (obj instanceof java.util.LinkedHashMap) {
815 String str = JSON_MAPPER.writeValueAsString(obj);
816 sb.append(str).append(" (a java.util.LinkedHashMap)");
817 } catch (Exception e) {
818 logger.debug("Exception :", e);
819 sb.append("(a LinkedHashMap value that would not convert nicely)");
821 } else if (obj instanceof Integer) {
824 str = obj.toString() + " (an Integer)\n";
825 } catch (Exception e) {
826 logger.debug("Exception :", e);
827 str = "(an Integer unable to call .toString() on)";
830 } else if (obj instanceof ArrayList) {
833 str = obj.toString() + " (an ArrayList)";
834 } catch (Exception e) {
835 logger.debug("Exception :", e);
836 str = "(an ArrayList unable to call .toString() on?)";
839 } else if (obj instanceof Boolean) {
842 str = obj.toString() + " (a Boolean)";
843 } catch (Exception e) {
844 logger.debug("Exception :", e);
845 str = "(an Boolean unable to call .toString() on?)";
851 str = obj.toString() + " (unknown Object type)";
852 } catch (Exception e) {
853 logger.debug("Exception :", e);
854 str = "(a value unable to call .toString() on?)";
865 public void copyBaseOutputsToInputs(Map<String, Object> inputs, Map<String, Object> otherStackOutputs,
866 List<String> paramNames, Map<String, String> aliases) {
867 if (inputs == null || otherStackOutputs == null)
869 for (String key : otherStackOutputs.keySet()) {
870 if (paramNames != null) {
871 if (!paramNames.contains(key) && !aliases.containsKey(key)) {
872 logger.debug("\tParameter {} is NOT defined to be in the template - do not copy to inputs", key);
875 if (aliases.containsKey(key)) {
876 logger.debug("Found an alias! Will move {} to {}", key, aliases.get(key));
877 Object obj = otherStackOutputs.get(key);
878 key = aliases.get(key);
879 otherStackOutputs.put(key, obj);
882 if (!inputs.containsKey(key)) {
883 Object obj = otherStackOutputs.get(key);
884 logger.debug("\t**Adding {} to inputs (.toString()={}", key, obj.toString());
885 if (obj instanceof String) {
886 logger.debug("\t\t**A String");
887 inputs.put(key, obj);
888 } else if (obj instanceof Integer) {
889 logger.debug("\t\t**An Integer");
890 inputs.put(key, obj);
891 } else if (obj instanceof JsonNode) {
892 logger.debug("\t\t**A JsonNode");
893 inputs.put(key, obj);
894 } else if (obj instanceof Boolean) {
895 logger.debug("\t\t**A Boolean");
896 inputs.put(key, obj);
897 } else if (obj instanceof java.util.LinkedHashMap) {
898 logger.debug("\t\t**A java.util.LinkedHashMap **");
899 inputs.put(key, obj);
900 } else if (obj instanceof java.util.ArrayList) {
901 logger.debug("\t\t**An ArrayList");
902 inputs.put(key, obj);
904 logger.debug("\t\t**UNKNOWN OBJECT TYPE");
905 inputs.put(key, obj);
908 logger.debug("key={} is already in the inputs - will not overwrite", key);
914 public List<String> convertCdlToArrayList(String cdl) {
915 String cdl2 = cdl.trim();
917 if (cdl2.startsWith("[") && cdl2.endsWith("]")) {
918 cdl3 = cdl2.substring(1, cdl2.lastIndexOf("]"));
922 return new ArrayList<>(Arrays.asList(cdl3.split(",")));
926 * New with 1707 - this method will convert all the String *values* of the inputs to their "actual" object type
927 * (based on the param type: in the db - which comes from the template): (heat variable type) -> java Object type
928 * string -> String number -> Integer json -> marshal object to json comma_delimited_list -> ArrayList boolean ->
929 * Boolean if any of the conversions should fail, we will default to adding it to the inputs as a string - see if
930 * Openstack can handle it. Also, will remove any params that are extra. Any aliases will be converted to their
931 * appropriate name (anyone use this feature?)
933 * @param inputs - the Map<String, String> of the inputs received on the request
934 * @param template the HeatTemplate object - this is so we can also verify if the param is valid for this template
935 * @return HashMap<String, Object> of the inputs, cleaned and converted
937 public Map<String, Object> convertInputMap(Map<String, Object> inputs, HeatTemplate template) {
938 HashMap<String, Object> newInputs = new HashMap<>();
939 HashMap<String, HeatTemplateParam> params = new HashMap<>();
940 HashMap<String, HeatTemplateParam> paramAliases = new HashMap<>();
942 if (inputs == null) {
943 return new HashMap<>();
946 Set<HeatTemplateParam> paramSet = template.getParameters();
947 } catch (Exception e) {
948 logger.debug("Exception occurred in convertInputMap {} :", e.getMessage(), e);
951 for (HeatTemplateParam htp : template.getParameters()) {
952 params.put(htp.getParamName(), htp);
953 if (htp.getParamAlias() != null && !"".equals(htp.getParamAlias())) {
954 logger.debug("\tFound ALIAS {} -> {}", htp.getParamName(), htp.getParamAlias());
955 paramAliases.put(htp.getParamAlias(), htp);
959 for (String key : inputs.keySet()) {
960 boolean alias = false;
961 String realName = null;
962 if (!params.containsKey(key)) {
963 // add check here for an alias
964 if (!paramAliases.containsKey(key)) {
968 realName = paramAliases.get(key).getParamName();
971 String type = params.get(key).getParamType();
972 if (type == null || "".equals(type)) {
973 logger.debug("**PARAM_TYPE is null/empty for {}, will default to string", key);
976 if ("string".equalsIgnoreCase(type)) {
978 String str = inputs.get(key) != null ? inputs.get(key).toString() : null;
980 newInputs.put(realName, str);
982 newInputs.put(key, str);
983 } else if ("number".equalsIgnoreCase(type)) {
984 String integerString = inputs.get(key) != null ? inputs.get(key).toString() : null;
985 Integer anInteger = null;
987 anInteger = Integer.parseInt(integerString);
988 } catch (Exception e) {
989 logger.debug("Unable to convert {} to an integer!!", integerString, e);
992 if (anInteger != null) {
994 newInputs.put(realName, anInteger);
996 newInputs.put(key, anInteger);
999 newInputs.put(realName, integerString);
1001 newInputs.put(key, integerString);
1003 } else if ("json".equalsIgnoreCase(type)) {
1004 Object jsonObj = inputs.get(key);
1007 if (jsonObj instanceof String) {
1008 json = JSON_MAPPER.readTree(jsonObj.toString());
1010 // will already marshal to json without intervention
1013 } catch (IOException e) {
1014 logger.error("failed to map to json, directly converting to string instead", e);
1015 json = jsonObj.toString();
1018 newInputs.put(realName, json);
1020 newInputs.put(key, json);
1021 } else if ("comma_delimited_list".equalsIgnoreCase(type)) {
1022 String commaSeparated = inputs.get(key) != null ? inputs.get(key).toString() : null;
1024 List<String> anArrayList = this.convertCdlToArrayList(commaSeparated);
1026 newInputs.put(realName, anArrayList);
1028 newInputs.put(key, anArrayList);
1029 } catch (Exception e) {
1030 logger.debug("Unable to convert {} to an ArrayList!!", commaSeparated, e);
1032 newInputs.put(realName, commaSeparated);
1034 newInputs.put(key, commaSeparated);
1036 } else if ("boolean".equalsIgnoreCase(type)) {
1037 String booleanString = inputs.get(key) != null ? inputs.get(key).toString() : null;
1038 Boolean aBool = Boolean.valueOf(booleanString);
1040 newInputs.put(realName, aBool);
1042 newInputs.put(key, aBool);
1044 // it's null or something undefined - just add it back as a String
1045 String str = inputs.get(key).toString();
1047 newInputs.put(realName, str);
1049 newInputs.put(key, str);
1056 * This helpful method added for Valet
1058 public String getCloudSiteKeystoneUrl(String cloudSiteId) throws MsoCloudSiteNotFound {
1059 String keystoneUrl = null;
1061 CloudSite cloudSite =
1062 cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
1063 CloudIdentity cloudIdentity = cloudSite.getIdentityService();
1064 keystoneUrl = cloudIdentity.getIdentityUrl();
1065 } catch (Exception e) {
1066 throw new MsoCloudSiteNotFound(cloudSiteId);
1068 if (keystoneUrl == null || keystoneUrl.isEmpty()) {
1069 throw new MsoCloudSiteNotFound(cloudSiteId);
1075 /*******************************************************************************
1077 * Methods (and associated utilities) to implement the VduPlugin interface
1079 *******************************************************************************/
1082 * VduPlugin interface for instantiate function.
1084 * Translate the VduPlugin parameters to the corresponding 'createStack' parameters, and then invoke the existing
1088 public VduInstance instantiateVdu(CloudInfo cloudInfo, String instanceName, Map<String, Object> inputs,
1089 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
1090 String cloudSiteId = cloudInfo.getCloudSiteId();
1091 String cloudOwner = cloudInfo.getCloudOwner();
1092 String tenantId = cloudInfo.getTenantId();
1094 // Translate the VDU ModelInformation structure to that which is needed for
1095 // creating the Heat stack. Loop through the artifacts, looking specifically
1096 // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
1097 // be attached as a FILE.
1098 String heatTemplate = null;
1099 Map<String, Object> nestedTemplates = new HashMap<>();
1100 Map<String, Object> files = new HashMap<>();
1101 String heatEnvironment = null;
1103 for (VduArtifact vduArtifact : vduModel.getArtifacts()) {
1104 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
1105 heatTemplate = new String(vduArtifact.getContent());
1106 } else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
1107 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
1108 } else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
1109 heatEnvironment = new String(vduArtifact.getContent());
1114 StackInfo stackInfo =
1115 createStack(cloudSiteId, cloudOwner, tenantId, instanceName, vduModel, heatTemplate, inputs, true, // poll
1118 vduModel.getTimeoutMinutes(), heatEnvironment, nestedTemplates, files, rollbackOnFailure);
1120 // Populate a vduInstance from the StackInfo
1121 return stackInfoToVduInstance(stackInfo);
1122 } catch (Exception e) {
1123 throw new VduException("MsoHeatUtils (instantiateVDU): createStack Exception", e);
1129 * VduPlugin interface for query function.
1132 public VduInstance queryVdu(CloudInfo cloudInfo, String instanceId) throws VduException {
1133 String cloudSiteId = cloudInfo.getCloudSiteId();
1134 String cloudOwner = cloudInfo.getCloudOwner();
1135 String tenantId = cloudInfo.getTenantId();
1138 // Query the Cloudify Deployment object and populate a VduInstance
1139 StackInfo stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
1141 return stackInfoToVduInstance(stackInfo);
1142 } catch (Exception e) {
1143 throw new VduException("MsoHeatUtile (queryVdu): queryStack Exception ", e);
1149 * VduPlugin interface for delete function.
1152 public VduInstance deleteVdu(CloudInfo cloudInfo, String instanceId, int timeoutMinutes) throws VduException {
1153 String cloudSiteId = cloudInfo.getCloudSiteId();
1154 String cloudOwner = cloudInfo.getCloudOwner();
1155 String tenantId = cloudInfo.getTenantId();
1158 // Delete the Heat stack
1159 StackInfo stackInfo = deleteStack(tenantId, cloudOwner, cloudSiteId, instanceId, true);
1161 // Populate a VduInstance based on the deleted Cloudify Deployment object
1162 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
1164 // Override return state to DELETED (HeatUtils sets to NOTFOUND)
1165 vduInstance.getStatus().setState(VduStateType.DELETED);
1168 } catch (Exception e) {
1169 throw new VduException("Delete VDU Exception", e);
1175 * VduPlugin interface for update function.
1177 * Update is currently not supported in the MsoHeatUtils implementation of VduPlugin. Just return a VduException.
1181 public VduInstance updateVdu(CloudInfo cloudInfo, String instanceId, Map<String, Object> inputs,
1182 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
1183 throw new VduException("MsoHeatUtils: updateVdu interface not supported");
1188 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1190 protected VduInstance stackInfoToVduInstance(StackInfo stackInfo) {
1191 VduInstance vduInstance = new VduInstance();
1193 // The full canonical name as the instance UUID
1194 vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
1195 vduInstance.setVduInstanceName(stackInfo.getName());
1197 // Copy inputs and outputs
1198 vduInstance.setInputs(stackInfo.getParameters());
1199 vduInstance.setOutputs(stackInfo.getOutputs());
1201 // Translate the status elements
1202 vduInstance.setStatus(stackStatusToVduStatus(stackInfo));
1207 private VduStatus stackStatusToVduStatus(StackInfo stackInfo) {
1208 VduStatus vduStatus = new VduStatus();
1210 // Map the status fields to more generic VduStatus.
1211 // There are lots of HeatStatus values, so this is a bit long...
1212 HeatStatus heatStatus = stackInfo.getStatus();
1213 String statusMessage = stackInfo.getStatusMessage();
1215 if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
1216 vduStatus.setState(VduStateType.INSTANTIATING);
1217 vduStatus.setLastAction((new PluginAction("create", IN_PROGRESS, statusMessage)));
1218 } else if (heatStatus == HeatStatus.NOTFOUND) {
1219 vduStatus.setState(VduStateType.NOTFOUND);
1220 } else if (heatStatus == HeatStatus.CREATED) {
1221 vduStatus.setState(VduStateType.INSTANTIATED);
1222 vduStatus.setLastAction((new PluginAction("create", "complete", statusMessage)));
1223 } else if (heatStatus == HeatStatus.UPDATED) {
1224 vduStatus.setState(VduStateType.INSTANTIATED);
1225 vduStatus.setLastAction((new PluginAction("update", "complete", statusMessage)));
1226 } else if (heatStatus == HeatStatus.UPDATING) {
1227 vduStatus.setState(VduStateType.UPDATING);
1228 vduStatus.setLastAction((new PluginAction("update", IN_PROGRESS, statusMessage)));
1229 } else if (heatStatus == HeatStatus.DELETING) {
1230 vduStatus.setState(VduStateType.DELETING);
1231 vduStatus.setLastAction((new PluginAction("delete", IN_PROGRESS, statusMessage)));
1232 } else if (heatStatus == HeatStatus.FAILED) {
1233 vduStatus.setState(VduStateType.FAILED);
1234 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
1236 vduStatus.setState(VduStateType.UNKNOWN);
1242 public Resources queryStackResources(String cloudSiteId, String tenantId, String stackName, int nestedDepth)
1243 throws MsoException {
1244 Heat heatClient = getHeatClient(cloudSiteId, tenantId);
1245 OpenStackRequest<Resources> request =
1246 heatClient.getResources().listResources(stackName).queryParam("nested_depth", nestedDepth);
1247 return executeAndRecordOpenstackRequest(request, false);
1250 public Events queryStackEvents(String cloudSiteId, String tenantId, String stackName, String stackId,
1251 int nestedDepth) throws MsoException {
1252 Heat heatClient = getHeatClient(cloudSiteId, tenantId);
1253 OpenStackRequest<Events> request =
1254 heatClient.getEvents().listEvents(stackName, stackId).queryParam("nested_depth", nestedDepth);
1255 return executeAndRecordOpenstackRequest(request, false);
1258 public Stacks queryStacks(String cloudSiteId, String tenantId, int limit, String marker)
1259 throws MsoCloudSiteNotFound, HeatClientException {
1262 heatClient = getHeatClient(cloudSiteId, tenantId);
1263 } catch (MsoException e) {
1264 logger.error("Error Creating Heat Client", e);
1265 throw new HeatClientException("Error Creating Heat Client", e);
1267 OpenStackRequest<Stacks> request =
1268 heatClient.getStacks().list().queryParam("limit", limit).queryParam("marker", marker);
1269 return executeAndRecordOpenstackRequest(request, false);
1272 public <R> R executeHeatClientRequest(String url, String cloudSiteId, String tenantId, Class<R> returnType)
1273 throws MsoException {
1274 Heat heatClient = getHeatClient(cloudSiteId, tenantId);
1275 OpenStackRequest<R> request = heatClient.get(url, returnType);
1276 return executeAndRecordOpenstackRequest(request, false);
1279 protected void sleep(long time) {
1282 } catch (InterruptedException e) {
1283 logger.debug("Thread interrupted while sleeping", e);
1284 Thread.currentThread().interrupt();