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