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