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