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