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