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