Merge "Fixed Sonar blocker issues"
[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 java.net.MalformedURLException;
29 import java.net.URL;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Scanner;
35 import javax.ws.rs.core.Response;
36 import javax.ws.rs.core.UriBuilder;
37 import javax.ws.rs.core.UriBuilderException;
38 import org.onap.so.adapters.vdu.CloudInfo;
39 import org.onap.so.adapters.vdu.PluginAction;
40 import org.onap.so.adapters.vdu.VduArtifact;
41 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
42 import org.onap.so.adapters.vdu.VduException;
43 import org.onap.so.adapters.vdu.VduInstance;
44 import org.onap.so.adapters.vdu.VduModelInfo;
45 import org.onap.so.adapters.vdu.VduPlugin;
46 import org.onap.so.adapters.vdu.VduStateType;
47 import org.onap.so.adapters.vdu.VduStatus;
48 import org.onap.so.client.HttpClientFactory;
49 import org.onap.so.client.RestClient;
50 import org.onap.so.logger.ErrorCode;
51 import org.onap.so.logger.MessageEnum;
52 import org.onap.so.openstack.beans.HeatStatus;
53 import org.onap.so.openstack.beans.StackInfo;
54 import org.onap.so.openstack.exceptions.MsoAdapterException;
55 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
56 import org.onap.so.openstack.exceptions.MsoException;
57 import org.onap.so.openstack.exceptions.MsoOpenstackException;
58 import org.onap.so.utils.TargetEntity;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.beans.factory.annotation.Autowired;
62 import org.springframework.core.env.Environment;
63 import org.springframework.stereotype.Component;
64
65 @Component
66 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
67
68     public static final String OOF_DIRECTIVES = "oof_directives";
69     public static final String SDNC_DIRECTIVES = "sdnc_directives";
70     public static final String USER_DIRECTIVES = "user_directives";
71     public static final String VNF_ID = "vnf_id";
72     public static final String VF_MODULE_ID = "vf_module_id";
73     public static final String TEMPLATE_TYPE = "template_type";
74     public static final String MULTICLOUD_QUERY_BODY_NULL = "multicloudQueryBody is null";
75     public static final List<String> MULTICLOUD_INPUTS =
76             Arrays.asList(OOF_DIRECTIVES, SDNC_DIRECTIVES, USER_DIRECTIVES, TEMPLATE_TYPE);
77
78     private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
79
80     private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
81     private static final Integer DEFAULT_MSB_PORT = 80;
82     private static final String DEFAULT_MSB_IP = "127.0.0.1";
83     private static final String ONAP_IP = "ONAP_IP";
84     private final HttpClientFactory httpClientFactory = new HttpClientFactory();
85
86     @Autowired
87     private Environment environment;
88
89     /******************************************************************************
90      *
91      * Methods (and associated utilities) to implement the VduPlugin interface
92      *
93      *******************************************************************************/
94
95     /**
96      * Create a new Stack in the specified cloud location and tenant. The Heat template
97      * and parameter map are passed in as arguments, along with the cloud access credentials.
98      * It is expected that parameters have been validated and contain at minimum the required
99      * 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.
102      * However, it should also be globally unique, as it will be the identifier for the
103      * resource going forward in Inventory. This latter is managed by the higher levels
104      * invoking this function.
105      *
106      * The caller may choose to let this function poll Openstack for completion of the
107      * stack creation, or may handle polling itself via separate calls to query the status.
108      * In either case, a StackInfo object will be returned containing the current status.
109      * 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
113      * Tenant and Cloud.
114      *
115      * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as
116      * parameters for createStack. If environment is non-null, it will be added to the stack.
117      * The nested templates and get_file entries both end up being added to the "files" on the
118      * stack. We must combine them before we add them to the stack if they're both non-null.
119      *
120      * @param cloudSiteId The cloud (may be a region) in which to create the stack
121      * @param cloudOwner the cloud owner of the cloud site in which to create the stack
122      * @param tenantId The Openstack ID of the tenant in which to create the Stack
123      * @param stackName The name of the stack to create
124      * @param heatTemplate The Heat template
125      * @param stackInputs A map of key/value inputs
126      * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
127      * @param environment An optional yaml-format string to specify environmental parameters
128      * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
129      *        Template id)
130      * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
131      * @param backout Do not delete stack on create Failure - defaulted to True
132      * @return A StackInfo object
133      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
134      */
135
136     @SuppressWarnings("unchecked")
137     @Override
138     public StackInfo createStack (String cloudSiteId,
139                                   String cloudOwner,
140                                   String tenantId,
141                                   String stackName,
142                                   VduModelInfo vduModel,
143                                   String heatTemplate,
144                                   Map <String, ?> stackInputs,
145                                   boolean pollForCompletion,
146                                   int timeoutMinutes,
147                                   String environment,
148                                   Map <String, Object> files,
149                                   Map <String, Object> heatFiles,
150                                   boolean backout) throws MsoException {
151
152         logger.trace("Started MsoMulticloudUtils.createStack");
153
154         // Get the directives, if present.
155         String oofDirectives = "{}";
156         String sdncDirectives = "{}";
157         String userDirectives = "{}";
158         String genericVnfId = "";
159         String vfModuleId = "";
160         String templateType = "";
161
162         for (String key: MULTICLOUD_INPUTS) {
163             if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
164                 if (key == OOF_DIRECTIVES) {
165                     oofDirectives = (String) stackInputs.get(key);
166                 }
167                 if (key == SDNC_DIRECTIVES) {
168                     sdncDirectives = (String) stackInputs.get(key);
169                 }
170                 if (key == USER_DIRECTIVES) {
171                     sdncDirectives = (String) stackInputs.get(key);
172                 }
173                 if (key == TEMPLATE_TYPE) {
174                     templateType = (String) stackInputs.get(key);
175                 }
176                 if (logger.isDebugEnabled()) {
177                     logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
178                 }
179                 stackInputs.remove(key);
180             }
181         }
182
183         if (!stackInputs.isEmpty() && stackInputs.containsKey(VF_MODULE_ID)){
184             vfModuleId = (String) stackInputs.get(VF_MODULE_ID);
185         }
186         if (!stackInputs.isEmpty() && stackInputs.containsKey(VNF_ID)){
187             genericVnfId = (String) stackInputs.get(VNF_ID);
188         }
189
190         // create the multicloud payload
191         CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
192
193         MulticloudRequest multicloudRequest= new MulticloudRequest();
194
195         multicloudRequest.setGenericVnfId(genericVnfId);
196         multicloudRequest.setVfModuleId(vfModuleId);
197         multicloudRequest.setVfModuleModelInvariantId(vduModel.getModelInvariantUUID());
198         multicloudRequest.setVfModuleModelVersionId(vduModel.getModelUUID());
199         multicloudRequest.setVfModuleModelCustomizationId(vduModel.getModelCustomizationUUID());
200         multicloudRequest.setTemplateType(templateType);
201         multicloudRequest.setTemplateData(stack);
202         multicloudRequest.setOofDirectives(getDirectiveNode(oofDirectives));
203         multicloudRequest.setSdncDirectives(getDirectiveNode(sdncDirectives));
204         multicloudRequest.setUserDirectives(getDirectiveNode(userDirectives));
205         if (logger.isDebugEnabled()) {
206             logger.debug(String.format("Multicloud Request is: %s", multicloudRequest.toString()));
207         }
208
209         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, null);
210         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
211
212         if (multicloudClient == null) {
213             MsoOpenstackException me = new MsoOpenstackException(0, "", "Multicloud client could not be initialized");
214             me.addContext(CREATE_STACK);
215             throw me;
216         }
217
218         Response response = multicloudClient.post(multicloudRequest);
219
220         MulticloudCreateResponse multicloudResponseBody = null;
221         if (response.hasEntity()) {
222             multicloudResponseBody = getCreateBody((java.io.InputStream) response.getEntity());
223         }
224         if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
225             String canonicalName = stackName + "/";
226             if (multicloudResponseBody != null) {
227                 canonicalName = canonicalName + multicloudResponseBody.getWorkloadId();
228             }
229             if (logger.isDebugEnabled()) {
230                 logger.debug("Multicloud Create Response Body: {}", multicloudResponseBody);
231             }
232             StackInfo stackStatus = getStackStatus(cloudSiteId, cloudOwner, tenantId, canonicalName, pollForCompletion, timeoutMinutes, backout);
233
234             if (HeatStatus.CREATED.equals(stackStatus.getStatus())) {
235                 multicloudAaiUpdate(cloudSiteId, cloudOwner, tenantId, genericVnfId, vfModuleId, multicloudResponseBody.getWorkloadId(), pollForCompletion, timeoutMinutes);
236             }
237
238             return stackStatus;
239         }
240         StringBuilder stackErrorStatusReason = new StringBuilder(response.getStatusInfo().getReasonPhrase());
241         if (null != multicloudResponseBody) {
242             stackErrorStatusReason.append(multicloudResponseBody.toString());
243         }
244         MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
245         me.addContext(CREATE_STACK);
246         throw me;
247     }
248
249     @Override
250     public Map<String, Object> queryStackForOutputs(String cloudSiteId, String cloudOwner,
251                                                            String tenantId, String stackName) throws MsoException {
252         logger.debug("MsoHeatUtils.queryStackForOutputs)");
253         StackInfo heatStack = this.queryStack(cloudSiteId, cloudOwner, tenantId, stackName);
254         if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
255             return null;
256         }
257         return heatStack.getOutputs();
258     }
259
260     /**
261      * Query for a single stack (by ID) in a tenant. This call will always return a
262      * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
263      * returned - containing only the stack name and a status of NOTFOUND.
264      *
265      * @param tenantId The Openstack ID of the tenant in which to query
266      * @param cloudSiteId The cloud identifier (may be a region) in which to query
267      * @param cloudOwner cloud owner of the cloud site in which to query
268      * @param stackId The ID of the stack to query
269      * @return A StackInfo object
270      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
271      */
272     @Override
273     public StackInfo queryStack (String cloudSiteId, String cloudOwner, String tenantId, String instanceId) throws MsoException {
274         if (logger.isDebugEnabled()) {
275             logger.debug (String.format("Query multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
276         }
277         String stackName = null;
278         String stackId = null;
279         int offset = instanceId.indexOf('/');
280         if (offset > 0 && offset < (instanceId.length() - 1)) {
281             stackName = instanceId.substring(0, offset);
282             stackId = instanceId.substring(offset + 1);
283         } else {
284             stackName = instanceId;
285             stackId = instanceId;
286         }
287
288         StackInfo returnInfo = new StackInfo();
289         returnInfo.setName(stackName);
290
291         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId);
292         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
293
294         if (multicloudClient != null) {
295             Response response = multicloudClient.get();
296             if (logger.isDebugEnabled()) {
297                 logger.debug (String.format("Multicloud GET Response: %s", response.toString()));
298             }
299
300             MulticloudQueryResponse multicloudQueryBody = null;
301             if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
302                 returnInfo.setStatus(HeatStatus.NOTFOUND);
303                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
304             } else if (response.getStatus() == Response.Status.OK.getStatusCode() && response.hasEntity()) {
305                 multicloudQueryBody = getQueryBody((java.io.InputStream)response.getEntity());
306                 if (multicloudQueryBody != null) {
307                     returnInfo.setCanonicalName(stackName + "/" + multicloudQueryBody.getWorkloadId());
308                     returnInfo.setStatus(getHeatStatus(multicloudQueryBody.getWorkloadStatus()));
309                     returnInfo.setStatusMessage(multicloudQueryBody.getWorkloadStatus());
310                     if (logger.isDebugEnabled()) {
311                         logger.debug("Multicloud Create Response Body: " + multicloudQueryBody.toString());
312                     }
313                 } else {
314                     returnInfo.setStatus(HeatStatus.FAILED);
315                     returnInfo.setStatusMessage(MULTICLOUD_QUERY_BODY_NULL);
316                 }
317             } else {
318                 returnInfo.setStatus(HeatStatus.FAILED);
319                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
320             }
321         }
322
323         return returnInfo;
324     }
325
326     public StackInfo deleteStack (String cloudSiteId, String cloudOwner, String tenantId, String instanceId) throws MsoException {
327         if (logger.isDebugEnabled()) {
328             logger.debug (String.format("Delete multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
329         }
330         String stackName = null;
331         String stackId = null;
332         int offset = instanceId.indexOf('/');
333         if (offset > 0 && offset < (instanceId.length() - 1)) {
334             stackName = instanceId.substring(0, offset);
335             stackId = instanceId.substring(offset + 1);
336         } else {
337             stackName = instanceId;
338             stackId = instanceId;
339         }
340
341         StackInfo returnInfo = new StackInfo();
342         returnInfo.setName(stackName);
343         Response response = null;
344
345         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, stackId);
346         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
347
348         if (multicloudClient != null) {
349             response = multicloudClient.delete();
350             if (logger.isDebugEnabled()) {
351                 logger.debug(String.format("Multicloud Delete response is: %s", response.getEntity().toString()));
352             }
353
354             if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
355                 returnInfo.setStatus(HeatStatus.NOTFOUND);
356                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
357             } else if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
358                 return getStackStatus(cloudSiteId, cloudOwner, tenantId, instanceId);
359             } else {
360                 returnInfo.setStatus(HeatStatus.FAILED);
361                 returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
362             }
363
364         }
365         returnInfo.setStatus(mapResponseToHeatStatus(response));
366         return returnInfo;
367     }
368
369     // ---------------------------------------------------------------
370     // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
371
372     private HeatStatus getHeatStatus(String workloadStatus) {
373         if (workloadStatus.length() == 0) return HeatStatus.INIT;
374         if ("CREATE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.BUILDING;
375         if ("CREATE_COMPLETE".equals(workloadStatus)) return HeatStatus.CREATED;
376         if ("CREATE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
377         if ("DELETE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.DELETING;
378         if ("DELETE_COMPLETE".equals(workloadStatus)) return HeatStatus.NOTFOUND;
379         if ("DELETE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
380         if ("UPDATE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.UPDATING;
381         if ("UPDATE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
382         if ("UPDATE_COMPLETE".equals(workloadStatus)) return HeatStatus.UPDATED;
383         return HeatStatus.UNKNOWN;
384     }
385
386     private void multicloudAaiUpdate(String cloudSiteId, String cloudOwner, String tenantId, String genericVnfId, String vfModuleId, String workloadId,
387             boolean pollForCompletion, int timeoutMinutes) {
388
389         MulticloudRequest multicloudRequest= new MulticloudRequest();
390
391         multicloudRequest.setGenericVnfId(genericVnfId);
392         multicloudRequest.setVfModuleId(vfModuleId);
393
394         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, cloudOwner, workloadId);
395         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
396
397         if (multicloudClient == null) {
398             if (logger.isDebugEnabled())
399                 logger.debug("Multicloud client could not be initialized");
400         }
401
402         Response response = multicloudClient.post(multicloudRequest);
403         if (response.getStatus() != Response.Status.ACCEPTED.getStatusCode()) {
404             if (logger.isDebugEnabled())
405                 logger.debug("Multicloud AAI update request failed: " + response.getStatus() + response.getStatusInfo());
406             return;
407         }
408
409         if (!pollForCompletion) {
410             return;
411         }
412
413         int updatePollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
414         int pollTimeout = (timeoutMinutes * 60) + updatePollInterval;
415         boolean updateTimedOut = false;
416         logger.debug("updatePollInterval=" + updatePollInterval + ", pollTimeout=" + pollTimeout);
417
418         StackInfo stackInfo = null;
419         while (true) {
420             try {
421                 stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, workloadId);
422                 if (logger.isDebugEnabled())
423                     logger.debug (stackInfo.getStatus() + " (" + workloadId + ")");
424
425                 if (HeatStatus.UPDATING.equals(stackInfo.getStatus())) {
426                     if (pollTimeout <= 0) {
427                         // Note that this should not occur, since there is a timeout specified
428                         // in the Openstack (multicloud?) call.
429                         if (logger.isDebugEnabled())
430                             logger.debug("Multicloud AAI update timeout failure: {} {} {} {}", cloudOwner, cloudSiteId, tenantId, workloadId);
431                         updateTimedOut = true;
432                         break;
433                     }
434
435                     sleep(updatePollInterval * 1000L);
436
437                     pollTimeout -= updatePollInterval;
438                     if (logger.isDebugEnabled())
439                         logger.debug("pollTimeout remaining: " + pollTimeout);
440                 } else {
441                     break;
442                 }
443             } catch (MsoException me) {
444                 if (logger.isDebugEnabled())
445                     logger.debug("Multicloud AAI update exception: {} {} {} {}", cloudOwner, cloudSiteId, tenantId, workloadId, me);
446                 return;
447             }
448         }
449         if (updateTimedOut) {
450             if (logger.isDebugEnabled())
451                 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(), response.getStatusInfo().toString());
452         } else if (!HeatStatus.UPDATED.equals(stackInfo.getStatus())) {
453             if (logger.isDebugEnabled())
454                 logger.debug("Multicloud AAI update request failed: {} {}", response.getStatus(), response.getStatusInfo().toString());
455         } else {
456             if (logger.isDebugEnabled())
457                 logger.debug("Multicloud AAI update successful: {} {}", response.getStatus(), response.getStatusInfo().toString());
458         }
459     }
460
461     private StackInfo getStackStatus(String cloudSiteId, String cloudOwner, String tenantId, String instanceId) throws MsoException {
462         return getStackStatus(cloudSiteId, cloudOwner, tenantId, instanceId, false, 0, false);
463     }
464
465     private StackInfo getStackStatus(String cloudSiteId, String cloudOwner, String tenantId, String instanceId, boolean pollForCompletion, int timeoutMinutes, boolean backout) throws MsoException {
466         StackInfo stackInfo = new StackInfo();
467
468         // If client has requested a final response, poll for stack completion
469         if (pollForCompletion) {
470             // Set a time limit on overall polling.
471             // Use the resource (template) timeout for Openstack (expressed in minutes)
472             // and add one poll interval to give Openstack a chance to fail on its own.s
473
474             int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
475             int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
476             // New 1610 - poll on delete if we rollback - use same values for now
477             int deletePollInterval = createPollInterval;
478             int deletePollTimeout = pollTimeout;
479             boolean createTimedOut = false;
480             StringBuilder stackErrorStatusReason = new StringBuilder("");
481             logger.debug("createPollInterval=" + createPollInterval + ", pollTimeout=" + pollTimeout);
482
483             while (true) {
484                 try {
485                     stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
486                     logger.debug (stackInfo.getStatus() + " (" + instanceId + ")");
487
488                     if (HeatStatus.BUILDING.equals(stackInfo.getStatus())) {
489                         // Stack creation is still running.
490                         // Sleep and try again unless timeout has been reached
491                         if (pollTimeout <= 0) {
492                             // Note that this should not occur, since there is a timeout specified
493                             // in the Openstack (multicloud?) call.
494                             logger.error(String.format("%s %s %s %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId, tenantId, instanceId, stackInfo.getStatus(), "", "", ErrorCode.AvailabilityError.getValue(), "Create stack timeout"));
495                             createTimedOut = true;
496                             break;
497                         }
498
499                         sleep(createPollInterval * 1000L);
500
501                         pollTimeout -= createPollInterval;
502                         logger.debug("pollTimeout remaining: " + pollTimeout);
503                     } else {
504                         //save off the status & reason msg before we attempt delete
505                         stackErrorStatusReason.append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
506                         break;
507                     }
508                 } catch (MsoException me) {
509                     // Cannot query the stack status. Something is wrong.
510                     // Try to roll back the stack
511                     if (!backout) {
512                         logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack error, stack deletion suppressed", "", "", ErrorCode.BusinessProcesssError.getValue(), "Exception in Create Stack, stack deletion suppressed"));
513                     } else {
514                         try {
515                             logger.debug("Create Stack error - unable to query for stack status - attempting to delete stack: " + instanceId + " - This will likely fail and/or we won't be able to query to see if delete worked");
516                             StackInfo deleteInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
517                             // this may be a waste of time - if we just got an exception trying to query the stack - we'll just
518                             // get another one, n'est-ce pas?
519                             boolean deleted = false;
520                             while (!deleted) {
521                                 try {
522                                     StackInfo queryInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
523                                     logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
524                                     if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
525                                         if (deletePollTimeout <= 0) {
526                                             logger.error(String.format("%s %s %s %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId, tenantId, instanceId,
527                                                     queryInfo.getStatus(), "", "", ErrorCode.AvailabilityError.getValue(),
528                                                     "Rollback: DELETE stack timeout"));
529                                             break;
530                                         } else {
531                                             sleep(deletePollInterval * 1000L);
532                                             deletePollTimeout -= deletePollInterval;
533                                         }
534                                     } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())){
535                                         logger.debug("DELETE_COMPLETE for " + instanceId);
536                                         deleted = true;
537                                         continue;
538                                     } else {
539                                         //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
540                                         break;
541                                     }
542                                 } catch (Exception e3) {
543                                     // Just log this one. We will report the original exception.
544                                     logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack: Nested exception rolling back stack: " + e3, "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack: Nested exception rolling back stack on error on query"));
545                                 }
546                             }
547                         } catch (Exception e2) {
548                             // Just log this one. We will report the original exception.
549                             logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack: Nested exception rolling back stack: " + e2, "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack: Nested exception rolling back stack"));
550                         }
551                     }
552
553                     // Propagate the original exception from Stack Query.
554                     me.addContext (CREATE_STACK);
555                     throw me;
556                 }
557             }
558
559             if (!HeatStatus.CREATED.equals(stackInfo.getStatus())) {
560                 logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack error:  Polling complete with non-success status: "
561                               + stackInfo.getStatus () + ", " + stackInfo.getStatusMessage(), "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack error"));
562
563                 // Rollback the stack creation, since it is in an indeterminate state.
564                 if (!backout) {
565                     logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack errored, stack deletion suppressed", "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack error, stack deletion suppressed"));
566                 }
567                 else
568                 {
569                     try {
570                         logger.debug("Create Stack errored - attempting to DELETE stack: " + instanceId);
571                         logger.debug("deletePollInterval=" + deletePollInterval + ", deletePollTimeout=" + deletePollTimeout);
572                         StackInfo deleteInfo = deleteStack(cloudSiteId, cloudOwner, tenantId, instanceId);
573                         boolean deleted = false;
574                         while (!deleted) {
575                             try {
576                                 StackInfo queryInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
577                                 logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
578                                 if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
579                                     if (deletePollTimeout <= 0) {
580                                         logger.error(String.format("%s %s %s %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudOwner, cloudSiteId, tenantId, instanceId,
581                                                 queryInfo.getStatus(), "", "", ErrorCode.AvailabilityError.getValue(),
582                                                 "Rollback: DELETE stack timeout"));
583                                         break;
584                                     } else {
585                                         sleep(deletePollInterval * 1000L);
586                                         deletePollTimeout -= deletePollInterval;
587                                     }
588                                 } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())){
589                                     logger.debug("DELETE_COMPLETE for " + instanceId);
590                                     deleted = true;
591                                     continue;
592                                 } else {
593                                     //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
594                                     logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack errored, stack deletion FAILED", "", "", ErrorCode.BusinessProcesssError.getValue(), "Create Stack error, stack deletion FAILED"));
595                                     logger.debug("Stack deletion FAILED on a rollback of a create - " + instanceId + ", status=" + queryInfo.getStatus() + ", reason=" + queryInfo.getStatusMessage());
596                                     break;
597                                 }
598                             } catch (MsoException me2) {
599                                 // Just log this one. We will report the original exception.
600                                 logger.debug("Exception thrown trying to delete " + instanceId + " on a create->rollback: " + me2.getContextMessage(), me2);
601                                 logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack errored, then stack deletion FAILED - exception thrown", "", "", ErrorCode.BusinessProcesssError.getValue(), me2.getContextMessage()));
602                             }
603                         }
604                         StringBuilder errorContextMessage;
605                         if (createTimedOut) {
606                             errorContextMessage = new StringBuilder("Stack Creation Timeout");
607                         } else {
608                             errorContextMessage  = stackErrorStatusReason;
609                         }
610                         if (deleted) {
611                             errorContextMessage.append(" - stack successfully deleted");
612                         } else {
613                             errorContextMessage.append(" - encountered an error trying to delete the stack");
614                         }
615                     } catch (MsoException e2) {
616                         // shouldn't happen - but handle
617                         logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack: Nested exception rolling back stack: " + e2, "", "", ErrorCode.BusinessProcesssError.getValue(), "Exception in Create Stack: rolling back stack"));
618                     }
619                 }
620                 MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
621                 me.addContext(CREATE_STACK);
622                 throw me;
623             }
624         } else {
625             // Get initial status, since it will have been null after the create.
626             stackInfo = queryStack(cloudSiteId, cloudOwner, tenantId, instanceId);
627             logger.debug("Multicloud stack query status is: " + stackInfo.getStatus());
628         }
629         return stackInfo;
630     }
631
632     private HeatStatus mapResponseToHeatStatus(Response response) {
633         if (response == null) {
634             return HeatStatus.FAILED;
635         } else if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) {
636             return HeatStatus.CREATED;
637         } else if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
638             return HeatStatus.CREATED;
639         } else if (response.getStatusInfo().getStatusCode() == Response.Status.NO_CONTENT.getStatusCode()) {
640             return HeatStatus.CREATED;
641         } else if (response.getStatusInfo().getStatusCode() == Response.Status.BAD_REQUEST.getStatusCode()) {
642             return HeatStatus.FAILED;
643         } else if (response.getStatusInfo().getStatusCode() == Response.Status.UNAUTHORIZED.getStatusCode()) {
644             return HeatStatus.FAILED;
645         } else if (response.getStatusInfo().getStatusCode() == Response.Status.NOT_FOUND.getStatusCode()) {
646             return HeatStatus.NOTFOUND;
647         } else if (response.getStatusInfo().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
648             return HeatStatus.FAILED;
649         } else {
650             return HeatStatus.UNKNOWN;
651         }
652     }
653
654     private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
655         Scanner scanner = new Scanner(in);
656         scanner.useDelimiter("\\Z");
657         String body = "";
658         if (scanner.hasNext()) {
659             body = scanner.next();
660         }
661         scanner.close();
662
663         try {
664             return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
665         } catch (Exception e) {
666             logger.debug("Exception retrieving multicloud vfModule POST response body " + e);
667         }
668         return null;
669     }
670
671     private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
672         Scanner scanner = new Scanner(in);
673         scanner.useDelimiter("\\Z");
674         String body = "";
675         if (scanner.hasNext()) {
676             body = scanner.next();
677         }
678         scanner.close();
679
680         try {
681             return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
682         } catch (Exception e) {
683             logger.debug("Exception retrieving multicloud workload query response body " + e);
684         }
685         return null;
686     }
687
688     private String getMulticloudEndpoint(String cloudSiteId, String cloudOwner, String workloadId) {
689         String msbIp = System.getenv().get(ONAP_IP);
690         if (null == msbIp || msbIp.isEmpty()) {
691             msbIp = environment.getProperty("mso.msb-ip", DEFAULT_MSB_IP);
692         }
693         Integer msbPort = environment.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
694
695         String path = "/api/multicloud/v1/" + cloudOwner + "/" + cloudSiteId + "/infra_workload";
696
697         String endpoint = UriBuilder.fromPath(path).host(msbIp).port(msbPort).scheme("http").build().toString();
698         if (workloadId != null) {
699             if (logger.isDebugEnabled()) {
700                 logger.debug(String.format("Multicloud Endpoint is: %s/%s", endpoint, workloadId));
701             }
702             return String.format("%s/%s", endpoint, workloadId);
703         } else {
704             if (logger.isDebugEnabled()) {
705                 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
706             }
707             return endpoint;
708         }
709     }
710
711     private RestClient getMulticloudClient(String endpoint) {
712         RestClient client = null;
713         try {
714             client = httpClientFactory.newJsonClient(
715                 new URL(endpoint),
716                 TargetEntity.MULTICLOUD);
717         } catch (MalformedURLException e) {
718             logger.debug(String.format("Encountered malformed URL error getting multicloud rest client %s", e.getMessage()));
719         } catch (IllegalArgumentException e) {
720             logger.debug(String.format("Encountered illegal argument getting multicloud rest client %s",e.getMessage()));
721         } catch (UriBuilderException e) {
722             logger.debug(String.format("Encountered URI builder error getting multicloud rest client %s", e.getMessage()));
723         }
724         return client;
725     }
726
727     private JsonNode getDirectiveNode(String directives) throws MsoException {
728         try {
729             return JSON_MAPPER.readTree(directives);
730         } catch (Exception e) {
731             logger.error(String.format("%s %s %s %s %d %s",
732                     MessageEnum.RA_CREATE_STACK_ERR.toString(),
733                     "Create Stack: " + e, "", "",
734                     ErrorCode.BusinessProcesssError.getValue(),
735                     "Exception in Create Stack: Invalid JSON format of directives" + directives));
736             MsoException me = new MsoAdapterException("Invalid JSON format of directives parameter: " + directives);
737             me.addContext(CREATE_STACK);
738             throw me;
739         }
740     }
741
742     /**
743      * VduPlugin interface for instantiate function.
744      *
745      * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
746      * and then invoke the existing function.
747      */
748     @Override
749     public VduInstance instantiateVdu (
750             CloudInfo cloudInfo,
751             String instanceName,
752             Map<String,Object> inputs,
753             VduModelInfo vduModel,
754             boolean rollbackOnFailure)
755         throws VduException
756     {
757         String cloudSiteId = cloudInfo.getCloudSiteId();
758         String cloudOwner = cloudInfo.getCloudOwner();
759         String tenantId = cloudInfo.getTenantId();
760
761         // Translate the VDU ModelInformation structure to that which is needed for
762         // creating the Heat stack.  Loop through the artifacts, looking specifically
763         // for MAIN_TEMPLATE and ENVIRONMENT.  Any other artifact will
764         // be attached as a FILE.
765         String heatTemplate = null;
766         Map<String,Object> nestedTemplates = new HashMap<>();
767         Map<String,Object> files = new HashMap<>();
768         String heatEnvironment = null;
769
770         for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
771             if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
772                 heatTemplate = new String(vduArtifact.getContent());
773             }
774             else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
775                 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
776             }
777             else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
778                 heatEnvironment = new String(vduArtifact.getContent());
779             }
780         }
781
782         try {
783             StackInfo stackInfo = createStack (cloudSiteId,
784                     cloudOwner,
785                     tenantId,
786                     instanceName,
787                     vduModel,
788                     heatTemplate,
789                     inputs,
790                     true,    // poll for completion
791                     vduModel.getTimeoutMinutes(),
792                     heatEnvironment,
793                     nestedTemplates,
794                     files,
795                     rollbackOnFailure);
796             // Populate a vduInstance from the StackInfo
797             return stackInfoToVduInstance(stackInfo);
798         }
799         catch (Exception e) {
800             throw new VduException ("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
801         }
802     }
803
804
805     /**
806      * VduPlugin interface for query function.
807      */
808     @Override
809     public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
810         throws VduException
811     {
812         String cloudSiteId = cloudInfo.getCloudSiteId();
813         String cloudOwner = cloudInfo.getCloudOwner();
814         String tenantId = cloudInfo.getTenantId();
815
816         try {
817             // Query the Cloudify Deployment object and  populate a VduInstance
818             StackInfo stackInfo = queryStack (cloudSiteId, cloudOwner, tenantId, instanceId);
819
820             return stackInfoToVduInstance(stackInfo);
821         }
822         catch (Exception e) {
823             throw new VduException ("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
824         }
825     }
826
827
828     /**
829      * VduPlugin interface for delete function.
830      */
831     @Override
832     public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
833         throws VduException
834     {
835         String cloudSiteId = cloudInfo.getCloudSiteId();
836         String cloudOwner = cloudInfo.getCloudOwner();
837         String tenantId = cloudInfo.getTenantId();
838
839         try {
840             // Delete the Multicloud stack
841             StackInfo stackInfo = deleteStack (cloudSiteId, cloudOwner, tenantId, instanceId);
842
843             // Populate a VduInstance based on the deleted Cloudify Deployment object
844             VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
845
846             // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
847             vduInstance.getStatus().setState(VduStateType.DELETED);
848
849             return vduInstance;
850         }
851         catch (Exception e) {
852             throw new VduException ("Delete VDU Exception", e);
853         }
854     }
855
856
857     /**
858      * VduPlugin interface for update function.
859      *
860      * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin.
861      * Just return a VduException.
862      *
863      */
864     @Override
865     public VduInstance updateVdu (
866             CloudInfo cloudInfo,
867             String instanceId,
868             Map<String,Object> inputs,
869             VduModelInfo vduModel,
870             boolean rollbackOnFailure)
871         throws VduException
872     {
873         throw new VduException ("MsoMulticloudUtils: updateVdu interface not supported");
874     }
875
876
877     /*
878      * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
879      */
880     protected VduInstance stackInfoToVduInstance (StackInfo stackInfo)
881     {
882         VduInstance vduInstance = new VduInstance();
883
884         if (logger.isDebugEnabled()) {
885             logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
886         }
887         // The full canonical name as the instance UUID
888         vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
889         vduInstance.setVduInstanceName(stackInfo.getName());
890
891         // Copy inputs and outputs
892         vduInstance.setInputs(stackInfo.getParameters());
893         vduInstance.setOutputs(stackInfo.getOutputs());
894
895         // Translate the status elements
896         vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
897
898         return vduInstance;
899     }
900
901     private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
902     {
903         VduStatus vduStatus = new VduStatus();
904
905         // Map the status fields to more generic VduStatus.
906         // There are lots of HeatStatus values, so this is a bit long...
907         HeatStatus heatStatus = stackInfo.getStatus();
908         String statusMessage = stackInfo.getStatusMessage();
909         logger.debug("HeatStatus = " + heatStatus + " msg = " + statusMessage);
910
911         if (logger.isDebugEnabled()) {
912             logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
913             logger.debug(String.format("Stack Status Message: %s", statusMessage));
914         }
915
916         if (heatStatus == HeatStatus.INIT  ||  heatStatus == HeatStatus.BUILDING) {
917             vduStatus.setState(VduStateType.INSTANTIATING);
918             vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
919         }
920         else if (heatStatus == HeatStatus.NOTFOUND) {
921             vduStatus.setState(VduStateType.NOTFOUND);
922         }
923         else if (heatStatus == HeatStatus.CREATED) {
924             vduStatus.setState(VduStateType.INSTANTIATED);
925             vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
926         }
927         else if (heatStatus == HeatStatus.UPDATED) {
928             vduStatus.setState(VduStateType.INSTANTIATED);
929             vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
930         }
931         else if (heatStatus == HeatStatus.UPDATING) {
932             vduStatus.setState(VduStateType.UPDATING);
933             vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
934         }
935         else if (heatStatus == HeatStatus.DELETING) {
936             vduStatus.setState(VduStateType.DELETING);
937             vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
938         }
939         else if (heatStatus == HeatStatus.FAILED) {
940             vduStatus.setState(VduStateType.FAILED);
941             vduStatus.setErrorMessage(stackInfo.getStatusMessage());
942         } else {
943             vduStatus.setState(VduStateType.UNKNOWN);
944         }
945
946         return vduStatus;
947     }
948 }