Merge "Add junit tests for CallbackHandlerService"
[so.git] / adapters / mso-adapter-utils / src / main / java / org / onap / so / openstack / utils / MsoHeatUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Modifications Copyright (c) 2019 Samsung
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.so.openstack.utils;
25
26 import com.fasterxml.jackson.databind.JsonNode;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import com.woorea.openstack.base.client.OpenStackConnectException;
29 import com.woorea.openstack.base.client.OpenStackRequest;
30 import com.woorea.openstack.base.client.OpenStackResponseException;
31 import com.woorea.openstack.heat.Heat;
32 import com.woorea.openstack.heat.model.CreateStackParam;
33 import com.woorea.openstack.heat.model.Resources;
34 import com.woorea.openstack.heat.model.Stack;
35 import com.woorea.openstack.heat.model.Stack.Output;
36 import com.woorea.openstack.heat.model.Stacks;
37 import com.woorea.openstack.keystone.Keystone;
38 import com.woorea.openstack.keystone.model.Access;
39 import com.woorea.openstack.keystone.model.Authentication;
40 import com.woorea.openstack.keystone.utils.KeystoneUtils;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Calendar;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Map.Entry;
48 import java.util.Set;
49 import org.onap.so.adapters.vdu.CloudInfo;
50 import org.onap.so.adapters.vdu.PluginAction;
51 import org.onap.so.adapters.vdu.VduArtifact;
52 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
53 import org.onap.so.adapters.vdu.VduException;
54 import org.onap.so.adapters.vdu.VduInstance;
55 import org.onap.so.adapters.vdu.VduModelInfo;
56 import org.onap.so.adapters.vdu.VduPlugin;
57 import org.onap.so.adapters.vdu.VduStateType;
58 import org.onap.so.adapters.vdu.VduStatus;
59 import org.onap.so.cloud.CloudConfig;
60 import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
61 import org.onap.so.cloud.authentication.KeystoneAuthHolder;
62 import org.onap.so.cloud.authentication.KeystoneV3Authentication;
63 import org.onap.so.cloud.authentication.ServiceEndpointNotFoundException;
64 import org.onap.so.db.catalog.beans.CloudIdentity;
65 import org.onap.so.db.catalog.beans.CloudSite;
66 import org.onap.so.db.catalog.beans.HeatTemplate;
67 import org.onap.so.db.catalog.beans.HeatTemplateParam;
68 import org.onap.so.db.catalog.beans.ServerType;
69 import org.onap.so.logger.MessageEnum;
70 import org.onap.so.logger.MsoLogger;
71 import org.onap.so.openstack.beans.HeatStatus;
72 import org.onap.so.openstack.beans.StackInfo;
73 import org.onap.so.openstack.exceptions.MsoAdapterException;
74 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
75 import org.onap.so.openstack.exceptions.MsoException;
76 import org.onap.so.openstack.exceptions.MsoIOException;
77 import org.onap.so.openstack.exceptions.MsoOpenstackException;
78 import org.onap.so.openstack.exceptions.MsoStackAlreadyExists;
79 import org.onap.so.openstack.exceptions.MsoTenantNotFound;
80 import org.onap.so.openstack.mappers.StackInfoMapper;
81 import org.onap.so.utils.CryptoUtils;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
84 import org.springframework.beans.factory.annotation.Autowired;
85 import org.springframework.context.annotation.Primary;
86 import org.springframework.core.env.Environment;
87 import org.springframework.stereotype.Component;
88
89 import com.fasterxml.jackson.core.JsonProcessingException;
90 import com.fasterxml.jackson.core.type.TypeReference;
91 import com.fasterxml.jackson.databind.JsonNode;
92 import com.fasterxml.jackson.databind.ObjectMapper;
93 import com.woorea.openstack.base.client.OpenStackConnectException;
94 import com.woorea.openstack.base.client.OpenStackRequest;
95 import com.woorea.openstack.base.client.OpenStackResponseException;
96 import com.woorea.openstack.heat.Heat;
97 import com.woorea.openstack.heat.model.CreateStackParam;
98 import com.woorea.openstack.heat.model.Resources;
99 import com.woorea.openstack.heat.model.Stack;
100 import com.woorea.openstack.heat.model.Stack.Output;
101 import com.woorea.openstack.heat.model.Stacks;
102 import com.woorea.openstack.keystone.Keystone;
103 import com.woorea.openstack.keystone.model.Access;
104 import com.woorea.openstack.keystone.model.Authentication;
105 import com.woorea.openstack.keystone.utils.KeystoneUtils;
106
107 @Primary
108 @Component
109 public class MsoHeatUtils extends MsoCommonUtils implements VduPlugin{
110
111     private static final String TOKEN_AUTH = "TokenAuth";
112
113     private static final String QUERY_ALL_STACKS = "QueryAllStacks";
114
115     private static final String DELETE_STACK = "DeleteStack";
116
117     protected static final String HEAT_ERROR = "HeatError";
118
119     protected static final String CREATE_STACK = "CreateStack";
120
121     // Fetch cloud configuration each time (may be cached in CloudConfig class)
122     @Autowired
123     protected CloudConfig cloudConfig;
124
125     @Autowired
126     private Environment environment;
127
128     @Autowired
129     private AuthenticationMethodFactory authenticationMethodFactory;
130
131     @Autowired
132     private MsoTenantUtilsFactory tenantUtilsFactory;
133     
134     @Autowired
135     private KeystoneV3Authentication keystoneV3Authentication;
136     
137     private static final Logger logger = LoggerFactory.getLogger(MsoHeatUtils.class);
138
139     // Properties names and variables (with default values)
140     protected String createPollIntervalProp = "org.onap.so.adapters.po.pollInterval";
141     private String deletePollIntervalProp = "org.onap.so.adapters.po.pollInterval";
142     private String deletePollTimeoutProp = "org.onap.so.adapters.po.pollTimeout";
143
144     protected static final String createPollIntervalDefault = "15";
145     private static final String deletePollIntervalDefault = "15";
146
147     private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
148
149     /**
150      * keep this old method signature here to maintain backwards compatibility. keep others as well.
151      * this method does not include environment, files, or heatFiles
152      */
153     public StackInfo createStack (String cloudSiteId,
154                                   String tenantId,
155                                   String stackName,
156                                   String heatTemplate,
157                                   Map <String, ?> stackInputs,
158                                   boolean pollForCompletion,
159                                   int timeoutMinutes) throws MsoException {
160         // Just call the new method with the environment & files variable set to null
161         return this.createStack (cloudSiteId,
162                                  tenantId,
163                                  stackName,
164                                  heatTemplate,
165                                  stackInputs,
166                                  pollForCompletion,
167                                  timeoutMinutes,
168                                  null,
169                                  null,
170                                  null,
171                                  true);
172     }
173
174     // This method has environment, but not files or heatFiles
175     public StackInfo createStack (String cloudSiteId,
176                                   String tenantId,
177                                   String stackName,
178                                   String heatTemplate,
179                                   Map <String, ?> stackInputs,
180                                   boolean pollForCompletion,
181                                   int timeoutMinutes,
182                                   String environment) throws MsoException {
183         // Just call the new method with the files/heatFiles variables set to null
184         return this.createStack (cloudSiteId,
185                                  tenantId,
186                                  stackName,
187                                  heatTemplate,
188                                  stackInputs,
189                                  pollForCompletion,
190                                  timeoutMinutes,
191                                  environment,
192                                  null,
193                                  null,
194                                  true);
195     }
196
197     // This method has environment and files, but not heatFiles.
198     public StackInfo createStack (String cloudSiteId,
199                                   String tenantId,
200                                   String stackName,
201                                   String heatTemplate,
202                                   Map <String, ?> stackInputs,
203                                   boolean pollForCompletion,
204                                   int timeoutMinutes,
205                                   String environment,
206                                   Map <String, Object> files) throws MsoException {
207         return this.createStack (cloudSiteId,
208                                  tenantId,
209                                  stackName,
210                                  heatTemplate,
211                                  stackInputs,
212                                  pollForCompletion,
213                                  timeoutMinutes,
214                                  environment,
215                                  files,
216                                  null,
217                                  true);
218     }
219
220     // This method has environment, files, heatfiles
221     public StackInfo createStack (String cloudSiteId,
222                                   String tenantId,
223                                   String stackName,
224                                   String heatTemplate,
225                                   Map <String, ?> stackInputs,
226                                   boolean pollForCompletion,
227                                   int timeoutMinutes,
228                                   String environment,
229                                   Map <String, Object> files,
230                                   Map <String, Object> heatFiles) throws MsoException {
231         return this.createStack (cloudSiteId,
232                                  tenantId,
233                                  stackName,
234                                  heatTemplate,
235                                  stackInputs,
236                                  pollForCompletion,
237                                  timeoutMinutes,
238                                  environment,
239                                  files,
240                                  heatFiles,
241                                  true);
242     }
243
244     /**
245      * Create a new Stack in the specified cloud location and tenant. The Heat template
246      * and parameter map are passed in as arguments, along with the cloud access credentials.
247      * It is expected that parameters have been validated and contain at minimum the required
248      * parameters for the given template with no extra (undefined) parameters..
249      *
250      * The Stack name supplied by the caller must be unique in the scope of this tenant.
251      * However, it should also be globally unique, as it will be the identifier for the
252      * resource going forward in Inventory. This latter is managed by the higher levels
253      * invoking this function.
254      *
255      * The caller may choose to let this function poll Openstack for completion of the
256      * stack creation, or may handle polling itself via separate calls to query the status.
257      * In either case, a StackInfo object will be returned containing the current status.
258      * When polling is enabled, a status of CREATED is expected. When not polling, a
259      * status of BUILDING is expected.
260      *
261      * An error will be thrown if the requested Stack already exists in the specified
262      * Tenant and Cloud.
263      *
264      * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as
265      * parameters for createStack. If environment is non-null, it will be added to the stack.
266      * The nested templates and get_file entries both end up being added to the "files" on the
267      * stack. We must combine them before we add them to the stack if they're both non-null.
268      *
269      * @param cloudSiteId The cloud (may be a region) in which to create the stack.
270      * @param tenantId The Openstack ID of the tenant in which to create the Stack
271      * @param stackName The name of the stack to create
272      * @param heatTemplate The Heat template
273      * @param stackInputs A map of key/value inputs
274      * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
275      * @param environment An optional yaml-format string to specify environmental parameters
276      * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
277      *        Template id)
278      * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
279      * @param backout Donot delete stack on create Failure - defaulted to True
280      * @return A StackInfo object
281      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
282      */
283
284     @SuppressWarnings("unchecked")
285     public StackInfo createStack (String cloudSiteId,
286                                   String tenantId,
287                                   String stackName,
288                                   String heatTemplate,
289                                   Map <String, ?> stackInputs,
290                                   boolean pollForCompletion,
291                                   int timeoutMinutes,
292                                   String environment,
293                                   Map <String, Object> files,
294                                   Map <String, Object> heatFiles,
295                                   boolean backout) throws MsoException {
296
297         // Take out the multicloud inputs, if present.
298         for (String key : MsoMulticloudUtils.MULTICLOUD_INPUTS) {
299             if (stackInputs.containsKey(key)) {
300                 stackInputs.remove(key);
301                 if (stackInputs.isEmpty()) {
302                     break;
303                 }
304             }
305         }
306
307         CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
308
309         // Obtain the cloud site information where we will create the stack
310         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
311                 () -> new MsoCloudSiteNotFound(cloudSiteId));
312         logger.debug("Found: {}", cloudSite);
313         // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
314         // This could throw MsoTenantNotFound or MsoOpenstackException (both propagated)
315         Heat heatClient = getHeatClient (cloudSite, tenantId);
316         logger.debug("Found: {}", heatClient);
317
318         logger.debug ("Ready to Create Stack ({}) with input params: {}", heatTemplate, stackInputs);
319
320         Stack heatStack = null;
321         try {
322             OpenStackRequest <Stack> request = heatClient.getStacks ().create (stack);
323             CloudIdentity cloudIdentity = cloudSite.getIdentityService();
324             request.header ("X-Auth-User", cloudIdentity.getMsoId ());
325             request.header ("X-Auth-Key", CryptoUtils.decryptCloudConfigPassword(cloudIdentity.getMsoPass ()));
326             heatStack = executeAndRecordOpenstackRequest (request);
327         } catch (OpenStackResponseException e) {
328             if (e.getStatus () == 409) {                
329                 MsoStackAlreadyExists me = new MsoStackAlreadyExists (stackName, tenantId, cloudSiteId);
330                 me.addContext (CREATE_STACK);
331                 throw me;
332         } else {
333                 logger.debug("ERROR STATUS = {},\n{}\n{}", e.getStatus(), e.getMessage(), e.getLocalizedMessage());
334                 throw heatExceptionToMsoException (e, CREATE_STACK);
335             }
336         } catch (OpenStackConnectException e) {          
337             throw heatExceptionToMsoException (e, CREATE_STACK);
338         } catch (RuntimeException e) {           
339             throw runtimeExceptionToMsoException (e, CREATE_STACK);
340         }
341
342         // Subsequent access by the canonical name "<stack name>/<stack-id>".
343         // Otherwise, simple query by name returns a 302 redirect.
344         // NOTE: This is specific to the v1 Orchestration API.
345         String canonicalName = stackName + "/" + heatStack.getId ();
346        
347         if (pollForCompletion) {
348             heatStack = pollStackForCompletion(cloudSiteId, tenantId, stackName, timeoutMinutes, backout, heatClient,
349                                         heatStack, canonicalName);
350         } else {
351             // Get initial status, since it will have been null after the create.
352             heatStack = queryHeatStack (heatClient, canonicalName);
353             logger.debug (heatStack.getStackStatus ());
354         }
355         return new StackInfoMapper(heatStack).map();
356     }
357
358         private Stack pollStackForCompletion(String cloudSiteId, String tenantId, String stackName, int timeoutMinutes,
359                         boolean backout, Heat heatClient, Stack heatStack, String canonicalName)
360                         throws MsoException, MsoOpenstackException {
361                 int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
362                 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
363                 int deletePollInterval = createPollInterval;
364                 int deletePollTimeout = pollTimeout;
365                 boolean createTimedOut = false;
366                 StringBuilder stackErrorStatusReason = new StringBuilder("");
367       logger.debug("createPollInterval={}, pollTimeout={}", createPollInterval, pollTimeout);
368
369                 while (true) {
370                     try {
371                         heatStack = queryHeatStack (heatClient, canonicalName);
372             logger.debug("{} ({})", heatStack.getStackStatus(), canonicalName);
373             try {
374                 logger.debug("Current stack {}", this.getOutputsAsStringBuilder(heatStack).toString());
375             } catch (Exception e) {
376                             logger.debug("an error occurred trying to print out the current outputs of the stack", e);
377                         }
378
379                         if ("CREATE_IN_PROGRESS".equals (heatStack.getStackStatus ())) {                       
380                             if (pollTimeout <= 0) {
381                     logger.error("{} Cloud site: {} Tenant: {} Stack: {} Stack status: {} {} Create stack timeout",
382                         MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
383                         heatStack.getStackStatus(), MsoLogger.ErrorCode.AvailabilityError.getValue());
384                     createTimedOut = true;
385                                 break;
386                             }
387                             sleep(createPollInterval * 1000L);
388                             pollTimeout -= createPollInterval;
389                 logger.debug("pollTimeout remaining: {}", pollTimeout);
390             } else {
391                                 stackErrorStatusReason.append("Stack error (" + heatStack.getStackStatus() + "): " + heatStack.getStackStatusReason());
392                             break;
393                         }
394                     } catch (MsoException me) {
395                         // Cannot query the stack status. Something is wrong.
396                         // Try to roll back the stack
397                         if (!backout)
398                         {
399               logger.warn("{} Exception in Create Stack, stack deletion suppressed {}",
400                   MessageEnum.RA_CREATE_STACK_ERR, MsoLogger.ErrorCode.BusinessProcesssError.getValue());
401           }
402                         else
403                         {
404                                 try {
405                 logger.debug(
406                     "Create Stack error - unable to query for stack status - attempting to delete stack: {}"
407                         + " - This will likely fail and/or we won't be able to query to see if delete worked",
408                     canonicalName);
409                 OpenStackRequest <Void> request = heatClient.getStacks ().deleteByName (canonicalName);
410                                         executeAndRecordOpenstackRequest (request);
411                                         boolean deleted = false;
412                                         while (!deleted) {
413                                                 try {
414                                                         heatStack = queryHeatStack(heatClient, canonicalName);
415                                                         if (heatStack != null) {
416                       logger.debug(heatStack.getStackStatus());
417                       if ("DELETE_IN_PROGRESS".equals(heatStack.getStackStatus())) {
418                                                                         if (deletePollTimeout <= 0) {
419                             logger.error(
420                                 "{} Cloud site: {} Tenant: {} Stack: {} Stack status: {} {} Rollback: DELETE stack timeout",
421                                 MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
422                                 heatStack.getStackStatus(), MsoLogger.ErrorCode.AvailabilityError.getValue());
423                             break;
424                                                                         } else {
425                                                                                 sleep(deletePollInterval * 1000L);
426                                                                                 deletePollTimeout -= deletePollInterval;
427                                                                         }
428                                                                 } else if ("DELETE_COMPLETE".equals(heatStack.getStackStatus())){
429                           logger.debug("DELETE_COMPLETE for {}", canonicalName);
430                           deleted = true;
431                                                                         continue;
432                                                                 } else {
433                                                                         //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
434                                                                         break;
435                                                                 }
436                                                         } else {
437                                                                 // assume if we can't find it - it's deleted
438                       logger.debug("heatStack returned null - assume the stack {} has been deleted", canonicalName);
439                       deleted = true;
440                                                                 continue;
441                                                         }
442
443                                                 } catch (Exception e3) {
444                                                         // Just log this one. We will report the original exception.
445                     logger.error("{} Create Stack: Nested exception rolling back stack: {} ",
446                         MessageEnum.RA_CREATE_STACK_ERR, MsoLogger.ErrorCode.BusinessProcesssError.getValue(), e3);
447                                                 }
448                                         }
449                                 } catch (Exception e2) {
450                                         // Just log this one. We will report the original exception.
451                 logger.error("{} Create Stack: Nested exception rolling back stack: {} ",
452                     MessageEnum.RA_CREATE_STACK_ERR, MsoLogger.ErrorCode.BusinessProcesssError.getValue(), e2);
453             }
454                         }
455
456                         // Propagate the original exception from Stack Query.
457                         me.addContext (CREATE_STACK);
458                         throw me;
459                     }
460                 }
461
462                 if (!"CREATE_COMPLETE".equals (heatStack.getStackStatus ())) {
463         logger.error("{} Create Stack error:  Polling complete with non-success status: {}, {} {} ",
464             MessageEnum.RA_CREATE_STACK_ERR, heatStack.getStackStatus(), heatStack.getStackStatusReason(),
465             MsoLogger.ErrorCode.BusinessProcesssError.getValue());
466
467                     // Rollback the stack creation, since it is in an indeterminate state.
468                     if (!backout)
469                     {
470             logger.warn(
471                 "{} Create Stack errored, stack deletion suppressed {} Create Stack error, stack deletion suppressed",
472                 MessageEnum.RA_CREATE_STACK_ERR, MsoLogger.ErrorCode.BusinessProcesssError.getValue());
473         }
474                     else
475                     {
476                         try {
477               logger.debug("Create Stack errored - attempting to DELETE stack: {}", canonicalName);
478               logger.debug("deletePollInterval={}, deletePollTimeout={}", deletePollInterval, deletePollTimeout);
479               OpenStackRequest <Void> request = heatClient.getStacks ().deleteByName (canonicalName);
480                                 executeAndRecordOpenstackRequest (request);
481                                 boolean deleted = false;
482                                 while (!deleted) {
483                                         try {
484                                                 heatStack = queryHeatStack(heatClient, canonicalName);
485                                                 if (heatStack != null) {
486                     logger.debug("{} ({})", heatStack.getStackStatus(), canonicalName);
487                     if ("DELETE_IN_PROGRESS".equals(heatStack.getStackStatus())) {
488                                                                 if (deletePollTimeout <= 0) {
489                         logger.error(
490                             "{} Cloud site: {} Tenant: {} Stack: {} Stack status: {} {} Rollback: DELETE stack timeout",
491                             MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
492                             heatStack.getStackStatus(), MsoLogger.ErrorCode.AvailabilityError.getValue());
493                         break;
494                                                                 } else {
495                                                                         sleep(deletePollInterval * 1000L);
496                                                                         deletePollTimeout -= deletePollInterval;
497                         logger.debug("deletePollTimeout remaining: {}", deletePollTimeout);
498                     }
499                                                         } else if ("DELETE_COMPLETE".equals(heatStack.getStackStatus())){
500                       logger.debug("DELETE_COMPLETE for {}", canonicalName);
501                       deleted = true;
502                                                                 continue;
503                                                         } else if ("DELETE_FAILED".equals(heatStack.getStackStatus())) {
504                                                                 // Warn about this (?) - but still throw the original exception
505                       logger.warn(
506                           "{} Create Stack errored, stack deletion FAILED {} Create Stack error, stack deletion FAILED",
507                           MessageEnum.RA_CREATE_STACK_ERR, MsoLogger.ErrorCode.BusinessProcesssError.getValue());
508                       logger.debug("Stack deletion FAILED on a rollback of a create - {}, status={}, reason={}",
509                           canonicalName, heatStack.getStackStatus(), heatStack.getStackStatusReason());
510                       break;
511                                                         } else {
512                                                                 //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
513                                                                 break;
514                                                         }
515                                                 } else {
516                                                         // assume if we can't find it - it's deleted
517                     logger.debug("heatStack returned null - assume the stack {} has been deleted", canonicalName);
518                     deleted = true;
519                                                         continue;
520                                                 }
521
522                                         } catch (MsoException me2) {
523                                                 // We got an exception on the delete - don't throw this exception - throw the original - just log.
524                   logger.debug("Exception thrown trying to delete {} on a create->rollback: {} ", canonicalName,
525                       me2.getContextMessage(), me2);
526                   logger.warn("{} Create Stack errored, then stack deletion FAILED - exception thrown {} {}",
527                       MessageEnum.RA_CREATE_STACK_ERR, MsoLogger.ErrorCode.BusinessProcesssError.getValue(),
528                       me2.getContextMessage());
529               }
530
531                                 } // end while !deleted
532                                 StringBuilder errorContextMessage;
533                                 if (createTimedOut) {
534                                         errorContextMessage = new StringBuilder("Stack Creation Timeout");
535                                 } else {
536                                         errorContextMessage  = stackErrorStatusReason;
537                                 }
538                                 if (deleted) {
539                                         errorContextMessage.append(" - stack successfully deleted");
540                                 } else {
541                                         errorContextMessage.append(" - encountered an error trying to delete the stack");
542                                 }
543                         } catch (Exception e2) {
544                                 // shouldn't happen - but handle
545               logger.error("{} Create Stack: Nested exception rolling back stack: {} ", MessageEnum.RA_CREATE_STACK_ERR,
546                   MsoLogger.ErrorCode.BusinessProcesssError.getValue(), e2);
547           }
548                     }
549                     MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
550                     me.addContext(CREATE_STACK);
551                     throw me;
552                 }
553                 return heatStack;
554         }
555
556     /**
557      * Query for a single stack (by Name) in a tenant. This call will always return a
558      * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
559      * returned - containing only the stack name and a status of NOTFOUND.
560      *
561      * @param tenantId The Openstack ID of the tenant in which to query
562      * @param cloudSiteId The cloud identifier (may be a region) in which to query
563      * @param stackName The name of the stack to query (may be simple or canonical)
564      * @return A StackInfo object
565      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
566      */
567     public StackInfo queryStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
568         logger.debug ("Query HEAT stack: {} in tenant {}", stackName, tenantId);
569
570         // Obtain the cloud site information where we will create the stack
571         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
572                 () -> new MsoCloudSiteNotFound(cloudSiteId));
573         logger.debug("Found: {}", cloudSite.toString());
574
575         // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
576         Heat heatClient = null;
577         try {
578             heatClient = getHeatClient (cloudSite, tenantId);
579             if (heatClient != null) {
580                 logger.debug("Found: {}", heatClient.toString());
581             }
582         } catch (MsoTenantNotFound e) {
583             // Tenant doesn't exist, so stack doesn't either
584             logger.debug ("Tenant with id " + tenantId + "not found.", e);
585             return new StackInfo (stackName, HeatStatus.NOTFOUND);
586         } catch (MsoException me) {
587             // Got an Openstack error. Propagate it
588             logger.error("{} {} Openstack Exception on Token request: ", MessageEnum.RA_CONNECTION_EXCEPTION,
589                 MsoLogger.ErrorCode.AvailabilityError.getValue(), me);
590             me.addContext ("QueryStack");
591             throw me;
592         }
593
594         // Query the Stack.
595         // An MsoException will propagate transparently to the caller.
596         Stack heatStack = queryHeatStack (heatClient, stackName);
597
598         if (heatStack == null) {
599             // Stack does not exist. Return a StackInfo with status NOTFOUND
600             return new StackInfo (stackName, HeatStatus.NOTFOUND);
601         }
602
603         return new StackInfoMapper(heatStack).map();
604     }
605
606     /**
607      * Delete a stack (by Name/ID) in a tenant. If the stack is not found, it will be
608      * considered a successful deletion. The return value is a StackInfo object which
609      * contains the current stack status.
610      *
611      * The client may choose to let the adapter poll Openstack for completion of the
612      * stack deletion, or may handle polling itself via separate query calls. In either
613      * case, a StackInfo object will be returned. When polling is enabled, a final
614      * status of NOTFOUND is expected. When not polling, a status of DELETING is expected.
615      *
616      * There is no rollback from a successful stack deletion. A deletion failure will
617      * also result in an undefined stack state - the components may or may not have been
618      * all or partially deleted, so the resulting stack must be considered invalid.
619      *
620      * @param tenantId The Openstack ID of the tenant in which to perform the delete
621      * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
622      * @param stackName The name/id of the stack to delete. May be simple or canonical
623      * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
624      * @return A StackInfo object
625      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
626      * @throws MsoCloudSiteNotFound
627      */
628     public StackInfo deleteStack (String tenantId,
629                                   String cloudSiteId,
630                                   String stackName,
631                                   boolean pollForCompletion) throws MsoException {
632         // Obtain the cloud site information where we will create the stack
633         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
634                 () -> new MsoCloudSiteNotFound(cloudSiteId));
635         logger.debug("Found: {}", cloudSite.toString());
636
637         // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
638         Heat heatClient = null;
639         try {
640             heatClient = getHeatClient (cloudSite, tenantId);
641             if (heatClient != null) {
642                 logger.debug("Found: {}", heatClient.toString());
643             }
644         } catch (MsoTenantNotFound e) {
645             // Tenant doesn't exist, so stack doesn't either
646             logger.debug ("Tenant with id " + tenantId + "not found.", e);
647             return new StackInfo (stackName, HeatStatus.NOTFOUND);
648         } catch (MsoException me) {
649             // Got an Openstack error. Propagate it
650             logger.error("{} {} Openstack Exception on Token request: ", MessageEnum.RA_CONNECTION_EXCEPTION,
651                 MsoLogger.ErrorCode.AvailabilityError.getValue(), me);
652             me.addContext (DELETE_STACK);
653             throw me;
654         }
655
656         // OK if stack not found, perform a query first
657         Stack heatStack = queryHeatStack (heatClient, stackName);
658         if (heatStack == null || "DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
659             // Not found. Return a StackInfo with status NOTFOUND
660             return new StackInfo (stackName, HeatStatus.NOTFOUND);
661         }
662
663         // Delete the stack.
664
665         // Use canonical name "<stack name>/<stack-id>" to delete.
666         // Otherwise, deletion by name returns a 302 redirect.
667         // NOTE: This is specific to the v1 Orchestration API.
668         String canonicalName = heatStack.getStackName () + "/" + heatStack.getId ();
669
670         try {
671             OpenStackRequest <Void> request = null;
672             if(null != heatClient) {
673                 request = heatClient.getStacks ().deleteByName (canonicalName);
674             }
675             else {
676                 logger.debug ("Heat Client is NULL" );
677             }
678
679             executeAndRecordOpenstackRequest (request);
680         } catch (OpenStackResponseException e) {
681             if (e.getStatus () == 404) {
682                 // Not found. We are OK with this. Return a StackInfo with status NOTFOUND
683                 return new StackInfo (stackName, HeatStatus.NOTFOUND);
684             } else {
685                 // Convert the OpenStackResponseException to an MsoOpenstackException
686                 throw heatExceptionToMsoException (e, DELETE_STACK);
687             }
688         } catch (OpenStackConnectException e) {
689             // Error connecting to Openstack instance. Convert to an MsoException
690             throw heatExceptionToMsoException (e, DELETE_STACK);
691         } catch (RuntimeException e) {
692             // Catch-all
693             throw runtimeExceptionToMsoException (e, DELETE_STACK);
694         }
695
696         // Requery the stack for current status.
697         // It will probably still exist with "DELETE_IN_PROGRESS" status.
698         heatStack = queryHeatStack (heatClient, canonicalName);
699
700         if (pollForCompletion) {
701             // Set a timeout on polling
702
703             int pollInterval = Integer.parseInt(this.environment.getProperty(deletePollIntervalProp, "" + deletePollIntervalDefault));
704             int pollTimeout = Integer.parseInt(this.environment.getProperty(deletePollTimeoutProp, "" + deletePollIntervalDefault));
705
706             // When querying by canonical name, Openstack returns DELETE_COMPLETE status
707             // instead of "404" (which would result from query by stack name).
708             while (heatStack != null && !"DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
709                 logger.debug ("Stack status: {}", heatStack.getStackStatus ());
710
711                 if ("DELETE_FAILED".equals (heatStack.getStackStatus ())) {
712                     // Throw a 'special case' of MsoOpenstackException to report the Heat status
713                     String error = "Stack delete error (" + heatStack.getStackStatus ()
714                                    + "): "
715                                    + heatStack.getStackStatusReason ();
716                     MsoOpenstackException me = new MsoOpenstackException (0, "", error);
717                     me.addContext (DELETE_STACK);
718
719                     // Alarm this condition, stack deletion failed
720
721
722                     throw me;
723                 }
724
725                 if (pollTimeout <= 0) {
726                     logger.error("{} Cloud site: {} Tenant: {} Stack: {} Stack status: {} {} Delete Stack Timeout",
727                         MessageEnum.RA_DELETE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
728                         heatStack.getStackStatus(), MsoLogger.ErrorCode.AvailabilityError.getValue());
729
730                     // Throw a 'special case' of MsoOpenstackException to report the Heat status
731                     MsoOpenstackException me = new MsoOpenstackException (0, "", "Stack Deletion Timeout");
732                     me.addContext (DELETE_STACK);
733
734                     // Alarm this condition, stack deletion failed
735
736
737                     throw me;
738                 }
739
740                 sleep(pollInterval * 1000L);
741
742                 pollTimeout -= pollInterval;
743                 logger.debug("pollTimeout remaining: {}", pollTimeout);
744
745                 heatStack = queryHeatStack (heatClient, canonicalName);
746             }
747
748             // The stack is gone when this point is reached
749             return new StackInfo (stackName, HeatStatus.NOTFOUND);
750         }
751
752         // Return the current status (if not polling, the delete may still be in progress)
753         StackInfo stackInfo = new StackInfoMapper(heatStack).map();
754         stackInfo.setName (stackName);
755
756         return stackInfo;
757     }
758
759     /**
760      * Query for all stacks in a tenant site. This call will return a List of StackInfo
761      * objects, one for each deployed stack.
762      *
763      * Note that this is limited to a single site. To ensure that a tenant is truly
764      * empty would require looping across all tenant endpoints.
765      *
766      * @param tenantId The Openstack ID of the tenant to query
767      * @param cloudSiteId The cloud identifier (may be a region) in which to query.
768      * @return A List of StackInfo objects
769      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
770      * @throws MsoCloudSiteNotFound
771      */
772     public List <StackInfo> queryAllStacks (String tenantId, String cloudSiteId) throws MsoException {
773         // Obtain the cloud site information where we will create the stack
774         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
775                 () -> new MsoCloudSiteNotFound(cloudSiteId));
776         // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
777         Heat heatClient = getHeatClient (cloudSite, tenantId);
778
779         try {
780             OpenStackRequest <Stacks> request = heatClient.getStacks ().list ();
781             Stacks stacks = executeAndRecordOpenstackRequest (request);
782
783             List <StackInfo> stackList = new ArrayList <> ();
784
785             // Not sure if returns an empty list or null if no stacks exist
786             if (stacks != null) {
787                 for (Stack stack : stacks) {
788                     stackList.add (new StackInfoMapper(stack).map());
789                 }
790             }
791
792             return stackList;
793         } catch (OpenStackResponseException e) {
794             if (e.getStatus () == 404) {
795                 // Not sure if this can happen, but return an empty list
796                 logger.debug ("queryAllStacks - stack not found: ");
797                 return new ArrayList <> ();
798             } else {
799                 // Convert the OpenStackResponseException to an MsoOpenstackException
800                 throw heatExceptionToMsoException (e, QUERY_ALL_STACKS);
801             }
802         } catch (OpenStackConnectException e) {
803             // Error connecting to Openstack instance. Convert to an MsoException
804             throw heatExceptionToMsoException (e, QUERY_ALL_STACKS);
805         } catch (RuntimeException e) {
806             // Catch-all
807             throw runtimeExceptionToMsoException (e, QUERY_ALL_STACKS);
808         }
809     }
810
811     /**
812      * Validate parameters to be passed to Heat template. This method performs
813      * three functions:
814      * 1. Apply default values to parameters which have them defined
815      * 2. Report any required parameters that are missing. This will generate an
816      * exception in the caller, since stack create/update operations would fail.
817      * 3. Report and remove any extraneous parameters. This will allow clients to
818      * pass supersets of parameters and not get errors.
819      *
820      * These functions depend on the HeatTemplate definition from the MSO Catalog DB,
821      * along with the input parameter Map. The output is an updated parameter map.
822      * If the parameters are invalid for the template, an IllegalArgumentException
823      * is thrown.
824      */
825     public Map <String, Object> validateStackParams (Map <String, Object> inputParams,
826                                                      HeatTemplate heatTemplate) {
827         // Check that required parameters have been supplied for this template type
828         StringBuilder missingParams = null;
829         List <String> paramList = new ArrayList <> ();
830
831         // TODO: Enhance DB to support defaults for Heat Template parameters
832
833         for (HeatTemplateParam parm : heatTemplate.getParameters ()) {
834             if (parm.isRequired () && !inputParams.containsKey (parm.getParamName ())) {
835                 if (missingParams == null) {
836                     missingParams = new StringBuilder(parm.getParamName());
837                 } else {
838                     missingParams.append("," + parm.getParamName());
839                 }
840             }
841             paramList.add (parm.getParamName ());
842         }
843         if (missingParams != null) {
844             // Problem - missing one or more required parameters
845             String error = "Missing Required inputs for HEAT Template: " + missingParams;
846             logger.error("{} for HEAT Template {} Missing Required inputs for HEAT Template: {}",
847                 MessageEnum.RA_MISSING_PARAM, MsoLogger.ErrorCode.SchemaError.getValue(), missingParams);
848             throw new IllegalArgumentException (error);
849         }
850
851         // Remove any extraneous parameters (don't throw an error)
852         Map <String, Object> updatedParams = new HashMap <> ();
853         List <String> extraParams = new ArrayList <> ();
854
855         for (Entry<String, Object> entry : inputParams.entrySet()) {
856                 if (!paramList.contains(entry.getKey())) {
857                         // This is not a valid parameter for this template
858                         extraParams.add(entry.getKey());
859                 } else {
860                         updatedParams.put(entry.getKey(), entry.getValue());
861                 }
862         }
863
864         if (!extraParams.isEmpty ()) {
865             logger.warn("{} Heat Stack ({}) extra input params received: {} {}", MessageEnum.RA_GENERAL_WARNING,
866                 heatTemplate.getTemplateName(), extraParams, MsoLogger.ErrorCode.DataError.getValue());
867         }
868
869         return updatedParams;
870     }
871
872     // ---------------------------------------------------------------
873     // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
874
875     /**
876      * Get a Heat client for the Openstack Identity service.
877      * This requires a 'member'-level userId + password, which will be retrieved from
878      * properties based on the specified cloud Id. The tenant in which to operate
879      * must also be provided.
880      * <p>
881      * On successful authentication, the Heat object will be cached for the
882      * tenantID + cloudId so that it can be reused without reauthenticating with
883      * Openstack every time.
884      *
885      * @return an authenticated Heat object
886      */
887     public Heat getHeatClient (CloudSite cloudSite, String tenantId) throws MsoException {
888         String cloudId = cloudSite.getId();
889         // For DCP/LCP, the region should be the cloudId.
890         String region = cloudSite.getRegionId ();
891
892         // Obtain an MSO token for the tenant
893         CloudIdentity cloudIdentity = cloudSite.getIdentityService();
894         logger.debug("Found: {}", cloudIdentity.toString());
895         MsoTenantUtils tenantUtils = tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
896         String keystoneUrl = tenantUtils.getKeystoneUrl(cloudId, cloudIdentity);
897         logger.debug("keystoneUrl={}", keystoneUrl);
898         String heatUrl = null;
899         String tokenId = null;
900         Calendar expiration = null;
901             try {
902                 if (ServerType.KEYSTONE.equals(cloudIdentity.getIdentityServerType())) {
903                         Keystone keystoneTenantClient = new Keystone (keystoneUrl);
904                         Access access = null;
905                 
906                         Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
907         
908                         OpenStackRequest <Access> request = keystoneTenantClient.tokens ()
909                                .authenticate (credentials).withTenantId (tenantId);
910         
911                     access = executeAndRecordOpenstackRequest (request);
912                 
913                         try {
914                                 // Isolate trying to printout the region IDs
915                                 try {
916                   logger.debug("access={}", access.toString());
917                   for (Access.Service service : access.getServiceCatalog()) {
918                                                 List<Access.Service.Endpoint> endpoints = service.getEndpoints();
919                                                 for (Access.Service.Endpoint endpoint : endpoints) {
920                       logger.debug("AIC returned region={}", endpoint.getRegion());
921                   }
922                                         }
923                                 } catch (Exception e) {
924                   logger.debug("Encountered an error trying to printout Access object returned from AIC. {}",
925                       e.getMessage(), e);
926               }
927                             heatUrl = KeystoneUtils.findEndpointURL (access.getServiceCatalog (), "orchestration", region, "public");
928                 logger.debug("heatUrl={}, region={}", heatUrl, region);
929             } catch (RuntimeException e) {
930                             // This comes back for not found (probably an incorrect region ID)
931                             String error = "AIC did not match an orchestration service for: region=" + region + ",cloud=" + cloudIdentity.getIdentityUrl();
932                             throw new MsoAdapterException (error, e);
933                         }
934                         tokenId = access.getToken ().getId ();
935                         expiration = access.getToken ().getExpires ();
936                 } else if (ServerType.KEYSTONE_V3.equals(cloudIdentity.getIdentityServerType())) {
937                         try {
938                                 KeystoneAuthHolder holder = keystoneV3Authentication.getToken(cloudSite, tenantId, "orchestration");
939                                 tokenId = holder.getId();
940                                 expiration = holder.getexpiration();
941                                 heatUrl = holder.getServiceUrl();
942                         } catch (ServiceEndpointNotFoundException e) {
943                                 // This comes back for not found (probably an incorrect region ID)
944                             String error = "cloud did not match an orchestration service for: region=" + region + ",cloud=" + cloudIdentity.getIdentityUrl();
945                             throw new MsoAdapterException (error, e);
946                         }
947                 }
948             } catch (OpenStackResponseException e) {
949             if (e.getStatus () == 401) {
950                 // Authentication error.
951                 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId ();
952
953                 throw new MsoAdapterException (error);
954             } else {
955                 throw keystoneErrorToMsoException (e, TOKEN_AUTH);
956             }
957         } catch (OpenStackConnectException e) {
958             // Connection to Openstack failed
959             MsoIOException me = new MsoIOException (e.getMessage (), e);
960             me.addContext (TOKEN_AUTH);
961             throw me;
962         } catch (RuntimeException e) {
963             // Catch-all
964             throw runtimeExceptionToMsoException (e, TOKEN_AUTH);
965         }
966         Heat heatClient = new Heat (heatUrl);
967         heatClient.token (tokenId);
968         return heatClient;
969     }
970
971     /*
972      * Query for a Heat Stack. This function is needed in several places, so
973      * a common method is useful. This method takes an authenticated Heat Client
974      * (which internally identifies the cloud & tenant to search), and returns
975      * a Stack object if found, Null if not found, or an MsoOpenstackException
976      * if the Openstack API call fails.
977      *
978      * The stack name may be a simple name or a canonical name ("{name}/{id}").
979      * When simple name is used, Openstack always returns a 302 redirect which
980      * results in a 2nd request (to the canonical name). Note that query by
981      * canonical name for a deleted stack returns a Stack object with status
982      * "DELETE_COMPLETE" while query by simple name for a deleted stack returns
983      * HTTP 404.
984      *
985      * @param heatClient an authenticated Heat client
986      *
987      * @param stackName the stack name to query
988      *
989      * @return a Stack object that describes the current stack or null if the
990      * requested stack doesn't exist.
991      *
992      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
993      */
994     protected Stack queryHeatStack (Heat heatClient, String stackName) throws MsoException {
995         if (stackName == null) {
996             return null;
997         }
998         try {
999             OpenStackRequest <Stack> request = heatClient.getStacks ().byName (stackName);
1000             return executeAndRecordOpenstackRequest (request);
1001         } catch (OpenStackResponseException e) {
1002                 logger.error("Error in Query Stack", e);
1003             if (e.getStatus () == 404) {
1004                 logger.debug ("queryHeatStack - stack not found: {}", stackName);
1005                 return null;
1006             } else {
1007                 // Convert the OpenStackResponseException to an MsoOpenstackException
1008                 throw heatExceptionToMsoException (e, "QueryStack");
1009             }
1010         } catch (OpenStackConnectException e) {
1011             // Connection to Openstack failed
1012             throw heatExceptionToMsoException (e, "QueryAllStack");
1013         }
1014     }
1015
1016
1017         public Map<String, Object> queryStackForOutputs(String cloudSiteId,
1018                         String tenantId, String stackName) throws MsoException {
1019       logger.debug("MsoHeatUtils.queryStackForOutputs)");
1020       StackInfo heatStack = this.queryStack(cloudSiteId, tenantId, stackName);
1021                 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
1022                         return null;
1023                 }
1024                 return heatStack.getOutputs();
1025         }
1026
1027         public void copyStringOutputsToInputs(Map<String, Object> inputs,
1028                         Map<String, Object> otherStackOutputs, boolean overWrite) {
1029                 if (inputs == null || otherStackOutputs == null)
1030                         return;
1031                 for (String key : otherStackOutputs.keySet()) {
1032                         if (!inputs.containsKey(key)) {
1033                                 Object obj = otherStackOutputs.get(key);
1034                                 if (obj instanceof String) {
1035                                         inputs.put(key, (String) otherStackOutputs.get(key));
1036                                 } else if (obj instanceof JsonNode ){
1037                                         // This is a bit of mess - but I think it's the least impacting
1038                                         // let's convert it BACK to a string - then it will get converted back later
1039                                         try {
1040                                                 String str = this.convertNode((JsonNode) obj);
1041                                                 inputs.put(key, str);
1042                                         } catch (Exception e) {
1043               logger.debug("DANGER WILL ROBINSON: unable to convert value for JsonNode {} ", key, e);
1044               //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1045                                         }
1046                                 } else if (obj instanceof java.util.LinkedHashMap) {
1047             logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
1048             try {
1049                                                 String str = JSON_MAPPER.writeValueAsString(obj);
1050                                                 inputs.put(key, str);
1051                                         } catch (Exception e) {
1052               logger.debug("DANGER WILL ROBINSON: unable to convert value for LinkedHashMap {} ", key, e);
1053           }
1054                                 } else if (obj instanceof Integer) {
1055                                         try {
1056                                                 String str = "" + obj;
1057                                                 inputs.put(key, str);
1058                                         } catch (Exception e) {
1059               logger.debug("DANGER WILL ROBINSON: unable to convert value for Integer {} ", key, e);
1060           }
1061                                 } else {
1062                                         try {
1063                                                 String str = obj.toString();
1064                                                 inputs.put(key, str);
1065                                         } catch (Exception e) {
1066               logger.debug("DANGER WILL ROBINSON: unable to convert value for Other {} ({}) ", key, e.getMessage(), e);
1067               //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
1068                                         }
1069                                 }
1070                         }
1071                 }
1072                 return;
1073         }
1074         public StringBuilder requestToStringBuilder(CreateStackParam stack) {
1075                 StringBuilder sb = new StringBuilder();
1076                 sb.append("Stack:\n");
1077                 sb.append("\tStackName: " + stack.getStackName());
1078                 sb.append("\tTemplateUrl: " + stack.getTemplateUrl());
1079                 sb.append("\tTemplate: " + stack.getTemplate());
1080                 sb.append("\tEnvironment: " + stack.getEnvironment());
1081                 sb.append("\tTimeout: " + stack.getTimeoutMinutes());
1082                 sb.append("\tParameters:\n");
1083                 Map<String, Object> params = stack.getParameters();
1084                 if (params == null || params.size() < 1) {
1085                         sb.append("\nNONE");
1086                 } else {
1087                         for (String key : params.keySet()) {
1088                                 if (params.get(key) instanceof String) {
1089                                         sb.append("\n").append(key).append("=").append((String) params.get(key));
1090                                 } else if (params.get(key) instanceof JsonNode) {
1091                                         String jsonStringOut = this.convertNode((JsonNode)params.get(key));
1092                                         sb.append("\n").append(key).append("=").append(jsonStringOut);
1093                                 } else if (params.get(key) instanceof Integer) {
1094                                         String integerOut = "" + params.get(key);
1095                                         sb.append("\n").append(key).append("=").append(integerOut);
1096
1097                                 } else {
1098                                         try {
1099                                                 String str = params.get(key).toString();
1100                                                 sb.append("\n").append(key).append("=").append(str);
1101                                         } catch (Exception e) {
1102               logger.debug("Exception :", e);
1103           }
1104                                 }
1105                         }
1106                 }
1107                 return sb;
1108         }
1109
1110         private String convertNode(final JsonNode node) {
1111                 try {
1112                         final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
1113                         final String json = JSON_MAPPER.writeValueAsString(obj);
1114                         return json;
1115                 } catch (Exception e) {
1116         logger.debug("Error converting json to string {} ", e.getMessage(), e);
1117     }
1118                 return "[Error converting json to string]";
1119         }
1120
1121
1122         protected StringBuilder getOutputsAsStringBuilder(Stack heatStack) {
1123                 // This should only be used as a utility to print out the stack outputs
1124                 // to the log
1125                 StringBuilder sb = new StringBuilder("");
1126                 if (heatStack == null) {
1127                         sb.append("(heatStack is null)");
1128                         return sb;
1129                 }
1130                 List<Output> outputList = heatStack.getOutputs();
1131                 if (outputList == null || outputList.isEmpty()) {
1132                         sb.append("(outputs is empty)");
1133                         return sb;
1134                 }
1135                 Map<String, Object> outputs = new HashMap<>();
1136                 for (Output outputItem : outputList) {
1137                         outputs.put(outputItem.getOutputKey(), outputItem.getOutputValue());
1138                 }
1139                 int counter = 0;
1140                 sb.append("OUTPUTS:\n");
1141                 for (String key : outputs.keySet()) {
1142                         sb.append("outputs[").append(counter++).append("]: ").append(key).append("=");
1143                         Object obj = outputs.get(key);
1144                         if (obj instanceof String) {
1145                                 sb.append((String) obj).append(" (a string)");
1146                         } else if (obj instanceof JsonNode) {
1147                                 sb.append(this.convertNode((JsonNode) obj)).append(" (a JsonNode)");
1148                         } else if (obj instanceof java.util.LinkedHashMap) {
1149                                 try {
1150                                         String str = JSON_MAPPER.writeValueAsString(obj);
1151                                         sb.append(str).append(" (a java.util.LinkedHashMap)");
1152                                 } catch (Exception e) {
1153             logger.debug("Exception :", e);
1154                                         sb.append("(a LinkedHashMap value that would not convert nicely)");
1155                                 }
1156                         } else if (obj instanceof Integer) {
1157                                 String str = "";
1158                                 try {
1159                                         str = obj.toString() + " (an Integer)\n";
1160                                 } catch (Exception e) {
1161             logger.debug("Exception :", e);
1162             str = "(an Integer unable to call .toString() on)";
1163                                 }
1164                                 sb.append(str);
1165                         } else if (obj instanceof ArrayList) {
1166                                 String str = "";
1167                                 try {
1168                                         str = obj.toString() + " (an ArrayList)";
1169                                 } catch (Exception e) {
1170             logger.debug("Exception :", e);
1171             str = "(an ArrayList unable to call .toString() on?)";
1172                                 }
1173                                 sb.append(str);
1174                         } else if (obj instanceof Boolean) {
1175                                 String str = "";
1176                                 try {
1177                                         str = obj.toString() + " (a Boolean)";
1178                                 } catch (Exception e) {
1179             logger.debug("Exception :", e);
1180             str = "(an Boolean unable to call .toString() on?)";
1181                                 }
1182                                 sb.append(str);
1183                         }
1184                         else {
1185                                 String str = "";
1186                                 try {
1187                                         str = obj.toString() + " (unknown Object type)";
1188                                 } catch (Exception e) {
1189             logger.debug("Exception :", e);
1190             str = "(a value unable to call .toString() on?)";
1191                                 }
1192                                 sb.append(str);
1193                         }
1194                         sb.append("\n");
1195                 }
1196                 sb.append("[END]");
1197                 return sb;
1198         }
1199  
1200
1201         public void copyBaseOutputsToInputs(Map<String, Object> inputs,
1202                         Map<String, Object> otherStackOutputs, List<String> paramNames, Map<String, String> aliases) {
1203                 if (inputs == null || otherStackOutputs == null)
1204                         return;
1205                 for (String key : otherStackOutputs.keySet()) {
1206                         if (paramNames != null) {
1207                                 if (!paramNames.contains(key) && !aliases.containsKey(key)) {
1208             logger.debug("\tParameter {} is NOT defined to be in the template - do not copy to inputs", key);
1209             continue;
1210                                 }
1211                                 if (aliases.containsKey(key)) {
1212             logger.debug("Found an alias! Will move {} to {}", key, aliases.get(key));
1213             Object obj = otherStackOutputs.get(key);
1214                                         key = aliases.get(key);
1215                                         otherStackOutputs.put(key, obj);
1216                                 }
1217                         }
1218                         if (!inputs.containsKey(key)) {
1219                                 Object obj = otherStackOutputs.get(key);
1220           logger.debug("\t**Adding {} to inputs (.toString()={}", key, obj.toString());
1221           if (obj instanceof String) {
1222               logger.debug("\t\t**A String");
1223               inputs.put(key, obj);
1224                                 } else if (obj instanceof Integer) {
1225               logger.debug("\t\t**An Integer");
1226               inputs.put(key, obj);
1227                                 } else if (obj instanceof JsonNode) {
1228               logger.debug("\t\t**A JsonNode");
1229               inputs.put(key, obj);
1230                                 } else if (obj instanceof Boolean) {
1231               logger.debug("\t\t**A Boolean");
1232               inputs.put(key, obj);
1233                                 } else if (obj instanceof java.util.LinkedHashMap) {
1234               logger.debug("\t\t**A java.util.LinkedHashMap **");
1235               inputs.put(key, obj);
1236                                 } else if (obj instanceof java.util.ArrayList) {
1237               logger.debug("\t\t**An ArrayList");
1238               inputs.put(key, obj);
1239                                 } else {
1240               logger.debug("\t\t**UNKNOWN OBJECT TYPE");
1241               inputs.put(key, obj);
1242                                 }
1243                         } else {
1244           logger.debug("key={} is already in the inputs - will not overwrite", key);
1245       }
1246                 }
1247                 return;
1248         }
1249
1250         public List<String> convertCdlToArrayList(String cdl) {
1251                 String cdl2 = cdl.trim();
1252                 String cdl3;
1253                 if (cdl2.startsWith("[") && cdl2.endsWith("]")) {
1254                         cdl3 = cdl2.substring(1, cdl2.lastIndexOf("]"));
1255                 } else {
1256                         cdl3 = cdl2;
1257                 }
1258                 return new ArrayList<>(Arrays.asList(cdl3.split(",")));
1259         }
1260
1261     /**
1262      * New with 1707 - this method will convert all the String *values* of the inputs
1263      * to their "actual" object type (based on the param type: in the db - which comes from the template):
1264      * (heat variable type) -> java Object type
1265      * string -> String
1266      * number -> Integer
1267      * json -> marshal object to json
1268      * comma_delimited_list -> ArrayList
1269      * boolean -> Boolean
1270      * if any of the conversions should fail, we will default to adding it to the inputs
1271      * as a string - see if Openstack can handle it.
1272      * Also, will remove any params that are extra.
1273      * Any aliases will be converted to their appropriate name (anyone use this feature?)
1274      * @param inputs - the Map<String, String> of the inputs received on the request
1275      * @param template the HeatTemplate object - this is so we can also verify if the param is valid for this template
1276      * @return HashMap<String, Object> of the inputs, cleaned and converted
1277      */
1278         public Map<String, Object> convertInputMap(Map<String, Object> inputs, HeatTemplate template) {
1279                 HashMap<String, Object> newInputs = new HashMap<>();
1280                 HashMap<String, HeatTemplateParam> params = new HashMap<>();
1281                 HashMap<String, HeatTemplateParam> paramAliases = new HashMap<>();
1282
1283                 if (inputs == null) {
1284         logger.debug("convertInputMap - inputs is null - nothing to do here");
1285         return new HashMap<>();
1286                 }
1287
1288       logger.debug("convertInputMap in MsoHeatUtils called, with {} inputs, and template {}", inputs.size(),
1289           template.getArtifactUuid());
1290       try {
1291           logger.debug(template.toString());
1292           Set<HeatTemplateParam> paramSet = template.getParameters();
1293           logger.debug("paramSet has {} entries", paramSet.size());
1294       } catch (Exception e) {
1295           logger.debug("Exception occurred in convertInputMap:" + e.getMessage(), e);
1296       }
1297
1298                 for (HeatTemplateParam htp : template.getParameters()) {
1299         logger.debug("Adding {}", htp.getParamName());
1300         params.put(htp.getParamName(), htp);
1301                         if (htp.getParamAlias() != null && !"".equals(htp.getParamAlias())) {
1302           logger.debug("\tFound ALIAS {} -> {}", htp.getParamName(), htp.getParamAlias());
1303           paramAliases.put(htp.getParamAlias(), htp);
1304                         }
1305                 }
1306       logger.debug("Now iterate through the inputs...");
1307       for (String key : inputs.keySet()) {
1308         logger.debug("key={}", key);
1309         boolean alias = false;
1310                         String realName = null;
1311                         if (!params.containsKey(key)) {
1312           logger.debug("{} is not a parameter in the template! - check for an alias", key);
1313           // add check here for an alias
1314                                 if (!paramAliases.containsKey(key)) {
1315             logger.debug("The parameter {} is in the inputs, but it's not a parameter for this template - omit", key);
1316             continue;
1317                                 } else {
1318                                         alias = true;
1319                                         realName = paramAliases.get(key).getParamName();
1320             logger.debug("FOUND AN ALIAS! Will use {} in lieu of give key/alias {}", realName, key);
1321         }
1322                         }
1323                         String type = params.get(key).getParamType();
1324                         if (type == null || "".equals(type)) {
1325           logger.debug("**PARAM_TYPE is null/empty for {}, will default to string", key);
1326           type = "string";
1327                         }
1328         logger.debug("Parameter: {} is of type {}", key, type);
1329         if ("string".equalsIgnoreCase(type)) {
1330                                 // Easiest!
1331                                 String str = inputs.get(key).toString();
1332                                 if (alias)
1333                                         newInputs.put(realName, str);
1334                                 else
1335                                         newInputs.put(key, str);
1336                         } else if ("number".equalsIgnoreCase(type)) {
1337                                 String integerString = inputs.get(key).toString();
1338                                 Integer anInteger = null;
1339                                 try {
1340                                         anInteger = Integer.parseInt(integerString);
1341                                 } catch (Exception e) {
1342             logger.debug("Unable to convert {} to an integer!!", integerString, e);
1343             anInteger = null;
1344                                 }
1345                                 if (anInteger != null) {
1346                                         if (alias)
1347                                                 newInputs.put(realName, anInteger);
1348                                         else
1349                                                 newInputs.put(key, anInteger);
1350                                 }
1351                                 else {
1352                                         if (alias)
1353                                                 newInputs.put(realName, integerString);
1354                                         else
1355                                                 newInputs.put(key, integerString);
1356                                 }
1357                         } else if ("json".equalsIgnoreCase(type)) {
1358                                 Object jsonObj = inputs.get(key);
1359                                 String jsonString;
1360                                 try {
1361                                         jsonString = JSON_MAPPER.writeValueAsString(jsonObj);
1362                                 } catch (JsonProcessingException e) {
1363                                         logger.error("failed to map to json, directly converting to string instead", e);
1364                                         jsonString = jsonObj.toString();
1365                                 }
1366                         if (alias)
1367                                 newInputs.put(realName, jsonString);
1368                         else
1369                                 newInputs.put(key, jsonString);
1370                         //}
1371                         } else if ("comma_delimited_list".equalsIgnoreCase(type)) {
1372                                 String commaSeparated = inputs.get(key).toString();
1373                                 try {
1374                                         List<String> anArrayList = this.convertCdlToArrayList(commaSeparated);
1375                                         if (alias)
1376                                                 newInputs.put(realName, anArrayList);
1377                                         else
1378                                                 newInputs.put(key, anArrayList);
1379                                 } catch (Exception e) {
1380             logger.debug("Unable to convert {} to an ArrayList!!", commaSeparated, e);
1381             if (alias)
1382                                                 newInputs.put(realName, commaSeparated);
1383                                         else
1384                                                 newInputs.put(key, commaSeparated);
1385                                 }
1386                         } else if ("boolean".equalsIgnoreCase(type)) {
1387                                 String booleanString = inputs.get(key).toString();
1388                                 Boolean aBool = Boolean.valueOf(booleanString);
1389                                 if (alias)
1390                                         newInputs.put(realName, aBool);
1391                                 else
1392                                         newInputs.put(key, aBool);
1393                         } else {
1394                                 // it's null or something undefined - just add it back as a String
1395                                 String str = inputs.get(key).toString();
1396                                 if (alias)
1397                                         newInputs.put(realName, str);
1398                                 else
1399                                         newInputs.put(key, str);
1400                         }
1401                 }
1402                 return newInputs;
1403         }
1404
1405         /*
1406          * This helpful method added for Valet
1407          */
1408         public String getCloudSiteKeystoneUrl(String cloudSiteId) throws MsoCloudSiteNotFound {
1409                 String keystone_url = null;
1410                 try {
1411                         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
1412                         CloudIdentity cloudIdentity = cloudSite.getIdentityService();
1413                         keystone_url = cloudIdentity.getIdentityUrl();
1414                 } catch (Exception e) {
1415                         throw new MsoCloudSiteNotFound(cloudSiteId);
1416                 }
1417                 if (keystone_url == null || keystone_url.isEmpty()) {
1418                         throw new MsoCloudSiteNotFound(cloudSiteId);
1419                 }
1420                 return keystone_url;
1421         }
1422
1423         /*
1424          * Create a string suitable for being dumped to a debug log that creates a
1425          * pseudo-JSON request dumping what's being sent to Openstack API in the create or update request
1426          */
1427
1428         private String printStackRequest(String tenantId,
1429                         Map<String, Object> heatFiles,
1430                         Map<String, Object> nestedTemplates,
1431                         String environment,
1432                         Map<String, Object> inputs,
1433                         String vfModuleName,
1434                         String template,
1435                         int timeoutMinutes,
1436                         boolean backout,
1437                         String cloudSiteId) {
1438                 StringBuilder sb = new StringBuilder();
1439                 sb.append("CREATE STACK REQUEST (formatted for readability)\n");
1440                 sb.append("tenant=" + tenantId + ", cloud=" + cloudSiteId);
1441                 sb.append("{\n");
1442                 sb.append("  \"stack_name\": \"" + vfModuleName + "\",\n");
1443                 sb.append("  \"disable_rollback\": " + backout + ",\n");
1444                 sb.append("  \"timeout_mins\": " + timeoutMinutes + ",\n");
1445                 sb.append("  \"template\": {\n");
1446                 sb.append(template);
1447                 sb.append("  },\n");
1448                 sb.append("  \"environment\": {\n");
1449                 if (environment == null)
1450                         sb.append("<none>");
1451                 else
1452                         sb.append(environment);
1453                 sb.append("  },\n");
1454                 sb.append("  \"files\": {\n");
1455                 int filesCounter = 0;
1456                 if (heatFiles != null) {
1457                         for (String key : heatFiles.keySet()) {
1458                                 filesCounter++;
1459                                 if (filesCounter > 1) {
1460                                         sb.append(",\n");
1461                                 }
1462                                 sb.append("    \"" + key + "\": {\n");
1463                                 sb.append(heatFiles.get(key).toString() + "\n    }");
1464                         }
1465                 }
1466                 if (nestedTemplates != null) {
1467                         for (String key : nestedTemplates.keySet()) {
1468                                 filesCounter++;
1469                                 if (filesCounter > 1) {
1470                                         sb.append(",\n");
1471                                 }
1472                                 sb.append("    \"" + key + "\": {\n");
1473                                 sb.append(nestedTemplates.get(key).toString() + "\n    }");
1474                         }
1475                 }
1476                 sb.append("\n  },\n");
1477                 sb.append("  \"parameters\": {\n");
1478                 int paramCounter = 0;
1479                 for (String name : inputs.keySet()) {
1480                         paramCounter++;
1481                         if (paramCounter > 1) {
1482                                 sb.append(",\n");
1483                         }
1484                         Object o = inputs.get(name);
1485                         if (o instanceof java.lang.String) {
1486                                 sb.append("    \"" + name + "\": \"" + inputs.get(name).toString() + "\"");
1487                         } else if (o instanceof Integer) {
1488                                 sb.append("    \"" + name + "\": " + inputs.get(name).toString() );
1489                         } else if (o instanceof ArrayList) {
1490                                 sb.append("    \"" + name + "\": " + inputs.get(name).toString() );
1491                         } else if (o instanceof Boolean) {
1492                                 sb.append("    \"" + name + "\": " + inputs.get(name).toString() );
1493                         } else {
1494                                 sb.append("    \"" + name + "\": " + "\"(there was an issue trying to dump this value...)\"" );
1495                         }
1496                 }
1497                 sb.append("\n  }\n}\n");
1498
1499                 return sb.toString();
1500         }
1501
1502         /*******************************************************************************
1503      *
1504      * Methods (and associated utilities) to implement the VduPlugin interface
1505      *
1506      *******************************************************************************/
1507
1508     /**
1509      * VduPlugin interface for instantiate function.
1510      *
1511      * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
1512      * and then invoke the existing function.
1513      */
1514     @Override
1515         public VduInstance instantiateVdu (
1516                         CloudInfo cloudInfo,
1517                         String instanceName,
1518                         Map<String,Object> inputs,
1519                         VduModelInfo vduModel,
1520                         boolean rollbackOnFailure)
1521         throws VduException
1522     {
1523         String cloudSiteId = cloudInfo.getCloudSiteId();
1524         String tenantId = cloudInfo.getTenantId();
1525
1526         // Translate the VDU ModelInformation structure to that which is needed for
1527         // creating the Heat stack.  Loop through the artifacts, looking specifically
1528         // for MAIN_TEMPLATE and ENVIRONMENT.  Any other artifact will
1529         // be attached as a FILE.
1530         String heatTemplate = null;
1531         Map<String,Object> nestedTemplates = new HashMap<>();
1532         Map<String,Object> files = new HashMap<>();
1533         String heatEnvironment = null;
1534
1535         for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
1536                 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
1537                         heatTemplate = new String(vduArtifact.getContent());
1538                 }
1539                 else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
1540                         nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
1541                 }
1542                 else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
1543                         heatEnvironment = new String(vduArtifact.getContent());
1544                 }
1545         }
1546
1547         try {
1548             StackInfo stackInfo = createStack (cloudSiteId,
1549                     tenantId,
1550                     instanceName,
1551                     heatTemplate,
1552                     inputs,
1553                     true,       // poll for completion
1554                     vduModel.getTimeoutMinutes(),
1555                     heatEnvironment,
1556                     nestedTemplates,
1557                     files,
1558                     rollbackOnFailure);
1559
1560             // Populate a vduInstance from the StackInfo
1561                 return stackInfoToVduInstance(stackInfo);
1562         }
1563         catch (Exception e) {
1564                 throw new VduException ("MsoHeatUtils (instantiateVDU): createStack Exception", e);
1565         }
1566     }
1567
1568
1569     /**
1570      * VduPlugin interface for query function.
1571      */
1572     @Override
1573         public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
1574         throws VduException
1575     {
1576         String cloudSiteId = cloudInfo.getCloudSiteId();
1577         String tenantId = cloudInfo.getTenantId();
1578
1579         try {
1580                 // Query the Cloudify Deployment object and  populate a VduInstance
1581                 StackInfo stackInfo = queryStack (cloudSiteId, tenantId, instanceId);
1582
1583                 return stackInfoToVduInstance(stackInfo);
1584         }
1585         catch (Exception e) {
1586                 throw new VduException ("MsoHeatUtile (queryVdu): queryStack Exception ", e);
1587         }
1588     }
1589
1590
1591     /**
1592      * VduPlugin interface for delete function.
1593      */
1594     @Override
1595         public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
1596         throws VduException
1597     {
1598         String cloudSiteId = cloudInfo.getCloudSiteId();
1599         String tenantId = cloudInfo.getTenantId();
1600
1601         try {
1602                 // Delete the Heat stack
1603                 StackInfo stackInfo = deleteStack (tenantId, cloudSiteId, instanceId, true);
1604
1605                 // Populate a VduInstance based on the deleted Cloudify Deployment object
1606                 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
1607
1608                 // Override return state to DELETED (HeatUtils sets to NOTFOUND)
1609                 vduInstance.getStatus().setState(VduStateType.DELETED);
1610
1611                 return vduInstance;
1612         }
1613         catch (Exception e) {
1614                 throw new VduException ("Delete VDU Exception", e);
1615         }
1616     }
1617
1618
1619     /**
1620      * VduPlugin interface for update function.
1621      *
1622      * Update is currently not supported in the MsoHeatUtils implementation of VduPlugin.
1623      * Just return a VduException.
1624      *
1625      */
1626     @Override
1627         public VduInstance updateVdu (
1628                         CloudInfo cloudInfo,
1629                         String instanceId,
1630                         Map<String,Object> inputs,
1631                         VduModelInfo vduModel,
1632                         boolean rollbackOnFailure)
1633         throws VduException
1634     {
1635         throw new VduException ("MsoHeatUtils: updateVdu interface not supported");
1636     }
1637
1638
1639     /*
1640      * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
1641      */
1642     protected VduInstance stackInfoToVduInstance (StackInfo stackInfo)
1643     {
1644         VduInstance vduInstance = new VduInstance();
1645
1646         // The full canonical name as the instance UUID
1647         vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
1648         vduInstance.setVduInstanceName(stackInfo.getName());
1649
1650         // Copy inputs and outputs
1651         vduInstance.setInputs(stackInfo.getParameters());
1652         vduInstance.setOutputs(stackInfo.getOutputs());
1653
1654         // Translate the status elements
1655         vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
1656
1657         return vduInstance;
1658     }
1659
1660     private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
1661     {
1662         VduStatus vduStatus = new VduStatus();
1663
1664         // Map the status fields to more generic VduStatus.
1665         // There are lots of HeatStatus values, so this is a bit long...
1666         HeatStatus heatStatus = stackInfo.getStatus();
1667         String statusMessage = stackInfo.getStatusMessage();
1668
1669         if (heatStatus == HeatStatus.INIT  ||  heatStatus == HeatStatus.BUILDING) {
1670                 vduStatus.setState(VduStateType.INSTANTIATING);
1671                 vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
1672         }
1673         else if (heatStatus == HeatStatus.NOTFOUND) {
1674                 vduStatus.setState(VduStateType.NOTFOUND);
1675         }
1676         else if (heatStatus == HeatStatus.CREATED) {
1677                 vduStatus.setState(VduStateType.INSTANTIATED);
1678                 vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
1679         }
1680         else if (heatStatus == HeatStatus.UPDATED) {
1681                 vduStatus.setState(VduStateType.INSTANTIATED);
1682                 vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
1683         }
1684         else if (heatStatus == HeatStatus.UPDATING) {
1685                 vduStatus.setState(VduStateType.UPDATING);
1686                 vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
1687         }
1688         else if (heatStatus == HeatStatus.DELETING) {
1689                 vduStatus.setState(VduStateType.DELETING);
1690                 vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
1691         }
1692         else if (heatStatus == HeatStatus.FAILED) {
1693                 vduStatus.setState(VduStateType.FAILED);
1694                 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
1695         } else {
1696                 vduStatus.setState(VduStateType.UNKNOWN);
1697         }
1698
1699         return vduStatus;
1700     }
1701     
1702         public Resources queryStackResources(String cloudSiteId, String tenantId, String stackName) throws MsoException {
1703                 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId)
1704                                 .orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
1705                 Heat heatClient = getHeatClient(cloudSite, tenantId);
1706                 OpenStackRequest<Resources> request = heatClient.getResources().listResources(stackName);
1707                 return executeAndRecordOpenstackRequest(request);
1708         }
1709
1710         public <R> R executeHeatClientRequest(String url, String cloudSiteId, String tenantId, Class<R> returnType) throws MsoException {
1711                 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId)
1712                                 .orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
1713                 Heat heatClient = getHeatClient(cloudSite, tenantId);
1714                 OpenStackRequest<R> request = heatClient.get(url, returnType);
1715                 return executeAndRecordOpenstackRequest(request);
1716         }
1717
1718     protected void sleep(long time) {
1719         try {
1720             Thread.sleep(time);
1721         } catch (InterruptedException e) {
1722             logger.debug ("Thread interrupted while sleeping", e);
1723             Thread.currentThread().interrupt();
1724         }
1725     }
1726
1727 }