4b68ddb67453d949b5afd8fa5e495a2347d18872
[so.git] / adapters / mso-adapter-utils / src / main / java / org / onap / so / openstack / utils / MsoMulticloudUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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=========================================================
21  */
22
23 package org.onap.so.openstack.utils;
24
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;
30 import java.net.URL;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
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;
67
68 @Component
69 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin {
70
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);
80
81     private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
82
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();
88
89     @Autowired
90     private Environment environment;
91
92     /******************************************************************************
93      *
94      * Methods (and associated utilities) to implement the VduPlugin interface
95      *
96      *******************************************************************************/
97
98     /**
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..
102      *
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.
106      *
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.
111      *
112      * An error will be thrown if the requested Stack already exists in the specified Tenant and Cloud.
113      *
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.
118      *
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
128      *        Template id)
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.
133      */
134
135     @SuppressWarnings("unchecked")
136     @Override
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 {
141
142         logger.trace("Started MsoMulticloudUtils.createStack");
143
144         // Get the directives, if present.
145         String oofDirectives = "{}";
146         String sdncDirectives = "{}";
147         String userDirectives = "{}";
148         String genericVnfId = "";
149         String vfModuleId = "";
150         String templateType = "";
151
152         for (String key : MULTICLOUD_INPUTS) {
153             if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
154                 if (key == OOF_DIRECTIVES) {
155                     oofDirectives = (String) stackInputs.get(key);
156                 }
157                 if (key == SDNC_DIRECTIVES) {
158                     sdncDirectives = (String) stackInputs.get(key);
159                 }
160                 if (key == USER_DIRECTIVES) {
161                     sdncDirectives = (String) stackInputs.get(key);
162                 }
163                 if (key == TEMPLATE_TYPE) {
164                     templateType = (String) stackInputs.get(key);
165                 }
166                 if (logger.isDebugEnabled()) {
167                     logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
168                 }
169                 stackInputs.remove(key);
170             }
171         }
172
173         if (!stackInputs.isEmpty() && stackInputs.containsKey(VF_MODULE_ID)) {
174             vfModuleId = (String) stackInputs.get(VF_MODULE_ID);
175         }
176         if (!stackInputs.isEmpty() && stackInputs.containsKey(VNF_ID)) {
177             genericVnfId = (String) stackInputs.get(VNF_ID);
178         }
179
180         // create the multicloud payload
181         CreateStackParam stack =
182                 createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
183
184         MulticloudRequest multicloudRequest = new MulticloudRequest();
185
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()));
198         }
199
200         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, null, false);
201         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
202
203         if (multicloudClient == null) {
204             MsoOpenstackException me = new MsoOpenstackException(0, "", "Multicloud client could not be initialized");
205             me.addContext(CREATE_STACK);
206             throw me;
207         }
208
209         Response response = multicloudClient.post(multicloudRequest);
210
211         MulticloudCreateResponse multicloudResponseBody = null;
212         if (response.hasEntity()) {
213             multicloudResponseBody = getCreateBody((java.io.InputStream) response.getEntity());
214         }
215         if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
216             String canonicalName = stackName + "/";
217             if (multicloudResponseBody != null) {
218                 canonicalName = canonicalName + multicloudResponseBody.getWorkloadId();
219             }
220             if (logger.isDebugEnabled()) {
221                 logger.debug("Multicloud Create Response Body: {}", multicloudResponseBody);
222             }
223             StackInfo stackStatus = getStackStatus(cloudSiteId, cloudOwner, tenantId, canonicalName, pollForCompletion,
224                     timeoutMinutes, backout);
225
226             if (HeatStatus.CREATED.equals(stackStatus.getStatus())) {
227                 multicloudAaiUpdate(cloudSiteId, cloudOwner, tenantId, genericVnfId, vfModuleId, canonicalName,
228                         pollForCompletion, timeoutMinutes);
229             }
230
231             return stackStatus;
232         }
233         StringBuilder stackErrorStatusReason = new StringBuilder(response.getStatusInfo().getReasonPhrase());
234         if (null != multicloudResponseBody) {
235             stackErrorStatusReason.append(multicloudResponseBody.toString());
236         }
237         MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
238         me.addContext(CREATE_STACK);
239         throw me;
240     }
241
242     @Override
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) {
248             return null;
249         }
250         return heatStack.getOutputs();
251     }
252
253     /**
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.
256      *
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.
263      */
264     @Override
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));
269         }
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);
277         } else {
278             stackName = instanceId;
279             stackId = instanceId;
280             byName = true;
281         }
282
283         StackInfo returnInfo = new StackInfo();
284         returnInfo.setName(stackName);
285
286         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, byName);
287         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
288
289         if (multicloudClient != null) {
290             Response response = multicloudClient.get();
291             if (logger.isDebugEnabled()) {
292                 logger.debug(String.format("Multicloud GET Response: %s", response.toString()));
293             }
294
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());
304                     }
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();
309                     } else {
310                         returnInfo.setCanonicalName(stackName + "/" + responseBody.getWorkloadId());
311                         returnInfo.setStatus(getHeatStatus(responseBody.getWorkloadStatus()));
312                         returnInfo.setStatusMessage(responseBody.getWorkloadStatus());
313                     }
314                 } else {
315                     returnInfo.setName(stackName);
316                     if (!byName)
317                         returnInfo.setCanonicalName(instanceId);
318                     returnInfo.setStatus(HeatStatus.FAILED);
319                     returnInfo.setStatusMessage(MULTICLOUD_QUERY_BODY_NULL);
320                 }
321             } else {
322                 returnInfo.setName(stackName);
323                 if (!byName)
324                     returnInfo.setCanonicalName(instanceId);
325                 returnInfo.setStatus(HeatStatus.FAILED);
326                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
327             }
328         }
329
330         return returnInfo;
331     }
332
333     private Stack getWorkloadStack(JsonNode node) {
334         if (node == null)
335             return null;
336         Stack workloadStack = null;
337         if (node.has("stacks")) {
338             try {
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);
341                 } else {
342                     workloadStack = new Stack();
343                     workloadStack.setStackStatus("NOT_FOUND");
344                 }
345             } catch (Exception e) {
346                 logger.debug("Multicloud Get Exception mapping /stack/0: {} ", node.toString(), e);
347             }
348         } else if (node.has("stack")) {
349             try {
350                 if (node.at("/stack").has("stack_status")) {
351                     workloadStack = JSON_MAPPER.treeToValue(node.at("/stack"), Stack.class);
352                 }
353             } catch (Exception e) {
354                 logger.debug("Multicloud Get Exception mapping /stack: {} ", node.toString(), e);
355             }
356         }
357         if (workloadStack != null)
358             logger.debug("Multicloud getWorkloadStack() returning Stack Object: {} ", workloadStack.toString());
359         return workloadStack;
360     }
361
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));
366         }
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);
373         } else {
374             stackName = instanceId;
375             stackId = instanceId;
376         }
377
378         StackInfo returnInfo = new StackInfo();
379         returnInfo.setName(stackName);
380         Response response = null;
381
382         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
383         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
384
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()));
389             }
390
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);
396             } else {
397                 returnInfo.setStatus(HeatStatus.FAILED);
398                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
399             }
400
401         }
402         returnInfo.setStatus(mapResponseToHeatStatus(response));
403         return returnInfo;
404     }
405
406     // ---------------------------------------------------------------
407     // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
408
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;
431     }
432
433     private void multicloudAaiUpdate(String cloudSiteId, String cloudOwner, String tenantId, String genericVnfId,
434             String vfModuleId, String workloadId, boolean pollForCompletion, int timeoutMinutes) {
435
436         String stackId = null;
437         int offset = workloadId.indexOf('/');
438         if (offset > 0 && offset < (workloadId.length() - 1)) {
439             stackId = workloadId.substring(offset + 1);
440         } else {
441             stackId = workloadId;
442         }
443
444         MulticloudRequest multicloudRequest = new MulticloudRequest();
445
446         multicloudRequest.setGenericVnfId(genericVnfId);
447         multicloudRequest.setVfModuleId(vfModuleId);
448
449         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
450         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
451
452         if (multicloudClient == null) {
453             if (logger.isDebugEnabled())
454                 logger.debug("Multicloud client could not be initialized");
455             return;
456         }
457
458         Response response = multicloudClient.post(multicloudRequest);
459         if (response.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
460             if (logger.isDebugEnabled())
461                 logger.debug(
462                         "Multicloud AAI update request failed: " + response.getStatus() + response.getStatusInfo());
463             return;
464         }
465
466         if (!pollForCompletion) {
467             return;
468         }
469
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);
475
476         StackInfo stackInfo = null;
477         while (true) {
478             try {
479                 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, workloadId);
480                 if (logger.isDebugEnabled())
481                     logger.debug(stackInfo.getStatus() + " (" + workloadId + ")");
482
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;
491                         break;
492                     }
493
494                     sleep(updatePollInterval * 1000L);
495
496                     pollTimeout -= updatePollInterval;
497                     if (logger.isDebugEnabled())
498                         logger.debug("pollTimeout remaining: " + pollTimeout);
499                 } else {
500                     break;
501                 }
502             } catch (MsoException me) {
503                 if (logger.isDebugEnabled())
504                     logger.debug("Multicloud AAI update exception: {} {} {} {}", cloudOwner, cloudSiteId, tenantId,
505                             workloadId, me);
506                 return;
507             }
508         }
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());
517         } else {
518             if (logger.isDebugEnabled())
519                 logger.debug("Multicloud AAI update successful: {} {}", response.getStatus(),
520                         response.getStatusInfo().toString());
521         }
522     }
523
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);
527     }
528
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();
532
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
538
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);
548
549             while (true) {
550                 try {
551                     stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
552                     logger.debug(stackInfo.getStatus() + " (" + instanceId + ")");
553
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;
565                             break;
566                         }
567
568                         sleep(createPollInterval * 1000L);
569
570                         pollTimeout -= createPollInterval;
571                         logger.debug("pollTimeout remaining: " + pollTimeout);
572                     } else {
573                         // save off the status & reason msg before we attempt delete
574                         stackErrorStatusReason
575                                 .append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
576                         break;
577                     }
578                 } catch (MsoException me) {
579                     // Cannot query the stack status. Something is wrong.
580                     // Try to roll back the stack
581                     if (!backout) {
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"));
586                     } else {
587                         try {
588                             logger.debug(
589                                     "Create Stack error - unable to query for stack status - attempting to delete stack: "
590                                             + instanceId
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 -
594                             // we'll just
595                             // get another one, n'est-ce pas?
596                             boolean deleted = false;
597                             while (!deleted) {
598                                 try {
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"));
608                                             break;
609                                         } else {
610                                             sleep(deletePollInterval * 1000L);
611                                             deletePollTimeout -= deletePollInterval;
612                                         }
613                                     } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
614                                         logger.debug("DELETE_COMPLETE for " + instanceId);
615                                         deleted = true;
616                                         continue;
617                                     } else {
618                                         // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
619                                         // evaluate
620                                         break;
621                                     }
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"));
629                                 }
630                             }
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"));
637                         }
638                     }
639
640                     // Propagate the original exception from Stack Query.
641                     me.addContext(CREATE_STACK);
642                     throw me;
643                 }
644             }
645
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"));
651
652                 // Rollback the stack creation, since it is in an indeterminate state.
653                 if (!backout) {
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"));
658                 } else {
659                     try {
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;
665                         while (!deleted) {
666                             try {
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"));
676                                         break;
677                                     } else {
678                                         sleep(deletePollInterval * 1000L);
679                                         deletePollTimeout -= deletePollInterval;
680                                     }
681                                 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
682                                     logger.debug("DELETE_COMPLETE for " + instanceId);
683                                     deleted = true;
684                                     continue;
685                                 } else {
686                                     // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
687                                     // evaluate
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());
696                                     break;
697                                 }
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()));
706                             }
707                         }
708                         StringBuilder errorContextMessage;
709                         if (createTimedOut) {
710                             errorContextMessage = new StringBuilder("Stack Creation Timeout");
711                         } else {
712                             errorContextMessage = stackErrorStatusReason;
713                         }
714                         if (deleted) {
715                             errorContextMessage.append(" - stack successfully deleted");
716                         } else {
717                             errorContextMessage.append(" - encountered an error trying to delete the stack");
718                         }
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"));
725                     }
726                 }
727                 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
728                 me.addContext(CREATE_STACK);
729                 throw me;
730             }
731         } else {
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());
735         }
736         return stackInfo;
737     }
738
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;
756         } else {
757             return HeatStatus.UNKNOWN;
758         }
759     }
760
761     private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
762         Scanner scanner = new Scanner(in);
763         scanner.useDelimiter("\\Z");
764         String body = "";
765         if (scanner.hasNext()) {
766             body = scanner.next();
767         }
768         scanner.close();
769
770         try {
771             return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
772         } catch (Exception e) {
773             logger.debug("Exception retrieving multicloud vfModule POST response body " + e);
774         }
775         return null;
776     }
777
778     private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
779         Scanner scanner = new Scanner(in);
780         scanner.useDelimiter("\\Z");
781         String body = "";
782         if (scanner.hasNext()) {
783             body = scanner.next();
784         }
785         scanner.close();
786
787         try {
788             return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
789         } catch (Exception e) {
790             logger.debug("Exception retrieving multicloud workload query response body " + e);
791         }
792         return null;
793     }
794
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);
799         }
800         Integer msbPort = environment.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
801
802         String path = "/api/multicloud/v1/" + cloudOwner + "/" + cloudSiteId + "/infra_workload";
803
804         String endpoint = UriBuilder.fromPath(path).host(msbIp).port(msbPort).scheme("http").build().toString();
805         if (workloadId != null) {
806             String middlepart = null;
807             if (isName) {
808                 middlepart = "?name=";
809             } else {
810                 middlepart = "/";
811             }
812             if (logger.isDebugEnabled()) {
813                 logger.debug(String.format("Multicloud Endpoint is: %s%s%s", endpoint, middlepart, workloadId));
814             }
815             return String.format("%s%s%s", endpoint, middlepart, workloadId);
816         } else {
817             if (logger.isDebugEnabled()) {
818                 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
819             }
820             return endpoint;
821         }
822     }
823
824     private RestClient getMulticloudClient(String endpoint, String tenantId) {
825         HttpClient client = null;
826         try {
827             client = httpClientFactory.newJsonClient(new URL(endpoint), TargetEntity.MULTICLOUD);
828             if (tenantId != null && !tenantId.isEmpty()) {
829                 client.addAdditionalHeader("Project", tenantId);
830             }
831         } catch (MalformedURLException e) {
832             logger.debug(
833                     String.format("Encountered malformed URL error getting multicloud rest client %s", e.getMessage()));
834         } catch (IllegalArgumentException e) {
835             logger.debug(
836                     String.format("Encountered illegal argument getting multicloud rest client %s", e.getMessage()));
837         } catch (UriBuilderException e) {
838             logger.debug(
839                     String.format("Encountered URI builder error getting multicloud rest client %s", e.getMessage()));
840         }
841         return client;
842     }
843
844     private JsonNode getDirectiveNode(String directives) throws MsoException {
845         try {
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);
853             throw me;
854         }
855     }
856
857     /**
858      * VduPlugin interface for instantiate function.
859      *
860      * Translate the VduPlugin parameters to the corresponding 'createStack' parameters, and then invoke the existing
861      * function.
862      */
863     @Override
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();
869
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;
878
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());
886             }
887         }
888
889         try {
890             StackInfo stackInfo =
891                     createStack(cloudSiteId, cloudOwner, tenantId, instanceName, vduModel, heatTemplate, inputs, true, // poll
892                                                                                                                        // for
893                                                                                                                        // completion
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);
899         }
900     }
901
902
903     /**
904      * VduPlugin interface for query function.
905      */
906     @Override
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();
911
912         try {
913             // Query the Cloudify Deployment object and populate a VduInstance
914             StackInfo stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
915
916             return stackInfoToVduInstance(stackInfo);
917         } catch (Exception e) {
918             throw new VduException("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
919         }
920     }
921
922
923     /**
924      * VduPlugin interface for delete function.
925      */
926     @Override
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();
931
932         try {
933             // Delete the Multicloud stack
934             StackInfo stackInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
935
936             // Populate a VduInstance based on the deleted Cloudify Deployment object
937             VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
938
939             // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
940             vduInstance.getStatus().setState(VduStateType.DELETED);
941
942             return vduInstance;
943         } catch (Exception e) {
944             throw new VduException("Delete VDU Exception", e);
945         }
946     }
947
948
949     /**
950      * VduPlugin interface for update function.
951      *
952      * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin. Just return a
953      * VduException.
954      *
955      */
956     @Override
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");
960     }
961
962
963     /*
964      * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
965      */
966     protected VduInstance stackInfoToVduInstance(StackInfo stackInfo) {
967         VduInstance vduInstance = new VduInstance();
968
969         if (logger.isDebugEnabled()) {
970             logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
971         }
972         // The full canonical name as the instance UUID
973         vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
974         vduInstance.setVduInstanceName(stackInfo.getName());
975
976         // Copy inputs and outputs
977         vduInstance.setInputs(stackInfo.getParameters());
978         vduInstance.setOutputs(stackInfo.getOutputs());
979
980         // Translate the status elements
981         vduInstance.setStatus(stackStatusToVduStatus(stackInfo));
982
983         return vduInstance;
984     }
985
986     private VduStatus stackStatusToVduStatus(StackInfo stackInfo) {
987         VduStatus vduStatus = new VduStatus();
988
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);
994
995         if (logger.isDebugEnabled()) {
996             logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
997             logger.debug(String.format("Stack Status Message: %s", statusMessage));
998         }
999
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());
1020         } else {
1021             vduStatus.setState(VduStateType.UNKNOWN);
1022         }
1023
1024         return vduStatus;
1025     }
1026 }