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