2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 Intel Corp. All rights reserved.
6 * ================================================================================
7 * Modifications Copyright (c) 2019 Samsung
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
23 package org.onap.so.openstack.utils;
25 import com.fasterxml.jackson.databind.JsonNode;
26 import com.fasterxml.jackson.databind.ObjectMapper;
27 import com.woorea.openstack.heat.model.CreateStackParam;
28 import com.woorea.openstack.heat.model.Stack;
29 import java.net.MalformedURLException;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.List;
35 import java.util.Scanner;
36 import javax.ws.rs.core.Response;
37 import javax.ws.rs.core.UriBuilder;
38 import javax.ws.rs.core.UriBuilderException;
39 import org.onap.so.adapters.vdu.CloudInfo;
40 import org.onap.so.adapters.vdu.PluginAction;
41 import org.onap.so.adapters.vdu.VduArtifact;
42 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
43 import org.onap.so.adapters.vdu.VduException;
44 import org.onap.so.adapters.vdu.VduInstance;
45 import org.onap.so.adapters.vdu.VduModelInfo;
46 import org.onap.so.adapters.vdu.VduPlugin;
47 import org.onap.so.adapters.vdu.VduStateType;
48 import org.onap.so.adapters.vdu.VduStatus;
49 import org.onap.so.client.HttpClient;
50 import org.onap.so.client.HttpClientFactory;
51 import org.onap.so.client.RestClient;
52 import org.onap.so.logger.ErrorCode;
53 import org.onap.so.logger.MessageEnum;
54 import org.onap.so.openstack.beans.HeatStatus;
55 import org.onap.so.openstack.beans.StackInfo;
56 import org.onap.so.openstack.exceptions.MsoAdapterException;
57 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
58 import org.onap.so.openstack.exceptions.MsoException;
59 import org.onap.so.openstack.exceptions.MsoOpenstackException;
60 import org.onap.so.openstack.mappers.StackInfoMapper;
61 import org.onap.so.utils.TargetEntity;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64 import org.springframework.beans.factory.annotation.Autowired;
65 import org.springframework.core.env.Environment;
66 import org.springframework.stereotype.Component;
69 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin {
71 public static final String OOF_DIRECTIVES = "oof_directives";
72 public static final String SDNC_DIRECTIVES = "sdnc_directives";
73 public static final String USER_DIRECTIVES = "user_directives";
74 public static final String VNF_ID = "vnf_id";
75 public static final String VF_MODULE_ID = "vf_module_id";
76 public static final String TEMPLATE_TYPE = "template_type";
77 public static final String MULTICLOUD_QUERY_BODY_NULL = "multicloudQueryBody is null";
78 public static final List<String> MULTICLOUD_INPUTS =
79 Arrays.asList(OOF_DIRECTIVES, SDNC_DIRECTIVES, USER_DIRECTIVES, TEMPLATE_TYPE);
81 private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
83 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
84 private static final Integer DEFAULT_MSB_PORT = 80;
85 private static final String DEFAULT_MSB_IP = "127.0.0.1";
86 private static final String ONAP_IP = "ONAP_IP";
87 private final HttpClientFactory httpClientFactory = new HttpClientFactory();
90 private Environment environment;
92 /******************************************************************************
94 * Methods (and associated utilities) to implement the VduPlugin interface
96 *******************************************************************************/
99 * Create a new Stack in the specified cloud location and tenant. The Heat template and parameter map are passed in
100 * as arguments, along with the cloud access credentials. It is expected that parameters have been validated and
101 * contain at minimum the required parameters for the given template with no extra (undefined) parameters..
103 * The Stack name supplied by the caller must be unique in the scope of this tenant. However, it should also be
104 * globally unique, as it will be the identifier for the resource going forward in Inventory. This latter is managed
105 * by the higher levels invoking this function.
107 * The caller may choose to let this function poll Openstack for completion of the stack creation, or may handle
108 * polling itself via separate calls to query the status. In either case, a StackInfo object will be returned
109 * containing the current status. When polling is enabled, a status of CREATED is expected. When not polling, a
110 * status of BUILDING is expected.
112 * An error will be thrown if the requested Stack already exists in the specified Tenant and Cloud.
114 * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as parameters for
115 * createStack. If environment is non-null, it will be added to the stack. The nested templates and get_file entries
116 * both end up being added to the "files" on the stack. We must combine them before we add them to the stack if
117 * they're both non-null.
119 * @param cloudSiteId The cloud (may be a region) in which to create the stack
120 * @param cloudOwner the cloud owner of the cloud site in which to create the stack
121 * @param tenantId The Openstack ID of the tenant in which to create the Stack
122 * @param stackName The name of the stack to create
123 * @param heatTemplate The Heat template
124 * @param stackInputs A map of key/value inputs
125 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
126 * @param environment An optional yaml-format string to specify environmental parameters
127 * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
129 * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
130 * @param backout Do not delete stack on create Failure - defaulted to True
131 * @return A StackInfo object
132 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
135 @SuppressWarnings("unchecked")
137 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
138 VduModelInfo vduModel, String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion,
139 int timeoutMinutes, String environment, Map<String, Object> files, Map<String, Object> heatFiles,
140 boolean backout) throws MsoException {
142 logger.trace("Started MsoMulticloudUtils.createStack");
144 // Get the directives, if present.
145 String oofDirectives = "{}";
146 String sdncDirectives = "{}";
147 String userDirectives = "{}";
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 == USER_DIRECTIVES) {
161 sdncDirectives = (String) stackInputs.get(key);
163 if (key == TEMPLATE_TYPE) {
164 templateType = (String) stackInputs.get(key);
166 if (logger.isDebugEnabled()) {
167 logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
169 stackInputs.remove(key);
173 if (!stackInputs.isEmpty() && stackInputs.containsKey(VF_MODULE_ID)) {
174 vfModuleId = (String) stackInputs.get(VF_MODULE_ID);
176 if (!stackInputs.isEmpty() && stackInputs.containsKey(VNF_ID)) {
177 genericVnfId = (String) stackInputs.get(VNF_ID);
180 // create the multicloud payload
181 CreateStackParam stack =
182 createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
184 MulticloudRequest multicloudRequest = new MulticloudRequest();
186 multicloudRequest.setGenericVnfId(genericVnfId);
187 multicloudRequest.setVfModuleId(vfModuleId);
188 multicloudRequest.setVfModuleModelInvariantId(vduModel.getModelInvariantUUID());
189 multicloudRequest.setVfModuleModelVersionId(vduModel.getModelUUID());
190 multicloudRequest.setVfModuleModelCustomizationId(vduModel.getModelCustomizationUUID());
191 multicloudRequest.setTemplateType(templateType);
192 multicloudRequest.setTemplateData(stack);
193 multicloudRequest.setOofDirectives(getDirectiveNode(oofDirectives));
194 multicloudRequest.setSdncDirectives(getDirectiveNode(sdncDirectives));
195 multicloudRequest.setUserDirectives(getDirectiveNode(userDirectives));
196 if (logger.isDebugEnabled()) {
197 logger.debug(String.format("Multicloud Request is: %s", multicloudRequest.toString()));
200 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, null, false);
201 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
203 if (multicloudClient == null) {
204 MsoOpenstackException me = new MsoOpenstackException(0, "", "Multicloud client could not be initialized");
205 me.addContext(CREATE_STACK);
209 Response response = multicloudClient.post(multicloudRequest);
211 MulticloudCreateResponse multicloudResponseBody = null;
212 if (response.hasEntity()) {
213 multicloudResponseBody = getCreateBody((java.io.InputStream) response.getEntity());
215 if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
216 String canonicalName = stackName + "/";
217 if (multicloudResponseBody != null) {
218 canonicalName = canonicalName + multicloudResponseBody.getWorkloadId();
220 if (logger.isDebugEnabled()) {
221 logger.debug("Multicloud Create Response Body: {}", multicloudResponseBody);
223 StackInfo stackStatus = getStackStatus(cloudSiteId, cloudOwner, tenantId, canonicalName, pollForCompletion,
224 timeoutMinutes, backout);
226 if (HeatStatus.CREATED.equals(stackStatus.getStatus())) {
227 multicloudAaiUpdate(cloudSiteId, cloudOwner, tenantId, genericVnfId, vfModuleId, canonicalName,
228 pollForCompletion, timeoutMinutes);
233 StringBuilder stackErrorStatusReason = new StringBuilder(response.getStatusInfo().getReasonPhrase());
234 if (null != multicloudResponseBody) {
235 stackErrorStatusReason.append(multicloudResponseBody.toString());
237 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
238 me.addContext(CREATE_STACK);
243 public Map<String, Object> queryStackForOutputs(String cloudSiteId, String cloudOwner, String tenantId,
244 String stackName) throws MsoException {
245 logger.debug("MsoHeatUtils.queryStackForOutputs)");
246 StackInfo heatStack = this.queryStack(cloudSiteId, cloudOwner, tenantId, stackName);
247 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
250 return heatStack.getOutputs();
254 * Query for a single stack (by ID) in a tenant. This call will always return a StackInfo object. If the stack does
255 * not exist, an "empty" StackInfo will be returned - containing only the stack name and a status of NOTFOUND.
257 * @param tenantId The Openstack ID of the tenant in which to query
258 * @param cloudSiteId The cloud identifier (may be a region) in which to query
259 * @param cloudOwner cloud owner of the cloud site in which to query
260 * @param stackId The ID of the stack to query
261 * @return A StackInfo object
262 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
265 public StackInfo queryStack(String cloudSiteId, String cloudOwner, String tenantId, String instanceId)
266 throws MsoException {
267 if (logger.isDebugEnabled()) {
268 logger.debug(String.format("Query multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
270 String stackName = null;
271 String stackId = null;
272 boolean byName = false;
273 int offset = instanceId.indexOf('/');
274 if (offset > 0 && offset < (instanceId.length() - 1)) {
275 stackName = instanceId.substring(0, offset);
276 stackId = instanceId.substring(offset + 1);
278 stackName = instanceId;
279 stackId = instanceId;
283 StackInfo returnInfo = new StackInfo();
284 returnInfo.setName(stackName);
286 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, byName);
287 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
289 if (multicloudClient != null) {
290 Response response = multicloudClient.get();
291 if (logger.isDebugEnabled()) {
292 logger.debug(String.format("Multicloud GET Response: %s", response.toString()));
295 MulticloudQueryResponse responseBody = null;
296 if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
297 returnInfo.setStatus(HeatStatus.NOTFOUND);
298 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
299 } else if (response.getStatus() == Response.Status.OK.getStatusCode() && response.hasEntity()) {
300 responseBody = getQueryBody((java.io.InputStream) response.getEntity());
301 if (responseBody != null) {
302 if (logger.isDebugEnabled()) {
303 logger.debug("Multicloud Create Response Body: " + responseBody.toString());
305 Stack workloadStack = getWorkloadStack(responseBody.getWorkloadStatusReason());
306 if (workloadStack != null && !responseBody.getWorkloadStatus().equals("GET_FAILED")
307 && !responseBody.getWorkloadStatus().contains("UPDATE")) {
308 returnInfo = new StackInfoMapper(workloadStack).map();
310 returnInfo.setCanonicalName(stackName + "/" + responseBody.getWorkloadId());
311 returnInfo.setStatus(getHeatStatus(responseBody.getWorkloadStatus()));
312 returnInfo.setStatusMessage(responseBody.getWorkloadStatus());
315 returnInfo.setName(stackName);
317 returnInfo.setCanonicalName(instanceId);
318 returnInfo.setStatus(HeatStatus.FAILED);
319 returnInfo.setStatusMessage(MULTICLOUD_QUERY_BODY_NULL);
322 returnInfo.setName(stackName);
324 returnInfo.setCanonicalName(instanceId);
325 returnInfo.setStatus(HeatStatus.FAILED);
326 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
333 private Stack getWorkloadStack(JsonNode node) {
336 Stack workloadStack = null;
337 if (node.has("stacks")) {
339 if (!node.at("/stacks/0").isNull() && node.at("/stacks/0").has("stack_status")) {
340 workloadStack = JSON_MAPPER.treeToValue(node.at("/stacks/0"), Stack.class);
342 workloadStack = new Stack();
343 workloadStack.setStackStatus(HeatStatus.NOTFOUND.toString());
345 } catch (Exception e) {
346 logger.debug("Multicloud Get Exception mapping /stack/0: {} ", node.toString(), e);
348 } else if (node.has("stack")) {
350 if (node.at("/stack").has("stack_status")) {
351 workloadStack = JSON_MAPPER.treeToValue(node.at("/stack"), Stack.class);
353 } catch (Exception e) {
354 logger.debug("Multicloud Get Exception mapping /stack: {} ", node.toString(), e);
357 if (workloadStack != null)
358 logger.debug("Multicloud getWorkloadStack() returning Stack Object: {} ", workloadStack.toString());
359 return workloadStack;
362 public StackInfo deleteStack(String cloudSiteId, String cloudOwner, String tenantId, String instanceId)
363 throws MsoException {
364 if (logger.isDebugEnabled()) {
365 logger.debug(String.format("Delete multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
367 String stackName = null;
368 String stackId = null;
369 int offset = instanceId.indexOf('/');
370 if (offset > 0 && offset < (instanceId.length() - 1)) {
371 stackName = instanceId.substring(0, offset);
372 stackId = instanceId.substring(offset + 1);
374 stackName = instanceId;
375 stackId = instanceId;
378 StackInfo returnInfo = new StackInfo();
379 returnInfo.setName(stackName);
380 Response response = null;
382 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
383 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
385 if (multicloudClient != null) {
386 response = multicloudClient.delete();
387 if (logger.isDebugEnabled()) {
388 logger.debug(String.format("Multicloud Delete response is: %s", response.getEntity().toString()));
391 if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
392 returnInfo.setStatus(HeatStatus.NOTFOUND);
393 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
394 } else if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
395 return getStackStatus(cloudSiteId, cloudOwner, tenantId, instanceId);
397 returnInfo.setStatus(HeatStatus.FAILED);
398 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
402 returnInfo.setStatus(mapResponseToHeatStatus(response));
406 // ---------------------------------------------------------------
407 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
409 private HeatStatus getHeatStatus(String workloadStatus) {
410 if (workloadStatus.length() == 0)
411 return HeatStatus.INIT;
412 if ("CREATE_IN_PROGRESS".equals(workloadStatus))
413 return HeatStatus.BUILDING;
414 if ("CREATE_COMPLETE".equals(workloadStatus))
415 return HeatStatus.CREATED;
416 if ("CREATE_FAILED".equals(workloadStatus))
417 return HeatStatus.FAILED;
418 if ("DELETE_IN_PROGRESS".equals(workloadStatus))
419 return HeatStatus.DELETING;
420 if ("DELETE_COMPLETE".equals(workloadStatus))
421 return HeatStatus.NOTFOUND;
422 if ("DELETE_FAILED".equals(workloadStatus))
423 return HeatStatus.FAILED;
424 if ("UPDATE_IN_PROGRESS".equals(workloadStatus))
425 return HeatStatus.UPDATING;
426 if ("UPDATE_FAILED".equals(workloadStatus))
427 return HeatStatus.FAILED;
428 if ("UPDATE_COMPLETE".equals(workloadStatus))
429 return HeatStatus.UPDATED;
430 return HeatStatus.UNKNOWN;
433 private void multicloudAaiUpdate(String cloudSiteId, String cloudOwner, String tenantId, String genericVnfId,
434 String vfModuleId, String workloadId, boolean pollForCompletion, int timeoutMinutes) {
436 String stackId = null;
437 int offset = workloadId.indexOf('/');
438 if (offset > 0 && offset < (workloadId.length() - 1)) {
439 stackId = workloadId.substring(offset + 1);
441 stackId = workloadId;
444 MulticloudRequest multicloudRequest = new MulticloudRequest();
446 multicloudRequest.setGenericVnfId(genericVnfId);
447 multicloudRequest.setVfModuleId(vfModuleId);
449 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
450 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
452 if (multicloudClient == null) {
453 if (logger.isDebugEnabled())
454 logger.debug("Multicloud client could not be initialized");
458 Response response = multicloudClient.post(multicloudRequest);
459 if (response.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
460 if (logger.isDebugEnabled())
462 "Multicloud AAI update request failed: " + response.getStatus() + response.getStatusInfo());
466 if (!pollForCompletion) {
470 int updatePollInterval =
471 Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
472 int pollTimeout = (timeoutMinutes * 60) + updatePollInterval;
473 boolean updateTimedOut = false;
474 logger.debug("updatePollInterval=" + updatePollInterval + ", pollTimeout=" + pollTimeout);
476 StackInfo stackInfo = null;
479 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, workloadId);
480 if (logger.isDebugEnabled())
481 logger.debug(stackInfo.getStatus() + " (" + workloadId + ")");
483 if (HeatStatus.UPDATING.equals(stackInfo.getStatus())) {
484 if (pollTimeout <= 0) {
485 // Note that this should not occur, since there is a timeout specified
486 // in the Openstack (multicloud?) call.
487 if (logger.isDebugEnabled())
488 logger.debug("Multicloud AAI update timeout failure: {} {} {} {}", cloudOwner, cloudSiteId,
489 tenantId, workloadId);
490 updateTimedOut = true;
494 sleep(updatePollInterval * 1000L);
496 pollTimeout -= updatePollInterval;
497 if (logger.isDebugEnabled())
498 logger.debug("pollTimeout remaining: " + pollTimeout);
502 } catch (MsoException me) {
503 if (logger.isDebugEnabled())
504 logger.debug("Multicloud AAI update exception: {} {} {} {}", cloudOwner, cloudSiteId, tenantId,
509 if (updateTimedOut) {
510 if (logger.isDebugEnabled())
511 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(),
512 response.getStatusInfo().toString());
513 } else if (!HeatStatus.UPDATED.equals(stackInfo.getStatus())) {
514 if (logger.isDebugEnabled())
515 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(),
516 response.getStatusInfo().toString());
518 if (logger.isDebugEnabled())
519 logger.debug("Multicloud AAI update successful: {} {}", response.getStatus(),
520 response.getStatusInfo().toString());
524 private StackInfo getStackStatus(String cloudSiteId, String cloudOwner, String tenantId, String instanceId)
525 throws MsoException {
526 return getStackStatus(cloudSiteId, cloudOwner, tenantId, instanceId, false, 0, false);
529 private StackInfo getStackStatus(String cloudSiteId, String cloudOwner, String tenantId, String instanceId,
530 boolean pollForCompletion, int timeoutMinutes, boolean backout) throws MsoException {
531 StackInfo stackInfo = new StackInfo();
533 // If client has requested a final response, poll for stack completion
534 if (pollForCompletion) {
535 // Set a time limit on overall polling.
536 // Use the resource (template) timeout for Openstack (expressed in minutes)
537 // and add one poll interval to give Openstack a chance to fail on its own.s
539 int createPollInterval =
540 Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
541 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
542 // New 1610 - poll on delete if we rollback - use same values for now
543 int deletePollInterval = createPollInterval;
544 int deletePollTimeout = pollTimeout;
545 boolean createTimedOut = false;
546 StringBuilder stackErrorStatusReason = new StringBuilder("");
547 logger.debug("createPollInterval=" + createPollInterval + ", pollTimeout=" + pollTimeout);
551 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
552 logger.debug(stackInfo.getStatus() + " (" + instanceId + ")");
554 if (HeatStatus.BUILDING.equals(stackInfo.getStatus())) {
555 // Stack creation is still running.
556 // Sleep and try again unless timeout has been reached
557 if (pollTimeout <= 0) {
558 // Note that this should not occur, since there is a timeout specified
559 // in the Openstack (multicloud?) call.
560 logger.error(String.format("%s %s %s %s %s %s %s %s %d %s",
561 MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId, tenantId,
562 instanceId, stackInfo.getStatus(), "", "", ErrorCode.AvailabilityError.getValue(),
563 "Create stack timeout"));
564 createTimedOut = true;
568 sleep(createPollInterval * 1000L);
570 pollTimeout -= createPollInterval;
571 logger.debug("pollTimeout remaining: " + pollTimeout);
573 // save off the status & reason msg before we attempt delete
574 stackErrorStatusReason
575 .append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
578 } catch (MsoException me) {
579 // Cannot query the stack status. Something is wrong.
580 // Try to roll back the stack
582 logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
583 "Create Stack error, stack deletion suppressed", "", "",
584 ErrorCode.BusinessProcesssError.getValue(),
585 "Exception in Create Stack, stack deletion suppressed"));
589 "Create Stack error - unable to query for stack status - attempting to delete stack: "
591 + " - This will likely fail and/or we won't be able to query to see if delete worked");
592 StackInfo deleteInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
593 // this may be a waste of time - if we just got an exception trying to query the stack -
595 // get another one, n'est-ce pas?
596 boolean deleted = false;
599 StackInfo queryInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
600 logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
601 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
602 if (deletePollTimeout <= 0) {
603 logger.error(String.format("%s %s %s %s %s %s %s %s %d %s",
604 MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner,
605 cloudSiteId, tenantId, instanceId, queryInfo.getStatus(), "", "",
606 ErrorCode.AvailabilityError.getValue(),
607 "Rollback: DELETE stack timeout"));
610 sleep(deletePollInterval * 1000L);
611 deletePollTimeout -= deletePollInterval;
613 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
614 logger.debug("DELETE_COMPLETE for " + instanceId);
618 // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
622 } catch (Exception e3) {
623 // Just log this one. We will report the original exception.
624 logger.error(String.format("%s %s %s %s %d %s",
625 MessageEnum.RA_CREATE_STACK_ERR.toString(),
626 "Create Stack: Nested exception rolling back stack: " + e3, "", "",
627 ErrorCode.BusinessProcesssError.getValue(),
628 "Create Stack: Nested exception rolling back stack on error on query"));
631 } catch (Exception e2) {
632 // Just log this one. We will report the original exception.
633 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
634 "Create Stack: Nested exception rolling back stack: " + e2, "", "",
635 ErrorCode.BusinessProcesssError.getValue(),
636 "Create Stack: Nested exception rolling back stack"));
640 // Propagate the original exception from Stack Query.
641 me.addContext(CREATE_STACK);
646 if (!HeatStatus.CREATED.equals(stackInfo.getStatus())) {
647 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
648 "Create Stack error: Polling complete with non-success status: " + stackInfo.getStatus() + ", "
649 + stackInfo.getStatusMessage(),
650 "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack error"));
652 // Rollback the stack creation, since it is in an indeterminate state.
654 logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
655 "Create Stack errored, stack deletion suppressed", "", "",
656 ErrorCode.BusinessProcesssError.getValue(),
657 "Create Stack error, stack deletion suppressed"));
660 logger.debug("Create Stack errored - attempting to DELETE stack: " + instanceId);
661 logger.debug("deletePollInterval=" + deletePollInterval + ", deletePollTimeout="
662 + deletePollTimeout);
663 StackInfo deleteInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
664 boolean deleted = false;
667 StackInfo queryInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
668 logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
669 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
670 if (deletePollTimeout <= 0) {
671 logger.error(String.format("%s %s %s %s %s %s %s %s %d %s",
672 MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId,
673 tenantId, instanceId, queryInfo.getStatus(), "", "",
674 ErrorCode.AvailabilityError.getValue(),
675 "Rollback: DELETE stack timeout"));
678 sleep(deletePollInterval * 1000L);
679 deletePollTimeout -= deletePollInterval;
681 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
682 logger.debug("DELETE_COMPLETE for " + instanceId);
686 // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
688 logger.warn(String.format("%s %s %s %s %d %s",
689 MessageEnum.RA_CREATE_STACK_ERR.toString(),
690 "Create Stack errored, stack deletion FAILED", "", "",
691 ErrorCode.BusinessProcesssError.getValue(),
692 "Create Stack error, stack deletion FAILED"));
693 logger.debug("Stack deletion FAILED on a rollback of a create - " + instanceId
694 + ", status=" + queryInfo.getStatus() + ", reason="
695 + queryInfo.getStatusMessage());
698 } catch (MsoException me2) {
699 // Just log this one. We will report the original exception.
700 logger.debug("Exception thrown trying to delete " + instanceId
701 + " on a create->rollback: " + me2.getContextMessage(), me2);
702 logger.warn(String.format("%s %s %s %s %d %s",
703 MessageEnum.RA_CREATE_STACK_ERR.toString(),
704 "Create Stack errored, then stack deletion FAILED - exception thrown", "", "",
705 ErrorCode.BusinessProcesssError.getValue(), me2.getContextMessage()));
708 StringBuilder errorContextMessage;
709 if (createTimedOut) {
710 errorContextMessage = new StringBuilder("Stack Creation Timeout");
712 errorContextMessage = stackErrorStatusReason;
715 errorContextMessage.append(" - stack successfully deleted");
717 errorContextMessage.append(" - encountered an error trying to delete the stack");
719 } catch (MsoException e2) {
720 // shouldn't happen - but handle
721 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
722 "Create Stack: Nested exception rolling back stack: " + e2, "", "",
723 ErrorCode.BusinessProcesssError.getValue(),
724 "Exception in Create Stack: rolling back stack"));
727 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
728 me.addContext(CREATE_STACK);
732 // Get initial status, since it will have been null after the create.
733 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
734 logger.debug("Multicloud stack query status is: " + stackInfo.getStatus());
739 private HeatStatus mapResponseToHeatStatus(Response response) {
740 if (response == null) {
741 return HeatStatus.FAILED;
742 } else if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) {
743 return HeatStatus.CREATED;
744 } else if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
745 return HeatStatus.CREATED;
746 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NO_CONTENT.getStatusCode()) {
747 return HeatStatus.CREATED;
748 } else if (response.getStatusInfo().getStatusCode() == Response.Status.BAD_REQUEST.getStatusCode()) {
749 return HeatStatus.FAILED;
750 } else if (response.getStatusInfo().getStatusCode() == Response.Status.UNAUTHORIZED.getStatusCode()) {
751 return HeatStatus.FAILED;
752 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NOT_FOUND.getStatusCode()) {
753 return HeatStatus.NOTFOUND;
754 } else if (response.getStatusInfo().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
755 return HeatStatus.FAILED;
757 return HeatStatus.UNKNOWN;
761 private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
762 Scanner scanner = new Scanner(in);
763 scanner.useDelimiter("\\Z");
765 if (scanner.hasNext()) {
766 body = scanner.next();
771 return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
772 } catch (Exception e) {
773 logger.debug("Exception retrieving multicloud vfModule POST response body " + e);
778 private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
779 Scanner scanner = new Scanner(in);
780 scanner.useDelimiter("\\Z");
782 if (scanner.hasNext()) {
783 body = scanner.next();
788 return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
789 } catch (Exception e) {
790 logger.debug("Exception retrieving multicloud workload query response body " + e);
795 private String getMulticloudEndpoint(String cloudSiteId, String cloudOwner, String workloadId, boolean isName) {
796 String msbIp = System.getenv().get(ONAP_IP);
797 if (null == msbIp || msbIp.isEmpty()) {
798 msbIp = environment.getProperty("mso.msb-ip", DEFAULT_MSB_IP);
800 Integer msbPort = environment.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
802 String path = "/api/multicloud/v1/" + cloudOwner + "/" + cloudSiteId + "/infra_workload";
804 String endpoint = UriBuilder.fromPath(path).host(msbIp).port(msbPort).scheme("http").build().toString();
805 if (workloadId != null) {
806 String middlepart = null;
808 middlepart = "?name=";
812 if (logger.isDebugEnabled()) {
813 logger.debug(String.format("Multicloud Endpoint is: %s%s%s", endpoint, middlepart, workloadId));
815 return String.format("%s%s%s", endpoint, middlepart, workloadId);
817 if (logger.isDebugEnabled()) {
818 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
824 private RestClient getMulticloudClient(String endpoint, String tenantId) {
825 HttpClient client = null;
827 client = httpClientFactory.newJsonClient(new URL(endpoint), TargetEntity.MULTICLOUD);
828 if (tenantId != null && !tenantId.isEmpty()) {
829 client.addAdditionalHeader("Project", tenantId);
831 } catch (MalformedURLException e) {
833 String.format("Encountered malformed URL error getting multicloud rest client %s", e.getMessage()));
834 } catch (IllegalArgumentException e) {
836 String.format("Encountered illegal argument getting multicloud rest client %s", e.getMessage()));
837 } catch (UriBuilderException e) {
839 String.format("Encountered URI builder error getting multicloud rest client %s", e.getMessage()));
844 private JsonNode getDirectiveNode(String directives) throws MsoException {
846 return JSON_MAPPER.readTree(directives);
847 } catch (Exception e) {
848 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
849 "Create Stack: " + e, "", "", ErrorCode.BusinessProcesssError.getValue(),
850 "Exception in Create Stack: Invalid JSON format of directives" + directives));
851 MsoException me = new MsoAdapterException("Invalid JSON format of directives parameter: " + directives);
852 me.addContext(CREATE_STACK);
858 * VduPlugin interface for instantiate function.
860 * Translate the VduPlugin parameters to the corresponding 'createStack' parameters, and then invoke the existing
864 public VduInstance instantiateVdu(CloudInfo cloudInfo, String instanceName, Map<String, Object> inputs,
865 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
866 String cloudSiteId = cloudInfo.getCloudSiteId();
867 String cloudOwner = cloudInfo.getCloudOwner();
868 String tenantId = cloudInfo.getTenantId();
870 // Translate the VDU ModelInformation structure to that which is needed for
871 // creating the Heat stack. Loop through the artifacts, looking specifically
872 // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
873 // be attached as a FILE.
874 String heatTemplate = null;
875 Map<String, Object> nestedTemplates = new HashMap<>();
876 Map<String, Object> files = new HashMap<>();
877 String heatEnvironment = null;
879 for (VduArtifact vduArtifact : vduModel.getArtifacts()) {
880 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
881 heatTemplate = new String(vduArtifact.getContent());
882 } else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
883 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
884 } else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
885 heatEnvironment = new String(vduArtifact.getContent());
890 StackInfo stackInfo =
891 createStack(cloudSiteId, cloudOwner, tenantId, instanceName, vduModel, heatTemplate, inputs, true, // poll
894 vduModel.getTimeoutMinutes(), heatEnvironment, nestedTemplates, files, rollbackOnFailure);
895 // Populate a vduInstance from the StackInfo
896 return stackInfoToVduInstance(stackInfo);
897 } catch (Exception e) {
898 throw new VduException("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
904 * VduPlugin interface for query function.
907 public VduInstance queryVdu(CloudInfo cloudInfo, String instanceId) throws VduException {
908 String cloudSiteId = cloudInfo.getCloudSiteId();
909 String cloudOwner = cloudInfo.getCloudOwner();
910 String tenantId = cloudInfo.getTenantId();
913 // Query the Cloudify Deployment object and populate a VduInstance
914 StackInfo stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
916 return stackInfoToVduInstance(stackInfo);
917 } catch (Exception e) {
918 throw new VduException("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
924 * VduPlugin interface for delete function.
927 public VduInstance deleteVdu(CloudInfo cloudInfo, String instanceId, int timeoutMinutes) throws VduException {
928 String cloudSiteId = cloudInfo.getCloudSiteId();
929 String cloudOwner = cloudInfo.getCloudOwner();
930 String tenantId = cloudInfo.getTenantId();
933 // Delete the Multicloud stack
934 StackInfo stackInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
936 // Populate a VduInstance based on the deleted Cloudify Deployment object
937 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
939 // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
940 vduInstance.getStatus().setState(VduStateType.DELETED);
943 } catch (Exception e) {
944 throw new VduException("Delete VDU Exception", e);
950 * VduPlugin interface for update function.
952 * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin. Just return a
957 public VduInstance updateVdu(CloudInfo cloudInfo, String instanceId, Map<String, Object> inputs,
958 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
959 throw new VduException("MsoMulticloudUtils: updateVdu interface not supported");
964 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
966 protected VduInstance stackInfoToVduInstance(StackInfo stackInfo) {
967 VduInstance vduInstance = new VduInstance();
969 if (logger.isDebugEnabled()) {
970 logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
972 // The full canonical name as the instance UUID
973 vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
974 vduInstance.setVduInstanceName(stackInfo.getName());
976 // Copy inputs and outputs
977 vduInstance.setInputs(stackInfo.getParameters());
978 vduInstance.setOutputs(stackInfo.getOutputs());
980 // Translate the status elements
981 vduInstance.setStatus(stackStatusToVduStatus(stackInfo));
986 private VduStatus stackStatusToVduStatus(StackInfo stackInfo) {
987 VduStatus vduStatus = new VduStatus();
989 // Map the status fields to more generic VduStatus.
990 // There are lots of HeatStatus values, so this is a bit long...
991 HeatStatus heatStatus = stackInfo.getStatus();
992 String statusMessage = stackInfo.getStatusMessage();
993 logger.debug("HeatStatus = " + heatStatus + " msg = " + statusMessage);
995 if (logger.isDebugEnabled()) {
996 logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
997 logger.debug(String.format("Stack Status Message: %s", statusMessage));
1000 if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
1001 vduStatus.setState(VduStateType.INSTANTIATING);
1002 vduStatus.setLastAction((new PluginAction("create", "in_progress", statusMessage)));
1003 } else if (heatStatus == HeatStatus.NOTFOUND) {
1004 vduStatus.setState(VduStateType.NOTFOUND);
1005 } else if (heatStatus == HeatStatus.CREATED) {
1006 vduStatus.setState(VduStateType.INSTANTIATED);
1007 vduStatus.setLastAction((new PluginAction("create", "complete", statusMessage)));
1008 } else if (heatStatus == HeatStatus.UPDATED) {
1009 vduStatus.setState(VduStateType.INSTANTIATED);
1010 vduStatus.setLastAction((new PluginAction("update", "complete", statusMessage)));
1011 } else if (heatStatus == HeatStatus.UPDATING) {
1012 vduStatus.setState(VduStateType.UPDATING);
1013 vduStatus.setLastAction((new PluginAction("update", "in_progress", statusMessage)));
1014 } else if (heatStatus == HeatStatus.DELETING) {
1015 vduStatus.setState(VduStateType.DELETING);
1016 vduStatus.setLastAction((new PluginAction("delete", "in_progress", statusMessage)));
1017 } else if (heatStatus == HeatStatus.FAILED) {
1018 vduStatus.setState(VduStateType.FAILED);
1019 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
1021 vduStatus.setState(VduStateType.UNKNOWN);