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 java.net.MalformedURLException;
27 import java.util.HashMap;
29 import java.util.Scanner;
30 import javax.ws.rs.core.Response;
31 import javax.ws.rs.core.UriBuilder;
32 import javax.ws.rs.core.UriBuilderException;
33 import org.onap.so.adapters.vdu.CloudInfo;
34 import org.onap.so.adapters.vdu.PluginAction;
35 import org.onap.so.adapters.vdu.VduArtifact;
36 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
37 import org.onap.so.adapters.vdu.VduException;
38 import org.onap.so.adapters.vdu.VduInstance;
39 import org.onap.so.adapters.vdu.VduModelInfo;
40 import org.onap.so.adapters.vdu.VduPlugin;
41 import org.onap.so.adapters.vdu.VduStateType;
42 import org.onap.so.adapters.vdu.VduStatus;
43 import org.onap.so.client.HttpClient;
44 import org.onap.so.client.HttpClientFactory;
45 import org.onap.so.client.RestClient;
46 import org.onap.so.logger.ErrorCode;
47 import org.onap.so.logger.MessageEnum;
48 import org.onap.so.openstack.beans.HeatStatus;
49 import org.onap.so.openstack.beans.StackInfo;
50 import org.onap.so.openstack.exceptions.MsoAdapterException;
51 import org.onap.so.openstack.exceptions.MsoException;
52 import org.onap.so.openstack.exceptions.MsoOpenstackException;
53 import org.onap.so.openstack.mappers.StackInfoMapper;
54 import org.onap.so.utils.TargetEntity;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.springframework.beans.factory.annotation.Autowired;
58 import org.springframework.core.env.Environment;
59 import org.springframework.stereotype.Component;
60 import com.fasterxml.jackson.databind.JsonNode;
61 import com.fasterxml.jackson.databind.ObjectMapper;
62 import com.google.common.collect.ImmutableSet;
63 import com.woorea.openstack.heat.model.CreateStackParam;
64 import com.woorea.openstack.heat.model.Stack;
67 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin {
69 public static final String OOF_DIRECTIVES = "oof_directives";
70 public static final String SDNC_DIRECTIVES = "sdnc_directives";
71 public static final String USER_DIRECTIVES = "user_directives";
72 public static final String VNF_ID = "vnf_id";
73 public static final String VF_MODULE_ID = "vf_module_id";
74 public static final String TEMPLATE_TYPE = "template_type";
75 public static final String MULTICLOUD_QUERY_BODY_NULL = "multicloudQueryBody is null";
76 public static final ImmutableSet<String> MULTICLOUD_INPUTS =
77 ImmutableSet.of(OOF_DIRECTIVES, SDNC_DIRECTIVES, USER_DIRECTIVES, TEMPLATE_TYPE);
79 private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
81 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
82 private static final Integer DEFAULT_MSB_PORT = 80;
83 private static final String DEFAULT_MSB_IP = "127.0.0.1";
84 private static final String ONAP_IP = "ONAP_IP";
85 private final HttpClientFactory httpClientFactory = new HttpClientFactory();
88 private Environment environment;
90 /******************************************************************************
92 * Methods (and associated utilities) to implement the VduPlugin interface
94 *******************************************************************************/
97 * Create a new Stack in the specified cloud location and tenant. The Heat template and parameter map are passed in
98 * as arguments, along with the cloud access credentials. It is expected that parameters have been validated and
99 * contain at minimum the required parameters for the given template with no extra (undefined) parameters..
101 * The Stack name supplied by the caller must be unique in the scope of this tenant. However, it should also be
102 * globally unique, as it will be the identifier for the resource going forward in Inventory. This latter is managed
103 * by the higher levels invoking this function.
105 * The caller may choose to let this function poll Openstack for completion of the stack creation, or may handle
106 * polling itself via separate calls to query the status. In either case, a StackInfo object will be returned
107 * containing the current status. When polling is enabled, a status of CREATED is expected. When not polling, a
108 * status of BUILDING is expected.
110 * An error will be thrown if the requested Stack already exists in the specified Tenant and Cloud.
112 * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as parameters for
113 * createStack. If environment is non-null, it will be added to the stack. The nested templates and get_file entries
114 * both end up being added to the "files" on the stack. We must combine them before we add them to the stack if
115 * they're both non-null.
117 * @param cloudSiteId The cloud (may be a region) in which to create the stack
118 * @param cloudOwner the cloud owner of the cloud site in which to create the stack
119 * @param tenantId The Openstack ID of the tenant in which to create the Stack
120 * @param stackName The name of the stack to create
121 * @param heatTemplate The Heat template
122 * @param stackInputs A map of key/value inputs
123 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
124 * @param environment An optional yaml-format string to specify environmental parameters
125 * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
127 * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
128 * @param backout Do not delete stack on create Failure - defaulted to True
129 * @return A StackInfo object
130 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
133 @SuppressWarnings("unchecked")
135 public StackInfo createStack(String cloudSiteId, String cloudOwner, String tenantId, String stackName,
136 VduModelInfo vduModel, String heatTemplate, Map<String, ?> stackInputs, boolean pollForCompletion,
137 int timeoutMinutes, String environment, Map<String, Object> files, Map<String, Object> heatFiles,
138 boolean backout) throws MsoException {
140 logger.trace("Started MsoMulticloudUtils.createStack");
142 // Get the directives, if present.
143 String oofDirectives = "{}";
144 String sdncDirectives = "{}";
145 String userDirectives = "{}";
146 String genericVnfId = "";
147 String vfModuleId = "";
148 String templateType = "";
150 for (String key : MULTICLOUD_INPUTS) {
151 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
152 if (key == OOF_DIRECTIVES) {
153 oofDirectives = (String) stackInputs.get(key);
155 if (key == SDNC_DIRECTIVES) {
156 sdncDirectives = (String) stackInputs.get(key);
158 if (key == USER_DIRECTIVES) {
159 userDirectives = (String) stackInputs.get(key);
161 if (key == TEMPLATE_TYPE) {
162 templateType = (String) stackInputs.get(key);
164 if (logger.isDebugEnabled()) {
165 logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
167 stackInputs.remove(key);
171 if (!stackInputs.isEmpty() && stackInputs.containsKey(VF_MODULE_ID)) {
172 vfModuleId = (String) stackInputs.get(VF_MODULE_ID);
174 if (!stackInputs.isEmpty() && stackInputs.containsKey(VNF_ID)) {
175 genericVnfId = (String) stackInputs.get(VNF_ID);
178 // create the multicloud payload
179 CreateStackParam stack =
180 createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
182 MulticloudRequest multicloudRequest = new MulticloudRequest();
184 multicloudRequest.setGenericVnfId(genericVnfId);
185 multicloudRequest.setVfModuleId(vfModuleId);
186 multicloudRequest.setVfModuleModelInvariantId(vduModel.getModelInvariantUUID());
187 multicloudRequest.setVfModuleModelVersionId(vduModel.getModelUUID());
188 multicloudRequest.setVfModuleModelCustomizationId(vduModel.getModelCustomizationUUID());
189 multicloudRequest.setTemplateType(templateType);
190 multicloudRequest.setTemplateData(stack);
191 multicloudRequest.setOofDirectives(getDirectiveNode(oofDirectives));
192 multicloudRequest.setSdncDirectives(getDirectiveNode(sdncDirectives));
193 multicloudRequest.setUserDirectives(getDirectiveNode(userDirectives));
194 if (logger.isDebugEnabled()) {
195 logger.debug(String.format("Multicloud Request is: %s", multicloudRequest.toString()));
198 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, null, false);
199 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
201 if (multicloudClient == null) {
202 MsoOpenstackException me = new MsoOpenstackException(0, "", "Multicloud client could not be initialized");
203 me.addContext(CREATE_STACK);
207 Response response = multicloudClient.post(multicloudRequest);
209 MulticloudCreateResponse multicloudResponseBody = null;
210 if (response.hasEntity()) {
211 multicloudResponseBody = getCreateBody((java.io.InputStream) response.getEntity());
213 if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
214 String canonicalName = stackName + "/";
215 if (multicloudResponseBody != null) {
216 canonicalName = canonicalName + multicloudResponseBody.getWorkloadId();
218 if (logger.isDebugEnabled()) {
219 logger.debug("Multicloud Create Response Body: {}", multicloudResponseBody);
221 StackInfo stackStatus = getStackStatus(cloudSiteId, cloudOwner, tenantId, canonicalName, pollForCompletion,
222 timeoutMinutes, backout);
224 if (HeatStatus.CREATED.equals(stackStatus.getStatus())) {
225 multicloudAaiUpdate(cloudSiteId, cloudOwner, tenantId, genericVnfId, vfModuleId, canonicalName,
226 pollForCompletion, timeoutMinutes);
231 StringBuilder stackErrorStatusReason = new StringBuilder(response.getStatusInfo().getReasonPhrase());
232 if (null != multicloudResponseBody) {
233 stackErrorStatusReason.append(multicloudResponseBody.toString());
235 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
236 me.addContext(CREATE_STACK);
241 public Map<String, Object> queryStackForOutputs(String cloudSiteId, String cloudOwner, String tenantId,
242 String stackName) throws MsoException {
243 logger.debug("MsoHeatUtils.queryStackForOutputs)");
244 StackInfo heatStack = this.queryStack(cloudSiteId, cloudOwner, tenantId, stackName);
245 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
248 return heatStack.getOutputs();
252 * Query for a single stack (by ID) in a tenant. This call will always return a StackInfo object. If the stack does
253 * not exist, an "empty" StackInfo will be returned - containing only the stack name and a status of NOTFOUND.
255 * @param tenantId The Openstack ID of the tenant in which to query
256 * @param cloudSiteId The cloud identifier (may be a region) in which to query
257 * @param cloudOwner cloud owner of the cloud site in which to query
258 * @param stackId The ID of the stack to query
259 * @return A StackInfo object
260 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
263 public StackInfo queryStack(String cloudSiteId, String cloudOwner, String tenantId, String instanceId)
264 throws MsoException {
265 if (logger.isDebugEnabled()) {
266 logger.debug(String.format("Query multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
268 String stackName = null;
269 String stackId = null;
270 boolean byName = false;
271 int offset = instanceId.indexOf('/');
272 if (offset > 0 && offset < (instanceId.length() - 1)) {
273 stackName = instanceId.substring(0, offset);
274 stackId = instanceId.substring(offset + 1);
276 stackName = instanceId;
277 stackId = instanceId;
281 StackInfo returnInfo = new StackInfo();
282 returnInfo.setName(stackName);
284 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, byName);
285 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
287 if (multicloudClient != null) {
288 Response response = multicloudClient.get();
289 if (logger.isDebugEnabled()) {
290 logger.debug(String.format("Multicloud GET Response: %s", response.toString()));
293 MulticloudQueryResponse responseBody = null;
294 if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
295 returnInfo.setStatus(HeatStatus.NOTFOUND);
296 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
297 } else if (response.getStatus() == Response.Status.OK.getStatusCode() && response.hasEntity()) {
298 responseBody = getQueryBody((java.io.InputStream) response.getEntity());
299 if (responseBody != null) {
300 if (logger.isDebugEnabled()) {
301 logger.debug("Multicloud Create Response Body: {}", responseBody);
303 Stack workloadStack = getWorkloadStack(responseBody.getWorkloadStatusReason());
304 if (workloadStack != null && !responseBody.getWorkloadStatus().equals("GET_FAILED")
305 && !responseBody.getWorkloadStatus().contains("UPDATE")) {
306 returnInfo = new StackInfoMapper(workloadStack).map();
308 returnInfo.setCanonicalName(stackName + "/" + responseBody.getWorkloadId());
309 returnInfo.setStatus(getHeatStatus(responseBody.getWorkloadStatus()));
310 returnInfo.setStatusMessage(responseBody.getWorkloadStatus());
313 returnInfo.setName(stackName);
315 returnInfo.setCanonicalName(instanceId);
316 returnInfo.setStatus(HeatStatus.FAILED);
317 returnInfo.setStatusMessage(MULTICLOUD_QUERY_BODY_NULL);
320 returnInfo.setName(stackName);
322 returnInfo.setCanonicalName(instanceId);
323 returnInfo.setStatus(HeatStatus.FAILED);
324 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
331 private Stack getWorkloadStack(JsonNode node) {
334 Stack workloadStack = null;
335 if (node.has("stacks")) {
337 if (!node.at("/stacks/0").isNull() && node.at("/stacks/0").has("stack_status")) {
338 workloadStack = JSON_MAPPER.treeToValue(node.at("/stacks/0"), Stack.class);
340 workloadStack = new Stack();
341 workloadStack.setStackStatus("NOT_FOUND");
343 } catch (Exception e) {
344 logger.debug("Multicloud Get Exception mapping /stack/0: {} ", node.toString(), e);
346 } else if (node.has("stack")) {
348 if (node.at("/stack").has("stack_status")) {
349 workloadStack = JSON_MAPPER.treeToValue(node.at("/stack"), Stack.class);
351 } catch (Exception e) {
352 logger.debug("Multicloud Get Exception mapping /stack: {} ", node.toString(), e);
355 if (workloadStack != null)
356 logger.debug("Multicloud getWorkloadStack() returning Stack Object: {} ", workloadStack);
357 return workloadStack;
360 public StackInfo deleteStack(String cloudSiteId, String cloudOwner, String tenantId, String instanceId)
361 throws MsoException {
362 if (logger.isDebugEnabled()) {
363 logger.debug(String.format("Delete multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
365 String stackName = null;
366 String stackId = null;
367 int offset = instanceId.indexOf('/');
368 if (offset > 0 && offset < (instanceId.length() - 1)) {
369 stackName = instanceId.substring(0, offset);
370 stackId = instanceId.substring(offset + 1);
372 stackName = instanceId;
373 stackId = instanceId;
376 StackInfo returnInfo = new StackInfo();
377 returnInfo.setName(stackName);
378 Response response = null;
380 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
381 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
383 if (multicloudClient != null) {
384 response = multicloudClient.delete();
385 if (logger.isDebugEnabled()) {
386 logger.debug(String.format("Multicloud Delete response is: %s", response.getEntity().toString()));
389 if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
390 returnInfo.setStatus(HeatStatus.NOTFOUND);
391 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
392 } else if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
393 return getStackStatus(cloudSiteId, cloudOwner, tenantId, instanceId);
395 returnInfo.setStatus(HeatStatus.FAILED);
396 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
400 returnInfo.setStatus(mapResponseToHeatStatus(response));
404 // ---------------------------------------------------------------
405 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
407 private HeatStatus getHeatStatus(String workloadStatus) {
408 if (workloadStatus.length() == 0)
409 return HeatStatus.INIT;
410 if ("CREATE_IN_PROGRESS".equals(workloadStatus))
411 return HeatStatus.BUILDING;
412 if ("CREATE_COMPLETE".equals(workloadStatus))
413 return HeatStatus.CREATED;
414 if ("CREATE_FAILED".equals(workloadStatus))
415 return HeatStatus.FAILED;
416 if ("DELETE_IN_PROGRESS".equals(workloadStatus))
417 return HeatStatus.DELETING;
418 if ("DELETE_COMPLETE".equals(workloadStatus))
419 return HeatStatus.NOTFOUND;
420 if ("DELETE_FAILED".equals(workloadStatus))
421 return HeatStatus.FAILED;
422 if ("UPDATE_IN_PROGRESS".equals(workloadStatus))
423 return HeatStatus.UPDATING;
424 if ("UPDATE_FAILED".equals(workloadStatus))
425 return HeatStatus.FAILED;
426 if ("UPDATE_COMPLETE".equals(workloadStatus))
427 return HeatStatus.UPDATED;
428 return HeatStatus.UNKNOWN;
431 private void multicloudAaiUpdate(String cloudSiteId, String cloudOwner, String tenantId, String genericVnfId,
432 String vfModuleId, String workloadId, boolean pollForCompletion, int timeoutMinutes) {
434 String stackId = null;
435 int offset = workloadId.indexOf('/');
436 if (offset > 0 && offset < (workloadId.length() - 1)) {
437 stackId = workloadId.substring(offset + 1);
439 stackId = workloadId;
442 MulticloudRequest multicloudRequest = new MulticloudRequest();
444 multicloudRequest.setGenericVnfId(genericVnfId);
445 multicloudRequest.setVfModuleId(vfModuleId);
447 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
448 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
450 if (multicloudClient == null) {
451 if (logger.isDebugEnabled())
452 logger.debug("Multicloud client could not be initialized");
456 Response response = multicloudClient.post(multicloudRequest);
457 if (response.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
458 if (logger.isDebugEnabled())
459 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(),
460 response.getStatusInfo());
464 if (!pollForCompletion) {
468 int updatePollInterval =
469 Integer.parseInt(this.environment.getProperty(createPollIntervalProp, CREATE_POLL_INTERVAL_DEFAULT));
470 int pollTimeout = (timeoutMinutes * 60) + updatePollInterval;
471 boolean updateTimedOut = false;
472 logger.debug("updatePollInterval={}, pollTimeout={}", updatePollInterval, pollTimeout);
474 StackInfo stackInfo = null;
477 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, workloadId);
478 if (logger.isDebugEnabled())
479 logger.debug("{} ({})", stackInfo.getStatus(), workloadId);
481 if (HeatStatus.UPDATING.equals(stackInfo.getStatus())) {
482 if (pollTimeout <= 0) {
483 // Note that this should not occur, since there is a timeout specified
484 // in the Openstack (multicloud?) call.
485 if (logger.isDebugEnabled())
486 logger.debug("Multicloud AAI update timeout failure: {} {} {} {}", cloudOwner, cloudSiteId,
487 tenantId, workloadId);
488 updateTimedOut = true;
492 sleep(updatePollInterval * 1000L);
494 pollTimeout -= updatePollInterval;
495 if (logger.isDebugEnabled())
496 logger.debug("pollTimeout remaining: {}", pollTimeout);
500 } catch (MsoException me) {
501 if (logger.isDebugEnabled())
502 logger.debug("Multicloud AAI update exception: {} {} {} {}", cloudOwner, cloudSiteId, tenantId,
507 if (updateTimedOut) {
508 if (logger.isDebugEnabled())
509 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(),
510 response.getStatusInfo());
511 } else if (!HeatStatus.UPDATED.equals(stackInfo.getStatus())) {
512 if (logger.isDebugEnabled())
513 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(),
514 response.getStatusInfo());
516 if (logger.isDebugEnabled())
517 logger.debug("Multicloud AAI update successful: {} {}", response.getStatus(), response.getStatusInfo());
521 private StackInfo getStackStatus(String cloudSiteId, String cloudOwner, String tenantId, String instanceId)
522 throws MsoException {
523 return getStackStatus(cloudSiteId, cloudOwner, tenantId, instanceId, false, 0, false);
526 private StackInfo getStackStatus(String cloudSiteId, String cloudOwner, String tenantId, String instanceId,
527 boolean pollForCompletion, int timeoutMinutes, boolean backout) throws MsoException {
528 StackInfo stackInfo = new StackInfo();
530 // If client has requested a final response, poll for stack completion
531 if (pollForCompletion) {
532 // Set a time limit on overall polling.
533 // Use the resource (template) timeout for Openstack (expressed in minutes)
534 // and add one poll interval to give Openstack a chance to fail on its own.s
536 int createPollInterval = Integer
537 .parseInt(this.environment.getProperty(createPollIntervalProp, CREATE_POLL_INTERVAL_DEFAULT));
538 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
539 // New 1610 - poll on delete if we rollback - use same values for now
540 int deletePollInterval = createPollInterval;
541 int deletePollTimeout = pollTimeout;
542 boolean createTimedOut = false;
543 StringBuilder stackErrorStatusReason = new StringBuilder("");
544 logger.debug("createPollInterval={}, pollTimeout={} ", createPollInterval, pollTimeout);
548 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
549 logger.debug("{} ({})", stackInfo.getStatus(), instanceId);
551 if (HeatStatus.BUILDING.equals(stackInfo.getStatus())) {
552 // Stack creation is still running.
553 // Sleep and try again unless timeout has been reached
554 if (pollTimeout <= 0) {
555 // Note that this should not occur, since there is a timeout specified
556 // in the Openstack (multicloud?) call.
557 logger.error(String.format("%s %s %s %s %s %s %s %s %d %s",
558 MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId, tenantId,
559 instanceId, stackInfo.getStatus(), "", "", ErrorCode.AvailabilityError.getValue(),
560 "Create stack timeout"));
561 createTimedOut = true;
565 sleep(createPollInterval * 1000L);
567 pollTimeout -= createPollInterval;
568 logger.debug("pollTimeout remaining: {}", pollTimeout);
570 // save off the status & reason msg before we attempt delete
571 stackErrorStatusReason
572 .append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
575 } catch (MsoException me) {
576 // Cannot query the stack status. Something is wrong.
577 // Try to roll back the stack
579 logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
580 "Create Stack error, stack deletion suppressed", "", "",
581 ErrorCode.BusinessProcesssError.getValue(),
582 "Exception in Create Stack, stack deletion suppressed"));
586 "Create Stack error - unable to query for stack status - attempting to delete stack: "
588 + " - This will likely fail and/or we won't be able to query to see if delete worked");
589 StackInfo deleteInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
590 // this may be a waste of time - if we just got an exception trying to query the stack -
592 // get another one, n'est-ce pas?
593 boolean deleted = false;
596 StackInfo queryInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
597 logger.debug("Deleting {}, status: {}", instanceId, queryInfo.getStatus());
598 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
599 if (deletePollTimeout <= 0) {
600 logger.error(String.format("%s %s %s %s %s %s %s %s %d %s",
601 MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner,
602 cloudSiteId, tenantId, instanceId, queryInfo.getStatus(), "", "",
603 ErrorCode.AvailabilityError.getValue(),
604 "Rollback: DELETE stack timeout"));
607 sleep(deletePollInterval * 1000L);
608 deletePollTimeout -= deletePollInterval;
610 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
611 logger.debug("DELETE_COMPLETE for {}", instanceId);
615 // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
619 } catch (Exception e3) {
620 // Just log this one. We will report the original exception.
621 logger.error(String.format("%s %s %s %s %d %s",
622 MessageEnum.RA_CREATE_STACK_ERR.toString(),
623 "Create Stack: Nested exception rolling back stack: " + e3, "", "",
624 ErrorCode.BusinessProcesssError.getValue(),
625 "Create Stack: Nested exception rolling back stack on error on query"));
628 } catch (Exception e2) {
629 // Just log this one. We will report the original exception.
630 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
631 "Create Stack: Nested exception rolling back stack: " + e2, "", "",
632 ErrorCode.BusinessProcesssError.getValue(),
633 "Create Stack: Nested exception rolling back stack"));
637 // Propagate the original exception from Stack Query.
638 me.addContext(CREATE_STACK);
643 if (!HeatStatus.CREATED.equals(stackInfo.getStatus())) {
644 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
645 "Create Stack error: Polling complete with non-success status: " + stackInfo.getStatus() + ", "
646 + stackInfo.getStatusMessage(),
647 "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack error"));
649 // Rollback the stack creation, since it is in an indeterminate state.
651 logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
652 "Create Stack errored, stack deletion suppressed", "", "",
653 ErrorCode.BusinessProcesssError.getValue(),
654 "Create Stack error, stack deletion suppressed"));
657 logger.debug("Create Stack errored - attempting to DELETE stack: " + instanceId);
658 logger.debug("deletePollInterval=" + deletePollInterval + ", deletePollTimeout="
659 + deletePollTimeout);
660 StackInfo deleteInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
661 boolean deleted = false;
664 StackInfo queryInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
665 logger.debug("Deleting {}, status: {}", instanceId, queryInfo.getStatus());
666 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
667 if (deletePollTimeout <= 0) {
668 logger.error(String.format("%s %s %s %s %s %s %s %s %d %s",
669 MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId,
670 tenantId, instanceId, queryInfo.getStatus(), "", "",
671 ErrorCode.AvailabilityError.getValue(),
672 "Rollback: DELETE stack timeout"));
675 sleep(deletePollInterval * 1000L);
676 deletePollTimeout -= deletePollInterval;
678 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
679 logger.debug("DELETE_COMPLETE for {}", instanceId);
683 // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
685 logger.warn(String.format("%s %s %s %s %d %s",
686 MessageEnum.RA_CREATE_STACK_ERR.toString(),
687 "Create Stack errored, stack deletion FAILED", "", "",
688 ErrorCode.BusinessProcesssError.getValue(),
689 "Create Stack error, stack deletion FAILED"));
690 logger.debug("Stack deletion FAILED on a rollback of a create - " + instanceId
691 + ", status=" + queryInfo.getStatus() + ", reason="
692 + queryInfo.getStatusMessage());
695 } catch (MsoException me2) {
696 // Just log this one. We will report the original exception.
697 logger.debug("Exception thrown trying to delete " + instanceId
698 + " on a create->rollback: " + me2.getContextMessage(), me2);
699 logger.warn(String.format("%s %s %s %s %d %s",
700 MessageEnum.RA_CREATE_STACK_ERR.toString(),
701 "Create Stack errored, then stack deletion FAILED - exception thrown", "", "",
702 ErrorCode.BusinessProcesssError.getValue(), me2.getContextMessage()));
705 StringBuilder errorContextMessage;
706 if (createTimedOut) {
707 errorContextMessage = new StringBuilder("Stack Creation Timeout");
709 errorContextMessage = stackErrorStatusReason;
712 errorContextMessage.append(" - stack successfully deleted");
714 errorContextMessage.append(" - encountered an error trying to delete the stack");
716 } catch (MsoException e2) {
717 // shouldn't happen - but handle
718 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
719 "Create Stack: Nested exception rolling back stack: " + e2, "", "",
720 ErrorCode.BusinessProcesssError.getValue(),
721 "Exception in Create Stack: rolling back stack"));
724 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
725 me.addContext(CREATE_STACK);
729 // Get initial status, since it will have been null after the create.
730 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
731 logger.debug("Multicloud stack query status is: {}", stackInfo.getStatus());
736 private HeatStatus mapResponseToHeatStatus(Response response) {
737 if (response == null) {
738 return HeatStatus.FAILED;
739 } else if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) {
740 return HeatStatus.CREATED;
741 } else if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
742 return HeatStatus.CREATED;
743 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NO_CONTENT.getStatusCode()) {
744 return HeatStatus.CREATED;
745 } else if (response.getStatusInfo().getStatusCode() == Response.Status.BAD_REQUEST.getStatusCode()) {
746 return HeatStatus.FAILED;
747 } else if (response.getStatusInfo().getStatusCode() == Response.Status.UNAUTHORIZED.getStatusCode()) {
748 return HeatStatus.FAILED;
749 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NOT_FOUND.getStatusCode()) {
750 return HeatStatus.NOTFOUND;
751 } else if (response.getStatusInfo().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
752 return HeatStatus.FAILED;
754 return HeatStatus.UNKNOWN;
758 private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
759 Scanner scanner = new Scanner(in);
760 scanner.useDelimiter("\\Z");
762 if (scanner.hasNext()) {
763 body = scanner.next();
768 return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
769 } catch (Exception e) {
770 logger.debug("Exception retrieving multicloud vfModule POST response body ", e);
775 private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
776 Scanner scanner = new Scanner(in);
777 scanner.useDelimiter("\\Z");
779 if (scanner.hasNext()) {
780 body = scanner.next();
785 return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
786 } catch (Exception e) {
787 logger.debug("Exception retrieving multicloud workload query response body ", e);
792 private String getMulticloudEndpoint(String cloudSiteId, String cloudOwner, String workloadId, boolean isName) {
793 String msbIp = System.getenv().get(ONAP_IP);
794 if (null == msbIp || msbIp.isEmpty()) {
795 msbIp = environment.getProperty("mso.msb-ip", DEFAULT_MSB_IP);
797 Integer msbPort = environment.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
799 String path = "/api/multicloud/v1/" + cloudOwner + "/" + cloudSiteId + "/infra_workload";
801 String endpoint = UriBuilder.fromPath(path).host(msbIp).port(msbPort).scheme("http").build().toString();
802 if (workloadId != null) {
803 String middlepart = null;
805 middlepart = "?name=";
809 if (logger.isDebugEnabled()) {
810 logger.debug(String.format("Multicloud Endpoint is: %s%s%s", endpoint, middlepart, workloadId));
812 return String.format("%s%s%s", endpoint, middlepart, workloadId);
814 if (logger.isDebugEnabled()) {
815 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
821 private RestClient getMulticloudClient(String endpoint, String tenantId) {
822 HttpClient client = null;
824 client = httpClientFactory.newJsonClient(new URL(endpoint), TargetEntity.MULTICLOUD);
825 if (tenantId != null && !tenantId.isEmpty()) {
826 client.addAdditionalHeader("Project", tenantId);
828 } catch (MalformedURLException e) {
829 logger.debug("Encountered malformed URL error getting multicloud rest client ", e);
830 } catch (IllegalArgumentException e) {
831 logger.debug("Encountered illegal argument getting multicloud rest client ", e);
832 } catch (UriBuilderException e) {
833 logger.debug("Encountered URI builder error getting multicloud rest client ", e);
838 private JsonNode getDirectiveNode(String directives) throws MsoException {
840 return JSON_MAPPER.readTree(directives);
841 } catch (Exception e) {
842 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(),
843 "Create Stack: " + e, "", "", ErrorCode.BusinessProcesssError.getValue(),
844 "Exception in Create Stack: Invalid JSON format of directives" + directives));
845 MsoException me = new MsoAdapterException("Invalid JSON format of directives parameter: " + directives);
846 me.addContext(CREATE_STACK);
852 * VduPlugin interface for instantiate function.
854 * Translate the VduPlugin parameters to the corresponding 'createStack' parameters, and then invoke the existing
858 public VduInstance instantiateVdu(CloudInfo cloudInfo, String instanceName, Map<String, Object> inputs,
859 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
860 String cloudSiteId = cloudInfo.getCloudSiteId();
861 String cloudOwner = cloudInfo.getCloudOwner();
862 String tenantId = cloudInfo.getTenantId();
864 // Translate the VDU ModelInformation structure to that which is needed for
865 // creating the Heat stack. Loop through the artifacts, looking specifically
866 // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
867 // be attached as a FILE.
868 String heatTemplate = null;
869 Map<String, Object> nestedTemplates = new HashMap<>();
870 Map<String, Object> files = new HashMap<>();
871 String heatEnvironment = null;
873 for (VduArtifact vduArtifact : vduModel.getArtifacts()) {
874 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
875 heatTemplate = new String(vduArtifact.getContent());
876 } else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
877 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
878 } else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
879 heatEnvironment = new String(vduArtifact.getContent());
884 StackInfo stackInfo =
885 createStack(cloudSiteId, cloudOwner, tenantId, instanceName, vduModel, heatTemplate, inputs, true, // poll
888 vduModel.getTimeoutMinutes(), heatEnvironment, nestedTemplates, files, rollbackOnFailure);
889 // Populate a vduInstance from the StackInfo
890 return stackInfoToVduInstance(stackInfo);
891 } catch (Exception e) {
892 throw new VduException("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
898 * VduPlugin interface for query function.
901 public VduInstance queryVdu(CloudInfo cloudInfo, String instanceId) throws VduException {
902 String cloudSiteId = cloudInfo.getCloudSiteId();
903 String cloudOwner = cloudInfo.getCloudOwner();
904 String tenantId = cloudInfo.getTenantId();
907 // Query the Cloudify Deployment object and populate a VduInstance
908 StackInfo stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
910 return stackInfoToVduInstance(stackInfo);
911 } catch (Exception e) {
912 throw new VduException("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
918 * VduPlugin interface for delete function.
921 public VduInstance deleteVdu(CloudInfo cloudInfo, String instanceId, int timeoutMinutes) throws VduException {
922 String cloudSiteId = cloudInfo.getCloudSiteId();
923 String cloudOwner = cloudInfo.getCloudOwner();
924 String tenantId = cloudInfo.getTenantId();
927 // Delete the Multicloud stack
928 StackInfo stackInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
930 // Populate a VduInstance based on the deleted Cloudify Deployment object
931 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
933 // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
934 vduInstance.getStatus().setState(VduStateType.DELETED);
937 } catch (Exception e) {
938 throw new VduException("Delete VDU Exception", e);
944 * VduPlugin interface for update function.
946 * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin. Just return a
951 public VduInstance updateVdu(CloudInfo cloudInfo, String instanceId, Map<String, Object> inputs,
952 VduModelInfo vduModel, boolean rollbackOnFailure) throws VduException {
953 throw new VduException("MsoMulticloudUtils: updateVdu interface not supported");
958 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
960 protected VduInstance stackInfoToVduInstance(StackInfo stackInfo) {
961 VduInstance vduInstance = new VduInstance();
963 if (logger.isDebugEnabled()) {
964 logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
966 // The full canonical name as the instance UUID
967 vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
968 vduInstance.setVduInstanceName(stackInfo.getName());
970 // Copy inputs and outputs
971 vduInstance.setInputs(stackInfo.getParameters());
972 vduInstance.setOutputs(stackInfo.getOutputs());
974 // Translate the status elements
975 vduInstance.setStatus(stackStatusToVduStatus(stackInfo));
980 private VduStatus stackStatusToVduStatus(StackInfo stackInfo) {
981 VduStatus vduStatus = new VduStatus();
983 // Map the status fields to more generic VduStatus.
984 // There are lots of HeatStatus values, so this is a bit long...
985 HeatStatus heatStatus = stackInfo.getStatus();
986 String statusMessage = stackInfo.getStatusMessage();
987 logger.debug("HeatStatus = {} msg = {}", heatStatus, statusMessage);
989 if (logger.isDebugEnabled()) {
990 logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
991 logger.debug(String.format("Stack Status Message: %s", statusMessage));
994 if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
995 vduStatus.setState(VduStateType.INSTANTIATING);
996 vduStatus.setLastAction((new PluginAction("create", "in_progress", statusMessage)));
997 } else if (heatStatus == HeatStatus.NOTFOUND) {
998 vduStatus.setState(VduStateType.NOTFOUND);
999 } else if (heatStatus == HeatStatus.CREATED) {
1000 vduStatus.setState(VduStateType.INSTANTIATED);
1001 vduStatus.setLastAction((new PluginAction("create", "complete", statusMessage)));
1002 } else if (heatStatus == HeatStatus.UPDATED) {
1003 vduStatus.setState(VduStateType.INSTANTIATED);
1004 vduStatus.setLastAction((new PluginAction("update", "complete", statusMessage)));
1005 } else if (heatStatus == HeatStatus.UPDATING) {
1006 vduStatus.setState(VduStateType.UPDATING);
1007 vduStatus.setLastAction((new PluginAction("update", "in_progress", statusMessage)));
1008 } else if (heatStatus == HeatStatus.DELETING) {
1009 vduStatus.setState(VduStateType.DELETING);
1010 vduStatus.setLastAction((new PluginAction("delete", "in_progress", statusMessage)));
1011 } else if (heatStatus == HeatStatus.FAILED) {
1012 vduStatus.setState(VduStateType.FAILED);
1013 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
1015 vduStatus.setState(VduStateType.UNKNOWN);