Merge "add junit coverage for SvnfmService"
[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 java.net.MalformedURLException;
26 import java.net.URL;
27 import java.util.HashMap;
28 import java.util.Map;
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;
65
66 @Component
67 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin {
68
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);
78
79     private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
80
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();
86
87     @Autowired
88     private Environment environment;
89
90     /******************************************************************************
91      *
92      * Methods (and associated utilities) to implement the VduPlugin interface
93      *
94      *******************************************************************************/
95
96     /**
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..
100      *
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.
104      *
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.
109      *
110      * An error will be thrown if the requested Stack already exists in the specified Tenant and Cloud.
111      *
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.
116      *
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
126      *        Template id)
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.
131      */
132
133     @SuppressWarnings("unchecked")
134     @Override
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 {
139
140         logger.trace("Started MsoMulticloudUtils.createStack");
141
142         // Get the directives, if present.
143         String oofDirectives = "{}";
144         String sdncDirectives = "{}";
145         String userDirectives = "{}";
146         String genericVnfId = "";
147         String vfModuleId = "";
148         String templateType = "";
149
150         for (String key : MULTICLOUD_INPUTS) {
151             if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
152                 if (key == OOF_DIRECTIVES) {
153                     oofDirectives = (String) stackInputs.get(key);
154                 }
155                 if (key == SDNC_DIRECTIVES) {
156                     sdncDirectives = (String) stackInputs.get(key);
157                 }
158                 if (key == USER_DIRECTIVES) {
159                     userDirectives = (String) stackInputs.get(key);
160                 }
161                 if (key == TEMPLATE_TYPE) {
162                     templateType = (String) stackInputs.get(key);
163                 }
164                 if (logger.isDebugEnabled()) {
165                     logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
166                 }
167                 stackInputs.remove(key);
168             }
169         }
170
171         if (!stackInputs.isEmpty() && stackInputs.containsKey(VF_MODULE_ID)) {
172             vfModuleId = (String) stackInputs.get(VF_MODULE_ID);
173         }
174         if (!stackInputs.isEmpty() && stackInputs.containsKey(VNF_ID)) {
175             genericVnfId = (String) stackInputs.get(VNF_ID);
176         }
177
178         // create the multicloud payload
179         CreateStackParam stack =
180                 createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
181
182         MulticloudRequest multicloudRequest = new MulticloudRequest();
183
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()));
196         }
197
198         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, null, false);
199         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
200
201         if (multicloudClient == null) {
202             MsoOpenstackException me = new MsoOpenstackException(0, "", "Multicloud client could not be initialized");
203             me.addContext(CREATE_STACK);
204             throw me;
205         }
206
207         Response response = multicloudClient.post(multicloudRequest);
208
209         MulticloudCreateResponse multicloudResponseBody = null;
210         if (response.hasEntity()) {
211             multicloudResponseBody = getCreateBody((java.io.InputStream) response.getEntity());
212         }
213         if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
214             String canonicalName = stackName + "/";
215             if (multicloudResponseBody != null) {
216                 canonicalName = canonicalName + multicloudResponseBody.getWorkloadId();
217             }
218             if (logger.isDebugEnabled()) {
219                 logger.debug("Multicloud Create Response Body: {}", multicloudResponseBody);
220             }
221             StackInfo stackStatus = getStackStatus(cloudSiteId, cloudOwner, tenantId, canonicalName, pollForCompletion,
222                     timeoutMinutes, backout);
223
224             if (HeatStatus.CREATED.equals(stackStatus.getStatus())) {
225                 multicloudAaiUpdate(cloudSiteId, cloudOwner, tenantId, genericVnfId, vfModuleId, canonicalName,
226                         pollForCompletion, timeoutMinutes);
227             }
228
229             return stackStatus;
230         }
231         StringBuilder stackErrorStatusReason = new StringBuilder(response.getStatusInfo().getReasonPhrase());
232         if (null != multicloudResponseBody) {
233             stackErrorStatusReason.append(multicloudResponseBody.toString());
234         }
235         MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
236         me.addContext(CREATE_STACK);
237         throw me;
238     }
239
240     @Override
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) {
246             return null;
247         }
248         return heatStack.getOutputs();
249     }
250
251     /**
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.
254      *
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.
261      */
262     @Override
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));
267         }
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);
275         } else {
276             stackName = instanceId;
277             stackId = instanceId;
278             byName = true;
279         }
280
281         StackInfo returnInfo = new StackInfo();
282         returnInfo.setName(stackName);
283
284         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, byName);
285         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
286
287         if (multicloudClient != null) {
288             Response response = multicloudClient.get();
289             if (logger.isDebugEnabled()) {
290                 logger.debug(String.format("Multicloud GET Response: %s", response.toString()));
291             }
292
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);
302                     }
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();
307                     } else {
308                         returnInfo.setCanonicalName(stackName + "/" + responseBody.getWorkloadId());
309                         returnInfo.setStatus(getHeatStatus(responseBody.getWorkloadStatus()));
310                         returnInfo.setStatusMessage(responseBody.getWorkloadStatus());
311                     }
312                 } else {
313                     returnInfo.setName(stackName);
314                     if (!byName)
315                         returnInfo.setCanonicalName(instanceId);
316                     returnInfo.setStatus(HeatStatus.FAILED);
317                     returnInfo.setStatusMessage(MULTICLOUD_QUERY_BODY_NULL);
318                 }
319             } else {
320                 returnInfo.setName(stackName);
321                 if (!byName)
322                     returnInfo.setCanonicalName(instanceId);
323                 returnInfo.setStatus(HeatStatus.FAILED);
324                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
325             }
326         }
327
328         return returnInfo;
329     }
330
331     private Stack getWorkloadStack(JsonNode node) {
332         if (node == null)
333             return null;
334         Stack workloadStack = null;
335         if (node.has("stacks")) {
336             try {
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);
339                 } else {
340                     workloadStack = new Stack();
341                     workloadStack.setStackStatus("NOT_FOUND");
342                 }
343             } catch (Exception e) {
344                 logger.debug("Multicloud Get Exception mapping /stack/0: {} ", node.toString(), e);
345             }
346         } else if (node.has("stack")) {
347             try {
348                 if (node.at("/stack").has("stack_status")) {
349                     workloadStack = JSON_MAPPER.treeToValue(node.at("/stack"), Stack.class);
350                 }
351             } catch (Exception e) {
352                 logger.debug("Multicloud Get Exception mapping /stack: {} ", node.toString(), e);
353             }
354         }
355         if (workloadStack != null)
356             logger.debug("Multicloud getWorkloadStack() returning Stack Object: {} ", workloadStack);
357         return workloadStack;
358     }
359
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));
364         }
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);
371         } else {
372             stackName = instanceId;
373             stackId = instanceId;
374         }
375
376         StackInfo returnInfo = new StackInfo();
377         returnInfo.setName(stackName);
378         Response response = null;
379
380         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
381         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
382
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()));
387             }
388
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);
394             } else {
395                 returnInfo.setStatus(HeatStatus.FAILED);
396                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
397             }
398
399         }
400         returnInfo.setStatus(mapResponseToHeatStatus(response));
401         return returnInfo;
402     }
403
404     // ---------------------------------------------------------------
405     // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
406
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;
429     }
430
431     private void multicloudAaiUpdate(String cloudSiteId, String cloudOwner, String tenantId, String genericVnfId,
432             String vfModuleId, String workloadId, boolean pollForCompletion, int timeoutMinutes) {
433
434         String stackId = null;
435         int offset = workloadId.indexOf('/');
436         if (offset > 0 && offset < (workloadId.length() - 1)) {
437             stackId = workloadId.substring(offset + 1);
438         } else {
439             stackId = workloadId;
440         }
441
442         MulticloudRequest multicloudRequest = new MulticloudRequest();
443
444         multicloudRequest.setGenericVnfId(genericVnfId);
445         multicloudRequest.setVfModuleId(vfModuleId);
446
447         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId, false);
448         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint, tenantId);
449
450         if (multicloudClient == null) {
451             if (logger.isDebugEnabled())
452                 logger.debug("Multicloud client could not be initialized");
453             return;
454         }
455
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());
461             return;
462         }
463
464         if (!pollForCompletion) {
465             return;
466         }
467
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);
473
474         StackInfo stackInfo = null;
475         while (true) {
476             try {
477                 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, workloadId);
478                 if (logger.isDebugEnabled())
479                     logger.debug("{} ({})", stackInfo.getStatus(), workloadId);
480
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;
489                         break;
490                     }
491
492                     sleep(updatePollInterval * 1000L);
493
494                     pollTimeout -= updatePollInterval;
495                     if (logger.isDebugEnabled())
496                         logger.debug("pollTimeout remaining: {}", pollTimeout);
497                 } else {
498                     break;
499                 }
500             } catch (MsoException me) {
501                 if (logger.isDebugEnabled())
502                     logger.debug("Multicloud AAI update exception: {} {} {} {}", cloudOwner, cloudSiteId, tenantId,
503                             workloadId, me);
504                 return;
505             }
506         }
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());
515         } else {
516             if (logger.isDebugEnabled())
517                 logger.debug("Multicloud AAI update successful: {} {}", response.getStatus(), response.getStatusInfo());
518         }
519     }
520
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);
524     }
525
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();
529
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
535
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);
545
546             while (true) {
547                 try {
548                     stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
549                     logger.debug("{} ({})", stackInfo.getStatus(), instanceId);
550
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;
562                             break;
563                         }
564
565                         sleep(createPollInterval * 1000L);
566
567                         pollTimeout -= createPollInterval;
568                         logger.debug("pollTimeout remaining: {}", pollTimeout);
569                     } else {
570                         // save off the status & reason msg before we attempt delete
571                         stackErrorStatusReason
572                                 .append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
573                         break;
574                     }
575                 } catch (MsoException me) {
576                     // Cannot query the stack status. Something is wrong.
577                     // Try to roll back the stack
578                     if (!backout) {
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"));
583                     } else {
584                         try {
585                             logger.debug(
586                                     "Create Stack error - unable to query for stack status - attempting to delete stack: "
587                                             + instanceId
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 -
591                             // we'll just
592                             // get another one, n'est-ce pas?
593                             boolean deleted = false;
594                             while (!deleted) {
595                                 try {
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"));
605                                             break;
606                                         } else {
607                                             sleep(deletePollInterval * 1000L);
608                                             deletePollTimeout -= deletePollInterval;
609                                         }
610                                     } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
611                                         logger.debug("DELETE_COMPLETE for {}", instanceId);
612                                         deleted = true;
613                                         continue;
614                                     } else {
615                                         // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
616                                         // evaluate
617                                         break;
618                                     }
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"));
626                                 }
627                             }
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"));
634                         }
635                     }
636
637                     // Propagate the original exception from Stack Query.
638                     me.addContext(CREATE_STACK);
639                     throw me;
640                 }
641             }
642
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"));
648
649                 // Rollback the stack creation, since it is in an indeterminate state.
650                 if (!backout) {
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"));
655                 } else {
656                     try {
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;
662                         while (!deleted) {
663                             try {
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"));
673                                         break;
674                                     } else {
675                                         sleep(deletePollInterval * 1000L);
676                                         deletePollTimeout -= deletePollInterval;
677                                     }
678                                 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())) {
679                                     logger.debug("DELETE_COMPLETE for {}", instanceId);
680                                     deleted = true;
681                                     continue;
682                                 } else {
683                                     // got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and
684                                     // evaluate
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());
693                                     break;
694                                 }
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()));
703                             }
704                         }
705                         StringBuilder errorContextMessage;
706                         if (createTimedOut) {
707                             errorContextMessage = new StringBuilder("Stack Creation Timeout");
708                         } else {
709                             errorContextMessage = stackErrorStatusReason;
710                         }
711                         if (deleted) {
712                             errorContextMessage.append(" - stack successfully deleted");
713                         } else {
714                             errorContextMessage.append(" - encountered an error trying to delete the stack");
715                         }
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"));
722                     }
723                 }
724                 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
725                 me.addContext(CREATE_STACK);
726                 throw me;
727             }
728         } else {
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());
732         }
733         return stackInfo;
734     }
735
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;
753         } else {
754             return HeatStatus.UNKNOWN;
755         }
756     }
757
758     private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
759         Scanner scanner = new Scanner(in);
760         scanner.useDelimiter("\\Z");
761         String body = "";
762         if (scanner.hasNext()) {
763             body = scanner.next();
764         }
765         scanner.close();
766
767         try {
768             return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
769         } catch (Exception e) {
770             logger.debug("Exception retrieving multicloud vfModule POST response body ", e);
771         }
772         return null;
773     }
774
775     private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
776         Scanner scanner = new Scanner(in);
777         scanner.useDelimiter("\\Z");
778         String body = "";
779         if (scanner.hasNext()) {
780             body = scanner.next();
781         }
782         scanner.close();
783
784         try {
785             return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
786         } catch (Exception e) {
787             logger.debug("Exception retrieving multicloud workload query response body ", e);
788         }
789         return null;
790     }
791
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);
796         }
797         Integer msbPort = environment.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
798
799         String path = "/api/multicloud/v1/" + cloudOwner + "/" + cloudSiteId + "/infra_workload";
800
801         String endpoint = UriBuilder.fromPath(path).host(msbIp).port(msbPort).scheme("http").build().toString();
802         if (workloadId != null) {
803             String middlepart = null;
804             if (isName) {
805                 middlepart = "?name=";
806             } else {
807                 middlepart = "/";
808             }
809             if (logger.isDebugEnabled()) {
810                 logger.debug(String.format("Multicloud Endpoint is: %s%s%s", endpoint, middlepart, workloadId));
811             }
812             return String.format("%s%s%s", endpoint, middlepart, workloadId);
813         } else {
814             if (logger.isDebugEnabled()) {
815                 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
816             }
817             return endpoint;
818         }
819     }
820
821     private RestClient getMulticloudClient(String endpoint, String tenantId) {
822         HttpClient client = null;
823         try {
824             client = httpClientFactory.newJsonClient(new URL(endpoint), TargetEntity.MULTICLOUD);
825             if (tenantId != null && !tenantId.isEmpty()) {
826                 client.addAdditionalHeader("Project", tenantId);
827             }
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);
834         }
835         return client;
836     }
837
838     private JsonNode getDirectiveNode(String directives) throws MsoException {
839         try {
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);
847             throw me;
848         }
849     }
850
851     /**
852      * VduPlugin interface for instantiate function.
853      *
854      * Translate the VduPlugin parameters to the corresponding 'createStack' parameters, and then invoke the existing
855      * function.
856      */
857     @Override
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();
863
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;
872
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());
880             }
881         }
882
883         try {
884             StackInfo stackInfo =
885                     createStack(cloudSiteId, cloudOwner, tenantId, instanceName, vduModel, heatTemplate, inputs, true, // poll
886                                                                                                                        // for
887                                                                                                                        // completion
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);
893         }
894     }
895
896
897     /**
898      * VduPlugin interface for query function.
899      */
900     @Override
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();
905
906         try {
907             // Query the Cloudify Deployment object and populate a VduInstance
908             StackInfo stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
909
910             return stackInfoToVduInstance(stackInfo);
911         } catch (Exception e) {
912             throw new VduException("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
913         }
914     }
915
916
917     /**
918      * VduPlugin interface for delete function.
919      */
920     @Override
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();
925
926         try {
927             // Delete the Multicloud stack
928             StackInfo stackInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
929
930             // Populate a VduInstance based on the deleted Cloudify Deployment object
931             VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
932
933             // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
934             vduInstance.getStatus().setState(VduStateType.DELETED);
935
936             return vduInstance;
937         } catch (Exception e) {
938             throw new VduException("Delete VDU Exception", e);
939         }
940     }
941
942
943     /**
944      * VduPlugin interface for update function.
945      *
946      * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin. Just return a
947      * VduException.
948      *
949      */
950     @Override
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");
954     }
955
956
957     /*
958      * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
959      */
960     protected VduInstance stackInfoToVduInstance(StackInfo stackInfo) {
961         VduInstance vduInstance = new VduInstance();
962
963         if (logger.isDebugEnabled()) {
964             logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
965         }
966         // The full canonical name as the instance UUID
967         vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
968         vduInstance.setVduInstanceName(stackInfo.getName());
969
970         // Copy inputs and outputs
971         vduInstance.setInputs(stackInfo.getParameters());
972         vduInstance.setOutputs(stackInfo.getOutputs());
973
974         // Translate the status elements
975         vduInstance.setStatus(stackStatusToVduStatus(stackInfo));
976
977         return vduInstance;
978     }
979
980     private VduStatus stackStatusToVduStatus(StackInfo stackInfo) {
981         VduStatus vduStatus = new VduStatus();
982
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);
988
989         if (logger.isDebugEnabled()) {
990             logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
991             logger.debug(String.format("Stack Status Message: %s", statusMessage));
992         }
993
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());
1014         } else {
1015             vduStatus.setState(VduStateType.UNKNOWN);
1016         }
1017
1018         return vduStatus;
1019     }
1020 }