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