2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 Intel Corp. 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=========================================================
21 package org.onap.so.openstack.utils;
23 import java.net.MalformedURLException;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Scanner;
31 import javax.ws.rs.core.UriBuilderException;
32 import javax.ws.rs.core.Response;
34 import org.onap.so.client.HttpClientFactory;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
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.openstack.beans.HeatStatus;
48 import org.onap.so.openstack.beans.StackInfo;
49 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
50 import org.onap.so.openstack.exceptions.MsoException;
51 import org.onap.so.openstack.exceptions.MsoOpenstackException;
52 import org.onap.so.client.RestClient;
53 import org.onap.so.db.catalog.beans.CloudSite;
54 import org.onap.so.logger.MessageEnum;
55 import org.onap.so.logger.MsoLogger;
56 import org.onap.so.utils.TargetEntity;
57 import org.springframework.beans.factory.annotation.Autowired;
58 import org.springframework.core.env.Environment;
59 import org.springframework.stereotype.Component;
61 import com.fasterxml.jackson.databind.ObjectMapper;
62 import com.woorea.openstack.heat.model.CreateStackParam;
65 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
67 public static final String OOF_DIRECTIVES = "oof_directives";
68 public static final String SDNC_DIRECTIVES = "sdnc_directives";
69 public static final String GENERIC_VNF_ID = "generic_vnf_id";
70 public static final String VF_MODULE_ID = "vf_module_id";
71 public static final String TEMPLATE_TYPE = "template_type";
72 public static final List<String> MULTICLOUD_INPUTS =
73 Arrays.asList(OOF_DIRECTIVES, SDNC_DIRECTIVES, TEMPLATE_TYPE);
75 private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
77 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
78 private final HttpClientFactory httpClientFactory = new HttpClientFactory();
81 private Environment environment;
83 /******************************************************************************
85 * Methods (and associated utilities) to implement the VduPlugin interface
87 *******************************************************************************/
90 * Create a new Stack in the specified cloud location and tenant. The Heat template
91 * and parameter map are passed in as arguments, along with the cloud access credentials.
92 * It is expected that parameters have been validated and contain at minimum the required
93 * parameters for the given template with no extra (undefined) parameters..
95 * The Stack name supplied by the caller must be unique in the scope of this tenant.
96 * However, it should also be globally unique, as it will be the identifier for the
97 * resource going forward in Inventory. This latter is managed by the higher levels
98 * invoking this function.
100 * The caller may choose to let this function poll Openstack for completion of the
101 * stack creation, or may handle polling itself via separate calls to query the status.
102 * In either case, a StackInfo object will be returned containing the current status.
103 * When polling is enabled, a status of CREATED is expected. When not polling, a
104 * status of BUILDING is expected.
106 * An error will be thrown if the requested Stack already exists in the specified
109 * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as
110 * parameters for createStack. If environment is non-null, it will be added to the stack.
111 * The nested templates and get_file entries both end up being added to the "files" on the
112 * stack. We must combine them before we add them to the stack if they're both non-null.
114 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
115 * @param tenantId The Openstack ID of the tenant in which to create the Stack
116 * @param stackName The name of the stack to create
117 * @param heatTemplate The Heat template
118 * @param stackInputs A map of key/value inputs
119 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
120 * @param environment An optional yaml-format string to specify environmental parameters
121 * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
123 * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
124 * @param backout Do not delete stack on create Failure - defaulted to True
125 * @return A StackInfo object
126 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
129 @SuppressWarnings("unchecked")
131 public StackInfo createStack (String cloudSiteId,
135 Map <String, ?> stackInputs,
136 boolean pollForCompletion,
139 Map <String, Object> files,
140 Map <String, Object> heatFiles,
141 boolean backout) throws MsoException {
143 logger.trace("Started MsoMulticloudUtils.createStack");
145 // Get the directives, if present.
146 String oofDirectives = "";
147 String sdncDirectives = "";
148 String genericVnfId = "";
149 String vfModuleId = "";
150 String templateType = "";
152 for (String key: MULTICLOUD_INPUTS) {
153 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
154 if (key == OOF_DIRECTIVES) {
155 oofDirectives = (String) stackInputs.get(key);
157 if (key == SDNC_DIRECTIVES) {
158 sdncDirectives = (String) stackInputs.get(key);
160 if (key == TEMPLATE_TYPE) {
161 templateType = (String) stackInputs.get(key);
163 if (logger.isDebugEnabled()) {
164 logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
166 stackInputs.remove(key);
170 if (!stackInputs.isEmpty() && stackInputs.containsKey("VF_MODULE_ID")){
171 vfModuleId = (String) stackInputs.get("VF_MODULE_ID");
173 if (!stackInputs.isEmpty() && stackInputs.containsKey("GENERIC_VNF_ID")){
174 genericVnfId = (String) stackInputs.get("GENERIC_VNF_ID");
177 // create the multicloud payload
178 CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
180 MulticloudRequest multicloudRequest= new MulticloudRequest();
183 multicloudRequest.setGenericVnfId(genericVnfId);
184 multicloudRequest.setVfModuleId(vfModuleId);
185 multicloudRequest.setOofDirectives(JSON_MAPPER.readTree(oofDirectives));
186 multicloudRequest.setSdncDirectives(JSON_MAPPER.readTree(sdncDirectives));
187 multicloudRequest.setTemplateType(templateType);
188 if (logger.isDebugEnabled()) {
189 logger.debug(String.format("Stack Template Data is: %s", stack.toString().substring(16)));
191 multicloudRequest.setTemplateData(stack);
192 if (logger.isDebugEnabled()) {
193 logger.debug(String.format("Multicloud Request is: %s", multicloudRequest.toString()));
195 } catch (Exception e) {
196 logger.debug("ERROR making multicloud JSON body ", e);
198 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, null);
199 if (logger.isDebugEnabled()) {
200 logger.debug(String.format("Multicloud Endpoint is: %s", multicloudEndpoint));
202 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
204 Response response = multicloudClient.post(multicloudRequest);
206 StackInfo createInfo = new StackInfo();
207 createInfo.setName(stackName);
209 MulticloudCreateResponse multicloudResponseBody = null;
210 if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
211 multicloudResponseBody = getCreateBody((java.io.InputStream)response.getEntity());
212 createInfo.setCanonicalName(stackName + "/" + multicloudResponseBody.getWorkloadId());
213 if (logger.isDebugEnabled()) {
214 logger.debug("Multicloud Create Response Body: " + multicloudResponseBody);
216 return getStackStatus(cloudSiteId, tenantId, createInfo.getCanonicalName(), pollForCompletion, timeoutMinutes, backout);
218 createInfo.setStatus(HeatStatus.FAILED);
219 createInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
225 public Map<String, Object> queryStackForOutputs(String cloudSiteId,
226 String tenantId, String stackName) throws MsoException {
227 logger.debug("MsoHeatUtils.queryStackForOutputs)");
228 StackInfo heatStack = this.queryStack(cloudSiteId, tenantId, stackName);
229 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
232 return heatStack.getOutputs();
236 * Query for a single stack (by ID) in a tenant. This call will always return a
237 * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
238 * returned - containing only the stack name and a status of NOTFOUND.
240 * @param tenantId The Openstack ID of the tenant in which to query
241 * @param cloudSiteId The cloud identifier (may be a region) in which to query
242 * @param stackId The ID of the stack to query
243 * @return A StackInfo object
244 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
247 public StackInfo queryStack (String cloudSiteId, String tenantId, String instanceId) throws MsoException {
248 if (logger.isDebugEnabled()) {
249 logger.debug (String.format("Query multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
251 String stackName = null;
252 String stackId = null;
253 int offset = instanceId.indexOf('/');
254 if (offset > 0 && offset < (instanceId.length() - 1)) {
255 stackName = instanceId.substring(0, offset);
256 stackId = instanceId.substring(offset + 1);
258 stackName = instanceId;
259 stackId = instanceId;
262 StackInfo returnInfo = new StackInfo();
263 returnInfo.setName(stackName);
265 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackId);
266 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
268 if (multicloudClient != null) {
269 Response response = multicloudClient.get();
270 if (logger.isDebugEnabled()) {
271 logger.debug (String.format("Mulicloud GET Response: %s", response.toString()));
274 MulticloudQueryResponse multicloudQueryBody = null;
275 if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
276 returnInfo.setStatus(HeatStatus.NOTFOUND);
277 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
278 } else if (response.getStatus() == Response.Status.OK.getStatusCode() && response.hasEntity()) {
279 multicloudQueryBody = getQueryBody((java.io.InputStream)response.getEntity());
280 returnInfo.setCanonicalName(stackName + "/" + multicloudQueryBody.getWorkloadId());
281 returnInfo.setStatus(getHeatStatus(multicloudQueryBody.getWorkloadStatus()));
282 returnInfo.setStatusMessage(multicloudQueryBody.getWorkloadStatus());
283 if (logger.isDebugEnabled()) {
284 logger.debug("Multicloud Create Response Body: " + multicloudQueryBody.toString());
287 returnInfo.setStatus(HeatStatus.FAILED);
288 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
295 public StackInfo deleteStack (String cloudSiteId, String tenantId, String instanceId) throws MsoException {
296 if (logger.isDebugEnabled()) {
297 logger.debug (String.format("Delete multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
299 String stackName = null;
300 String stackId = null;
301 int offset = instanceId.indexOf('/');
302 if (offset > 0 && offset < (instanceId.length() - 1)) {
303 stackName = instanceId.substring(0, offset);
304 stackId = instanceId.substring(offset + 1);
306 stackName = instanceId;
307 stackId = instanceId;
310 StackInfo returnInfo = new StackInfo();
311 returnInfo.setName(stackName);
312 Response response = null;
314 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackId);
315 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
317 if (multicloudClient != null) {
318 response = multicloudClient.delete();
319 if (logger.isDebugEnabled()) {
320 logger.debug(String.format("Multicloud Delete response is: %s", response.getEntity().toString()));
323 if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
324 returnInfo.setStatus(HeatStatus.NOTFOUND);
325 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
326 } else if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
327 return getStackStatus(cloudSiteId, tenantId, instanceId);
329 returnInfo.setStatus(HeatStatus.FAILED);
330 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
334 returnInfo.setStatus(mapResponseToHeatStatus(response));
338 // ---------------------------------------------------------------
339 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
341 private HeatStatus getHeatStatus(String workloadStatus) {
342 if (workloadStatus.length() == 0) return HeatStatus.INIT;
343 if ("CREATE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.BUILDING;
344 if ("CREATE_COMPLETE".equals(workloadStatus)) return HeatStatus.CREATED;
345 if ("CREATE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
346 if ("DELETE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.DELETING;
347 if ("DELETE_COMPLETE".equals(workloadStatus)) return HeatStatus.NOTFOUND;
348 if ("DELETE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
349 if ("UPDATE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.UPDATING;
350 if ("UPDATE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
351 if ("UPDATE_COMPLETE".equals(workloadStatus)) return HeatStatus.UPDATED;
352 return HeatStatus.UNKNOWN;
355 private StackInfo getStackStatus(String cloudSiteId, String tenantId, String instanceId) throws MsoException {
356 return getStackStatus(cloudSiteId, tenantId, instanceId, false, 0, false);
359 private StackInfo getStackStatus(String cloudSiteId, String tenantId, String instanceId, boolean pollForCompletion, int timeoutMinutes, boolean backout) throws MsoException {
360 StackInfo stackInfo = new StackInfo();
362 // If client has requested a final response, poll for stack completion
363 if (pollForCompletion) {
364 // Set a time limit on overall polling.
365 // Use the resource (template) timeout for Openstack (expressed in minutes)
366 // and add one poll interval to give Openstack a chance to fail on its own.s
368 int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
369 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
370 // New 1610 - poll on delete if we rollback - use same values for now
371 int deletePollInterval = createPollInterval;
372 int deletePollTimeout = pollTimeout;
373 boolean createTimedOut = false;
374 StringBuilder stackErrorStatusReason = new StringBuilder("");
375 logger.debug("createPollInterval=" + createPollInterval + ", pollTimeout=" + pollTimeout);
379 stackInfo = queryStack(cloudSiteId, tenantId, instanceId);
380 logger.debug (stackInfo.getStatus() + " (" + instanceId + ")");
382 if (HeatStatus.BUILDING.equals(stackInfo.getStatus())) {
383 // Stack creation is still running.
384 // Sleep and try again unless timeout has been reached
385 if (pollTimeout <= 0) {
386 // Note that this should not occur, since there is a timeout specified
387 // in the Openstack (multicloud?) call.
388 logger.error(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, instanceId, stackInfo.getStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError, "Create stack timeout"));
389 createTimedOut = true;
393 sleep(createPollInterval * 1000L);
395 pollTimeout -= createPollInterval;
396 logger.debug("pollTimeout remaining: " + pollTimeout);
398 //save off the status & reason msg before we attempt delete
399 stackErrorStatusReason.append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
402 } catch (MsoException me) {
403 // Cannot query the stack status. Something is wrong.
404 // Try to roll back the stack
406 logger.warn(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, stack deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Create Stack, stack deletion suppressed"));
409 logger.debug("Create Stack error - unable to query for stack status - attempting to delete stack: " + instanceId + " - This will likely fail and/or we won't be able to query to see if delete worked");
410 StackInfo deleteInfo = deleteStack(cloudSiteId, tenantId, instanceId);
411 // this may be a waste of time - if we just got an exception trying to query the stack - we'll just
412 // get another one, n'est-ce pas?
413 boolean deleted = false;
416 StackInfo queryInfo = queryStack(cloudSiteId, tenantId, instanceId);
417 logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
418 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
419 if (deletePollTimeout <= 0) {
420 logger.error(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, instanceId,
421 queryInfo.getStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError,
422 "Rollback: DELETE stack timeout"));
425 sleep(deletePollInterval * 1000L);
426 deletePollTimeout -= deletePollInterval;
428 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())){
429 logger.debug("DELETE_COMPLETE for " + instanceId);
433 //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
436 } catch (Exception e3) {
437 // Just log this one. We will report the original exception.
438 logger.error(String.format("%d %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back stack: " + e3, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back stack on error on query"));
441 } catch (Exception e2) {
442 // Just log this one. We will report the original exception.
443 logger.error(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back stack: " + e2, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back stack"));
447 // Propagate the original exception from Stack Query.
448 me.addContext (CREATE_STACK);
453 if (!HeatStatus.CREATED.equals(stackInfo.getStatus())) {
454 logger.error(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack error: Polling complete with non-success status: "
455 + stackInfo.getStatus () + ", " + stackInfo.getStatusMessage(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack error"));
457 // Rollback the stack creation, since it is in an indeterminate state.
459 logger.warn(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, stack deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack error, stack deletion suppressed"));
464 logger.debug("Create Stack errored - attempting to DELETE stack: " + instanceId);
465 logger.debug("deletePollInterval=" + deletePollInterval + ", deletePollTimeout=" + deletePollTimeout);
466 StackInfo deleteInfo = deleteStack(cloudSiteId, tenantId, instanceId);
467 boolean deleted = false;
470 StackInfo queryInfo = queryStack(cloudSiteId, tenantId, instanceId);
471 logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
472 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
473 if (deletePollTimeout <= 0) {
474 logger.error(String.format("%d %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, instanceId,
475 queryInfo.getStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError,
476 "Rollback: DELETE stack timeout"));
479 sleep(deletePollInterval * 1000L);
480 deletePollTimeout -= deletePollInterval;
482 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())){
483 logger.debug("DELETE_COMPLETE for " + instanceId);
487 //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
488 logger.warn(String.format("%d %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, stack deletion FAILED", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack error, stack deletion FAILED"));
489 logger.debug("Stack deletion FAILED on a rollback of a create - " + instanceId + ", status=" + queryInfo.getStatus() + ", reason=" + queryInfo.getStatusMessage());
492 } catch (MsoException me2) {
493 // Just log this one. We will report the original exception.
494 logger.debug("Exception thrown trying to delete " + instanceId + " on a create->rollback: " + me2.getContextMessage(), me2);
495 logger.warn(String.format("%d %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, then stack deletion FAILED - exception thrown", "", "", MsoLogger.ErrorCode.BusinessProcesssError, me2.getContextMessage()));
498 StringBuilder errorContextMessage;
499 if (createTimedOut) {
500 errorContextMessage = new StringBuilder("Stack Creation Timeout");
502 errorContextMessage = stackErrorStatusReason;
505 errorContextMessage.append(" - stack successfully deleted");
507 errorContextMessage.append(" - encountered an error trying to delete the stack");
509 } catch (MsoException e2) {
510 // shouldn't happen - but handle
511 logger.error(String.format("%d %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back stack: " + e2, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Create Stack: rolling back stack"));
514 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
515 me.addContext(CREATE_STACK);
519 // Get initial status, since it will have been null after the create.
520 stackInfo = queryStack(cloudSiteId, tenantId, instanceId);
521 logger.debug("Multicloud stack query status is: " + stackInfo.getStatus());
526 private HeatStatus mapResponseToHeatStatus(Response response) {
527 if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) {
528 return HeatStatus.CREATED;
529 } else if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
530 return HeatStatus.CREATED;
531 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NO_CONTENT.getStatusCode()) {
532 return HeatStatus.CREATED;
533 } else if (response.getStatusInfo().getStatusCode() == Response.Status.BAD_REQUEST.getStatusCode()) {
534 return HeatStatus.FAILED;
535 } else if (response.getStatusInfo().getStatusCode() == Response.Status.UNAUTHORIZED.getStatusCode()) {
536 return HeatStatus.FAILED;
537 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NOT_FOUND.getStatusCode()) {
538 return HeatStatus.NOTFOUND;
539 } else if (response.getStatusInfo().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
540 return HeatStatus.FAILED;
542 return HeatStatus.UNKNOWN;
546 private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
547 Scanner scanner = new Scanner(in);
548 scanner.useDelimiter("\\Z");
550 if (scanner.hasNext()) {
551 body = scanner.next();
556 return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
557 } catch (Exception e) {
558 logger.debug("Exception retrieving multicloud vfModule POST response body " + e);
563 private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
564 Scanner scanner = new Scanner(in);
565 scanner.useDelimiter("\\Z");
567 if (scanner.hasNext()) {
568 body = scanner.next();
573 return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
574 } catch (Exception e) {
575 logger.debug("Exception retrieving multicloud workload query response body " + e);
580 private String getMulticloudEndpoint(String cloudSiteId, String workloadId) throws MsoCloudSiteNotFound {
582 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
583 String endpoint = cloudSite.getIdentityService().getIdentityUrl();
585 if (workloadId != null) {
586 if (logger.isDebugEnabled()) {
587 logger.debug(String.format("Multicloud Endpoint is: %s/%s", endpoint, workloadId));
589 return String.format("%s/%s", endpoint, workloadId);
591 if (logger.isDebugEnabled()) {
592 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
598 private RestClient getMulticloudClient(String endpoint) {
599 RestClient client = null;
601 client = httpClientFactory.newJsonClient(
603 TargetEntity.MULTICLOUD);
604 } catch (MalformedURLException e) {
605 logger.debug(String.format("Encountered malformed URL error getting multicloud rest client %s", e.getMessage()));
606 } catch (IllegalArgumentException e) {
607 logger.debug(String.format("Encountered illegal argument getting multicloud rest client %s",e.getMessage()));
608 } catch (UriBuilderException e) {
609 logger.debug(String.format("Encountered URI builder error getting multicloud rest client %s", e.getMessage()));
615 * VduPlugin interface for instantiate function.
617 * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
618 * and then invoke the existing function.
621 public VduInstance instantiateVdu (
624 Map<String,Object> inputs,
625 VduModelInfo vduModel,
626 boolean rollbackOnFailure)
629 String cloudSiteId = cloudInfo.getCloudSiteId();
630 String tenantId = cloudInfo.getTenantId();
632 // Translate the VDU ModelInformation structure to that which is needed for
633 // creating the Heat stack. Loop through the artifacts, looking specifically
634 // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
635 // be attached as a FILE.
636 String heatTemplate = null;
637 Map<String,Object> nestedTemplates = new HashMap<>();
638 Map<String,Object> files = new HashMap<>();
639 String heatEnvironment = null;
641 for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
642 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
643 heatTemplate = new String(vduArtifact.getContent());
645 else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
646 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
648 else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
649 heatEnvironment = new String(vduArtifact.getContent());
654 StackInfo stackInfo = createStack (cloudSiteId,
659 true, // poll for completion
660 vduModel.getTimeoutMinutes(),
665 // Populate a vduInstance from the StackInfo
666 return stackInfoToVduInstance(stackInfo);
668 catch (Exception e) {
669 throw new VduException ("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
675 * VduPlugin interface for query function.
678 public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
681 String cloudSiteId = cloudInfo.getCloudSiteId();
682 String tenantId = cloudInfo.getTenantId();
685 // Query the Cloudify Deployment object and populate a VduInstance
686 StackInfo stackInfo = queryStack (cloudSiteId, tenantId, instanceId);
688 return stackInfoToVduInstance(stackInfo);
690 catch (Exception e) {
691 throw new VduException ("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
697 * VduPlugin interface for delete function.
700 public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
703 String cloudSiteId = cloudInfo.getCloudSiteId();
704 String tenantId = cloudInfo.getTenantId();
707 // Delete the Multicloud stack
708 StackInfo stackInfo = deleteStack (cloudSiteId, tenantId, instanceId);
710 // Populate a VduInstance based on the deleted Cloudify Deployment object
711 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
713 // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
714 vduInstance.getStatus().setState(VduStateType.DELETED);
718 catch (Exception e) {
719 throw new VduException ("Delete VDU Exception", e);
725 * VduPlugin interface for update function.
727 * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin.
728 * Just return a VduException.
732 public VduInstance updateVdu (
735 Map<String,Object> inputs,
736 VduModelInfo vduModel,
737 boolean rollbackOnFailure)
740 throw new VduException ("MsoMulticloudUtils: updateVdu interface not supported");
745 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
747 protected VduInstance stackInfoToVduInstance (StackInfo stackInfo)
749 VduInstance vduInstance = new VduInstance();
751 if (logger.isDebugEnabled()) {
752 logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
754 // The full canonical name as the instance UUID
755 vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
756 vduInstance.setVduInstanceName(stackInfo.getName());
758 // Copy inputs and outputs
759 vduInstance.setInputs(stackInfo.getParameters());
760 vduInstance.setOutputs(stackInfo.getOutputs());
762 // Translate the status elements
763 vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
768 private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
770 VduStatus vduStatus = new VduStatus();
772 // Map the status fields to more generic VduStatus.
773 // There are lots of HeatStatus values, so this is a bit long...
774 HeatStatus heatStatus = stackInfo.getStatus();
775 String statusMessage = stackInfo.getStatusMessage();
776 logger.debug("HeatStatus = " + heatStatus + " msg = " + statusMessage);
778 if (logger.isDebugEnabled()) {
779 logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
780 logger.debug(String.format("Stack Status Message: %s", statusMessage));
783 if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
784 vduStatus.setState(VduStateType.INSTANTIATING);
785 vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
787 else if (heatStatus == HeatStatus.NOTFOUND) {
788 vduStatus.setState(VduStateType.NOTFOUND);
790 else if (heatStatus == HeatStatus.CREATED) {
791 vduStatus.setState(VduStateType.INSTANTIATED);
792 vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
794 else if (heatStatus == HeatStatus.UPDATED) {
795 vduStatus.setState(VduStateType.INSTANTIATED);
796 vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
798 else if (heatStatus == HeatStatus.UPDATING) {
799 vduStatus.setState(VduStateType.UPDATING);
800 vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
802 else if (heatStatus == HeatStatus.DELETING) {
803 vduStatus.setState(VduStateType.DELETING);
804 vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
806 else if (heatStatus == HeatStatus.FAILED) {
807 vduStatus.setState(VduStateType.FAILED);
808 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
810 vduStatus.setState(VduStateType.UNKNOWN);