19f1b217ffc1b7f048e287582c8883eaf762247f
[so.git] / adapters / mso-openstack-adapters / src / main / java / org / onap / so / adapters / vnf / MsoVnfCloudifyAdapterImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * OPENECOMP - MSO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Modifications Copyright (C) 2018 IBM.
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.adapters.vnf;
25
26
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.Set;
33 import javax.jws.WebService;
34 import javax.xml.ws.Holder;
35 import org.onap.so.adapters.vnf.exceptions.VnfAlreadyExists;
36 import org.onap.so.adapters.vnf.exceptions.VnfException;
37 import org.onap.so.cloud.CloudConfig;
38 import org.onap.so.db.catalog.beans.CloudSite;
39 import org.onap.so.cloudify.beans.DeploymentInfo;
40 import org.onap.so.cloudify.beans.DeploymentStatus;
41 import org.onap.so.cloudify.exceptions.MsoCloudifyManagerNotFound;
42 import org.onap.so.cloudify.utils.MsoCloudifyUtils;
43 import org.onap.so.db.catalog.beans.HeatEnvironment;
44 import org.onap.so.db.catalog.beans.HeatFiles;
45 import org.onap.so.db.catalog.beans.HeatTemplate;
46 import org.onap.so.db.catalog.beans.HeatTemplateParam;
47 import org.onap.so.db.catalog.beans.VfModule;
48 import org.onap.so.db.catalog.beans.VfModuleCustomization;
49 import org.onap.so.db.catalog.beans.VnfResource;
50 import org.onap.so.db.catalog.data.repository.VFModuleCustomizationRepository;
51 import org.onap.so.db.catalog.utils.MavenLikeVersioning;
52 import org.onap.so.entity.MsoRequest;
53 import org.onap.so.logger.ErrorCode;
54 import org.onap.so.logger.MessageEnum;
55 import org.onap.so.openstack.beans.MsoTenant;
56 import org.onap.so.openstack.beans.VnfRollback;
57 import org.onap.so.openstack.beans.VnfStatus;
58 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
59 import org.onap.so.openstack.exceptions.MsoException;
60 import org.onap.so.openstack.exceptions.MsoExceptionCategory;
61 import org.onap.so.openstack.utils.MsoHeatEnvironmentEntry;
62 import org.onap.so.openstack.utils.MsoHeatEnvironmentParameter;
63 import org.onap.so.openstack.utils.MsoKeystoneUtils;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66 import org.springframework.beans.factory.annotation.Autowired;
67 import org.springframework.core.env.Environment;
68 import org.springframework.stereotype.Component;
69 import com.fasterxml.jackson.core.JsonParseException;
70 import com.fasterxml.jackson.databind.JsonNode;
71 import com.fasterxml.jackson.databind.ObjectMapper;
72 import org.springframework.transaction.annotation.Transactional;
73
74 @Component
75 @Transactional
76 @WebService(serviceName = "VnfAdapter", endpointInterface = "org.onap.so.adapters.vnf.MsoVnfAdapter",
77         targetNamespace = "http://org.onap.so/vnf")
78 public class MsoVnfCloudifyAdapterImpl implements MsoVnfAdapter {
79
80     private static Logger logger = LoggerFactory.getLogger(MsoVnfCloudifyAdapterImpl.class);
81
82     private static final String CHECK_REQD_PARAMS = "org.onap.so.adapters.vnf.checkRequiredParameters";
83     private static final String CLOUDIFY = "Cloudify";
84
85     private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
86     private static final String BRACKETS = "{} {} {} {} {} {} {} {} {}";
87
88     @Autowired
89     protected CloudConfig cloudConfig;
90
91     @Autowired
92     private VFModuleCustomizationRepository vfModuleCustomRepo;
93
94     @Autowired
95     private Environment environment;
96
97     @Autowired
98     protected MsoKeystoneUtils keystoneUtils;
99
100     @Autowired
101     protected MsoCloudifyUtils cloudifyUtils;
102
103     /**
104      * DO NOT use that constructor to instantiate this class, the msoPropertiesfactory will be NULL.
105      *
106      * @see MsoVnfCloudifyAdapterImpl#MsoVnfAdapterImpl(MsoPropertiesFactory, CloudConfigFactory)
107      */
108     public MsoVnfCloudifyAdapterImpl() {
109
110     }
111
112     /**
113      * Health Check web method. Does nothing but return to show the adapter is deployed.
114      */
115     @Override
116     public void healthCheck() {
117         logger.debug("Health check call in VNF Cloudify Adapter");
118     }
119
120     /**
121      * This is the "Create VNF" web service implementation. This function is now unsupported and will return an error.
122      *
123      */
124     @Override
125     public void createVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfType, String vnfVersion,
126             String vnfName, String requestType, String volumeGroupHeatStackId, Map<String, Object> inputs,
127             Boolean failIfExists, Boolean backout, Boolean enableBridge, MsoRequest msoRequest, Holder<String> vnfId,
128             Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback) throws VnfException {
129         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
130         logger.debug("CreateVNF command attempted but not supported");
131         throw new VnfException("CreateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
132     }
133
134     /**
135      * This is the "Update VNF" web service implementation. This function is now unsupported and will return an error.
136      *
137      */
138     @Override
139     public void updateVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfType, String vnfVersion,
140             String vnfName, String requestType, String volumeGroupHeatStackId, Map<String, Object> inputs,
141             MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
142             throws VnfException {
143         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
144         logger.debug("UpdateVNF command attempted but not supported");
145         throw new VnfException("UpdateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
146     }
147
148     /**
149      * This is the "Query VNF" web service implementation.
150      *
151      * This really should be QueryVfModule, but nobody ever changed it.
152      *
153      * For Cloudify, this will look up a deployment by its deployment ID, which is really the same as deployment name,
154      * since it assigned by the client when a deployment is created. Also, the input cloudSiteId is used only to
155      * identify which Cloudify instance to query, and the tenantId is ignored (since that really only applies for
156      * Openstack/Heat).
157      *
158      * The method returns an indicator that the VNF exists, along with its status and outputs. The input "vnfName" will
159      * also be reflected back as its ID.
160      *
161      * @param cloudSiteId CLLI code of the cloud site in which to query
162      * @param cloudOwner cloud owner of the cloud site in which to query
163      * @param tenantId Openstack tenant identifier - ignored for Cloudify
164      * @param vnfName VNF Name (should match a deployment ID)
165      * @param msoRequest Request tracking information for logs
166      * @param vnfExists Flag reporting the result of the query
167      * @param vnfId Holder for output VNF ID
168      * @param outputs Holder for Map of VNF outputs from Cloudify deployment (assigned IPs, etc)
169      */
170     @Override
171     public void queryVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfName, MsoRequest msoRequest,
172             Holder<Boolean> vnfExists, Holder<String> vnfId, Holder<VnfStatus> status,
173             Holder<Map<String, String>> outputs) throws VnfException {
174         logger.debug("Querying VNF {} in {}", vnfName, cloudSiteId + "/" + tenantId);
175
176         // Will capture execution time for metrics
177         long startTime = System.currentTimeMillis();
178         long subStartTime = System.currentTimeMillis();
179
180         DeploymentInfo deployment = null;
181
182         try {
183             deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
184         } catch (MsoCloudifyManagerNotFound e) {
185             // This site does not have a Cloudify Manager.
186             // This isn't an error, just means we won't find the VNF here.
187             deployment = null;
188         } catch (MsoException me) {
189             // Failed to query the Deployment due to a cloudify exception.
190             // Convert to a generic VnfException
191             me.addContext("QueryVNF");
192             String error = "Query VNF (Cloudify): " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
193                     + ": " + me;
194             logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner, cloudSiteId, tenantId,
195                     CLOUDIFY, "QueryVNF", ErrorCode.DataError.getValue(), "Exception - queryDeployment", me);
196             logger.debug(error);
197             throw new VnfException(me);
198         }
199
200         if (deployment != null && deployment.getStatus() != DeploymentStatus.NOTFOUND) {
201             vnfExists.value = Boolean.TRUE;
202             status.value = deploymentStatusToVnfStatus(deployment);
203             vnfId.value = deployment.getId();
204             outputs.value = copyStringOutputs(deployment.getOutputs());
205
206             logger.debug("VNF {} found in Cloudify, ID = {}", vnfName, vnfId.value);
207         } else {
208             vnfExists.value = Boolean.FALSE;
209             status.value = VnfStatus.NOTFOUND;
210             vnfId.value = null;
211             outputs.value = new HashMap<String, String>(); // Return as an empty map
212
213             logger.debug("VNF {} not found", vnfName);
214         }
215         return;
216     }
217
218
219     /**
220      * This is the "Delete VNF" web service implementation. This function is now unsupported and will return an error.
221      *
222      */
223     @Override
224     public void deleteVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfName, MsoRequest msoRequest)
225             throws VnfException {
226
227         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
228         logger.debug("DeleteVNF command attempted but not supported");
229         throw new VnfException("DeleteVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
230     }
231
232     /**
233      * This web service endpoint will rollback a previous Create VNF operation. A rollback object is returned to the
234      * client in a successful creation response. The client can pass that object as-is back to the rollbackVnf operation
235      * to undo the creation.
236      *
237      * TODO: This should be rollbackVfModule and/or rollbackVolumeGroup, but APIs were apparently never updated.
238      */
239     @Override
240     public void rollbackVnf(VnfRollback rollback) throws VnfException {
241         long startTime = System.currentTimeMillis();
242         // rollback may be null (e.g. if stack already existed when Create was called)
243         if (rollback == null) {
244             logger.info("{} {} {}", MessageEnum.RA_ROLLBACK_NULL.toString(), "OpenStack", "rollbackVnf");
245             return;
246         }
247
248         // Don't rollback if nothing was done originally
249         if (!rollback.getVnfCreated()) {
250             return;
251         }
252
253         // Get the elements of the VnfRollback object for easier access
254         String cloudSiteId = rollback.getCloudSiteId();
255         String cloudOwner = rollback.getCloudOwner();
256         String tenantId = rollback.getTenantId();
257         String vfModuleId = rollback.getVfModuleStackId();
258
259         logger.debug("Rolling Back VF Module {} in {}", vfModuleId, cloudOwner + "/" + cloudSiteId + "/" + tenantId);
260
261         DeploymentInfo deployment = null;
262
263         // Use the MsoCloudifyUtils to delete the deployment. Set the polling flag to true.
264         // The possible outcomes of deleteStack are a StackInfo object with status
265         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
266         // could be thrown.
267         long subStartTime = System.currentTimeMillis();
268         try {
269             // KLUDGE - Cloudify requires Tenant Name for Openstack. We have the ID.
270             // Go directly to Keystone until APIs could be updated to supply the name.
271             MsoTenant msoTenant = keystoneUtils.queryTenant(tenantId, cloudSiteId);
272             String tenantName = (msoTenant != null ? msoTenant.getTenantName() : tenantId);
273
274             // TODO: Get a reasonable timeout. Use a global property, or store the creation timeout in rollback object
275             // and use that.
276             deployment = cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantName, vfModuleId, 5);
277             logger.debug("Rolled back deployment: {}", deployment.getId());
278         } catch (MsoException me) {
279             // Failed to rollback the VNF due to a cloudify exception.
280             // Convert to a generic VnfException
281             me.addContext("RollbackVNF");
282             String error = "Rollback VF Module: " + vfModuleId + " in " + cloudOwner + "/" + cloudSiteId + "/"
283                     + tenantId + ": " + me;
284             logger.error(BRACKETS, MessageEnum.RA_DELETE_VNF_ERR.toString(), vfModuleId, cloudOwner, cloudSiteId,
285                     tenantId, CLOUDIFY, "DeleteDeployment", ErrorCode.DataError.getValue(),
286                     "Exception - DeleteDeployment", me);
287             logger.debug(error);
288             throw new VnfException(me);
289         }
290         return;
291     }
292
293
294     private VnfStatus deploymentStatusToVnfStatus(DeploymentInfo deployment) {
295         // Determine the status based on last action & status
296         // DeploymentInfo object should be enhanced to report a better status internally.
297         DeploymentStatus status = deployment.getStatus();
298         String lastAction = deployment.getLastAction();
299
300         if (status == null || lastAction == null) {
301             return VnfStatus.UNKNOWN;
302         } else if (status == DeploymentStatus.NOTFOUND) {
303             return VnfStatus.NOTFOUND;
304         } else if (status == DeploymentStatus.INSTALLED) {
305             return VnfStatus.ACTIVE;
306         } else if (status == DeploymentStatus.CREATED) {
307             // Should have an INACTIVE status for this case. Shouldn't really happen, but
308             // Install was never run, or Uninstall was done but deployment didn't get deleted.
309             return VnfStatus.UNKNOWN;
310         } else if (status == DeploymentStatus.FAILED) {
311             return VnfStatus.FAILED;
312         }
313
314         return VnfStatus.UNKNOWN;
315     }
316
317     private Map<String, String> copyStringOutputs(Map<String, Object> stackOutputs) {
318         Map<String, String> stringOutputs = new HashMap<String, String>();
319         for (String key : stackOutputs.keySet()) {
320             if (stackOutputs.get(key) instanceof String) {
321                 stringOutputs.put(key, (String) stackOutputs.get(key));
322             } else if (stackOutputs.get(key) instanceof Integer) {
323                 try {
324                     String str = "" + stackOutputs.get(key);
325                     stringOutputs.put(key, str);
326                 } catch (Exception e) {
327                     logger.debug("Unable to add " + key + " to outputs");
328                 }
329             } else if (stackOutputs.get(key) instanceof JsonNode) {
330                 try {
331                     String str = this.convertNode((JsonNode) stackOutputs.get(key));
332                     stringOutputs.put(key, str);
333                 } catch (Exception e) {
334                     logger.debug("Unable to add " + key + " to outputs - exception converting JsonNode");
335                 }
336             } else if (stackOutputs.get(key) instanceof java.util.LinkedHashMap) {
337                 try {
338                     String str = JSON_MAPPER.writeValueAsString(stackOutputs.get(key));
339                     stringOutputs.put(key, str);
340                 } catch (Exception e) {
341                     logger.debug("Unable to add " + key + " to outputs - exception converting LinkedHashMap");
342                 }
343             } else {
344                 try {
345                     String str = stackOutputs.get(key).toString();
346                     stringOutputs.put(key, str);
347                 } catch (Exception e) {
348                     logger.debug("Unable to add " + key + " to outputs - unable to call .toString() " + e.getMessage());
349                 }
350             }
351         }
352         return stringOutputs;
353     }
354
355
356     private void sendMapToDebug(Map<String, Object> inputs, String optionalName) {
357         int i = 0;
358         StringBuilder sb = new StringBuilder(optionalName == null ? "\ninputs" : "\n" + optionalName);
359         if (inputs == null) {
360             sb.append("\tNULL");
361         } else if (inputs.size() < 1) {
362             sb.append("\tEMPTY");
363         } else {
364             for (String str : inputs.keySet()) {
365                 String outputString;
366                 try {
367                     outputString = inputs.get(str).toString();
368                 } catch (Exception e) {
369                     outputString = "Unable to call toString() on the value for " + str;
370                 }
371                 sb.append("\t\nitem " + i++ + ": '" + str + "'='" + outputString + "'");
372             }
373         }
374         logger.debug(sb.toString());
375         return;
376     }
377
378     private void sendMapToDebug(Map<String, Object> inputs) {
379         int i = 0;
380         StringBuilder sb = new StringBuilder("inputs:");
381         if (inputs == null) {
382             sb.append("\tNULL");
383         } else if (inputs.size() < 1) {
384             sb.append("\tEMPTY");
385         } else {
386             for (String str : inputs.keySet()) {
387                 sb.append("\titem " + i++ + ": " + str + "=" + inputs.get(str));
388             }
389         }
390         logger.debug(sb.toString());
391         return;
392     }
393
394     private String convertNode(final JsonNode node) {
395         try {
396             final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
397             final String json = JSON_MAPPER.writeValueAsString(obj);
398             return json;
399         } catch (JsonParseException jpe) {
400             logger.debug("Error converting json to string " + jpe.getMessage());
401         } catch (Exception e) {
402             logger.debug("Error converting json to string " + e.getMessage());
403         }
404         return "[Error converting json to string]";
405     }
406
407     private Map<String, String> convertMapStringObjectToStringString(Map<String, Object> objectMap) {
408         if (objectMap == null) {
409             return null;
410         }
411         Map<String, String> stringMap = new HashMap<String, String>();
412         for (String key : objectMap.keySet()) {
413             if (!stringMap.containsKey(key)) {
414                 Object obj = objectMap.get(key);
415                 if (obj instanceof String) {
416                     stringMap.put(key, (String) objectMap.get(key));
417                 } else if (obj instanceof JsonNode) {
418                     // This is a bit of mess - but I think it's the least impacting
419                     // let's convert it BACK to a string - then it will get converted back later
420                     try {
421                         String str = this.convertNode((JsonNode) obj);
422                         stringMap.put(key, str);
423                     } catch (Exception e) {
424                         logger.debug("DANGER WILL ROBINSON: unable to convert value for JsonNode " + key);
425                         // okay in this instance - only string values (fqdn) are expected to be needed
426                     }
427                 } else if (obj instanceof java.util.LinkedHashMap) {
428                     logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
429                     try {
430                         String str = JSON_MAPPER.writeValueAsString(obj);
431                         stringMap.put(key, str);
432                     } catch (Exception e) {
433                         logger.debug("DANGER WILL ROBINSON: unable to convert value for LinkedHashMap " + key);
434                     }
435                 } else if (obj instanceof Integer) {
436                     try {
437                         String str = "" + obj;
438                         stringMap.put(key, str);
439                     } catch (Exception e) {
440                         logger.debug("DANGER WILL ROBINSON: unable to convert value for Integer " + key);
441                     }
442                 } else {
443                     try {
444                         String str = obj.toString();
445                         stringMap.put(key, str);
446                     } catch (Exception e) {
447                         logger.debug(
448                                 "DANGER WILL ROBINSON: unable to convert value " + key + " (" + e.getMessage() + ")");
449                     }
450                 }
451             }
452         }
453
454         return stringMap;
455     }
456
457     /**
458      * This is the "Create VF Module" web service implementation. It will instantiate a new VF Module of the requested
459      * type in the specified cloud and tenant. The tenant must exist before this service is called.
460      *
461      * If a VF Module with the same name already exists, this can be considered a success or failure, depending on the
462      * value of the 'failIfExists' parameter.
463      *
464      * All VF Modules are defined in the MSO catalog. The caller must request one of the pre-defined module types or an
465      * error will be returned. Within the catalog, each VF Module references (among other things) a cloud template which
466      * is used to deploy the required artifacts (VMs, networks, etc.) to the cloud. In this adapter implementation, that
467      * artifact is expected to be a Cloudify blueprint.
468      *
469      * Depending on the blueprint, a variable set of input parameters will be defined, some of which are required. The
470      * caller is responsible to pass the necessary input data for the module or an error will be thrown.
471      *
472      * The method returns the vfModuleId, a Map of output attributes, and a VnfRollback object. This last object can be
473      * passed as-is to the rollbackVnf operation to undo everything that was created for the Module. This is useful if a
474      * VF module is successfully created but the orchestration fails on a subsequent step.
475      *
476      * @param cloudSiteId CLLI code of the cloud site in which to create the VNF
477      * @param cloudOwner cloud owner of the cloud site in which to create the VNF
478      * @param tenantId Openstack tenant identifier
479      * @param vfModuleType VF Module type key, should match a VNF definition in catalog DB. Deprecated - should use
480      *        modelCustomizationUuid
481      * @param vnfVersion VNF version key, should match a VNF definition in catalog DB Deprecated - VF Module versions
482      *        also captured by modelCustomizationUuid
483      * @param genericVnfId Generic VNF ID
484      * @param vfModuleName Name to be assigned to the new VF Module
485      * @param vfModuleId Id of the new VF Module
486      * @param requestType Indicates if this is a Volume Group or Module request
487      * @param volumeGroupId Identifier (i.e. deployment ID) for a Volume Group to attach to a VF Module
488      * @param baseVfModuleId Identifier (i.e. deployment ID) of the Base Module if this is an Add-on module
489      * @param modelCustomizationUuid Unique ID for the VF Module's model. Replaces the use of vfModuleType.
490      * @param inputs Map of key=value inputs for VNF stack creation
491      * @param failIfExists Flag whether already existing VNF should be considered
492      * @param backout Flag whether to suppress automatic backout (for testing)
493      * @param msoRequest Request tracking information for logs
494      * @param vnfId Holder for output VNF Cloudify Deployment ID
495      * @param outputs Holder for Map of VNF outputs from Deployment (assigned IPs, etc)
496      * @param rollback Holder for returning VnfRollback object
497      */
498     @Override
499     public void createVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vfModuleType,
500             String vnfVersion, String genericVnfId, String vfModuleName, String vfModuleId, String requestType,
501             String volumeGroupId, String baseVfModuleId, String modelCustomizationUuid, Map<String, Object> inputs,
502             Boolean failIfExists, Boolean backout, Boolean enableBridge, MsoRequest msoRequest, Holder<String> vnfId,
503             Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback) throws VnfException {
504         // Will capture execution time for metrics
505         long startTime = System.currentTimeMillis();
506
507         // Require a model customization ID. Every VF Module definition must have one.
508         if (modelCustomizationUuid == null || modelCustomizationUuid.isEmpty()) {
509             logger.debug("Missing required input: modelCustomizationUuid");
510             String error = "Create vfModule error: Missing required input: modelCustomizationUuid";
511             logger.error("{} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
512                     "VF Module ModelCustomizationUuid", CLOUDIFY, ErrorCode.DataError.getValue(),
513                     "Create VF Module: Missing required input: modelCustomizationUuid");
514             logger.debug(error);
515             throw new VnfException(error, MsoExceptionCategory.USERDATA);
516         }
517
518         // Clean up some inputs to make comparisons easier
519         if (requestType == null)
520             requestType = "";
521
522         if ("".equals(volumeGroupId) || "null".equals(volumeGroupId))
523             volumeGroupId = null;
524
525         if ("".equals(baseVfModuleId) || "null".equals(baseVfModuleId))
526             baseVfModuleId = null;
527
528         if (inputs == null) {
529             // Create an empty set of inputs
530             inputs = new HashMap<>();
531             logger.debug("inputs == null - setting to empty");
532         } else {
533             this.sendMapToDebug(inputs);
534         }
535
536         // Check if this is for a "Volume" module
537         boolean isVolumeRequest = false;
538         if (requestType.startsWith("VOLUME")) {
539             isVolumeRequest = true;
540         }
541
542         logger.debug("requestType = " + requestType + ", volumeGroupStackId = " + volumeGroupId + ", baseStackId = "
543                 + baseVfModuleId);
544
545         // Build a default rollback object (no actions performed)
546         VnfRollback vfRollback = new VnfRollback();
547         vfRollback.setCloudSiteId(cloudSiteId);
548         vfRollback.setCloudOwner(cloudOwner);
549         vfRollback.setTenantId(tenantId);
550         vfRollback.setMsoRequest(msoRequest);
551         vfRollback.setRequestType(requestType);
552         vfRollback.setIsBase(false); // Until we know better
553         vfRollback.setVolumeGroupHeatStackId(volumeGroupId);
554         vfRollback.setBaseGroupHeatStackId(baseVfModuleId);
555         vfRollback.setModelCustomizationUuid(modelCustomizationUuid);
556         vfRollback.setMode("CFY");
557
558         rollback.value = vfRollback; // Default rollback - no updates performed
559
560         // Get the VNF/VF Module definition from the Catalog DB first.
561         // There are three relevant records: VfModule, VfModuleCustomization, VnfResource
562
563         VfModule vf = null;
564         VnfResource vnfResource = null;
565         VfModuleCustomization vfmc = null;
566
567         try {
568             vfmc = vfModuleCustomRepo.findFirstByModelCustomizationUUIDOrderByCreatedDesc(modelCustomizationUuid);
569
570             if (vfmc == null) {
571                 String error = "Create vfModule error: Unable to find vfModuleCust with modelCustomizationUuid="
572                         + modelCustomizationUuid;
573                 logger.debug(error);
574                 logger.error("{} {} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
575                         "VF Module " + "ModelCustomizationUuid", modelCustomizationUuid, "CatalogDb",
576                         ErrorCode.DataError.getValue(), error);
577                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
578             } else {
579                 logger.debug("Found vfModuleCust entry " + vfmc.toString());
580             }
581
582             // Get the vfModule and vnfResource records
583             vf = vfmc.getVfModule();
584             vnfResource = vfmc.getVfModule().getVnfResources();
585         } catch (Exception e) {
586
587             logger.debug("unhandled exception in create VF - [Query]" + e.getMessage());
588             throw new VnfException("Exception during create VF " + e.getMessage());
589         }
590
591         // Perform a version check against cloudSite
592         // Obtain the cloud site information where we will create the VF Module
593         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite(cloudSiteId);
594         if (!cloudSiteOp.isPresent()) {
595             throw new VnfException(new MsoCloudSiteNotFound(cloudSiteId));
596         }
597         CloudSite cloudSite = cloudSiteOp.get();
598         MavenLikeVersioning aicV = new MavenLikeVersioning();
599         aicV.setVersion(cloudSite.getCloudVersion());
600
601         String vnfMin = vnfResource.getAicVersionMin();
602         String vnfMax = vnfResource.getAicVersionMax();
603
604         if ((vnfMin != null && !(aicV.isMoreRecentThan(vnfMin) || aicV.isTheSameVersion(vnfMin)))
605                 || (vnfMax != null && aicV.isMoreRecentThan(vnfMax))) {
606             // ERROR
607             String error = "VNF Resource type: " + vnfResource.getModelName() + ", ModelUuid="
608                     + vnfResource.getModelUUID() + " VersionMin=" + vnfMin + " VersionMax:" + vnfMax
609                     + " NOT supported on Cloud: " + cloudSiteId + " with AIC_Version:" + cloudSite.getCloudVersion();
610             logger.error("{} {} {} {} {}", MessageEnum.RA_CONFIG_EXC.toString(), error, "OpenStack",
611                     ErrorCode.BusinessProcesssError.getValue(), "Exception - setVersion");
612             logger.debug(error);
613             throw new VnfException(error, MsoExceptionCategory.USERDATA);
614         }
615         // End Version check
616
617
618         DeploymentInfo cloudifyDeployment = null;
619
620         // First, look up to see if the VF already exists.
621
622         long subStartTime1 = System.currentTimeMillis();
623         try {
624             cloudifyDeployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vfModuleName);
625         } catch (MsoException me) {
626             // Failed to query the Deployment due to a cloudify exception.
627             String error = "Create VF Module: Query " + vfModuleName + " in " + cloudOwner + "/" + cloudSiteId + "/"
628                     + tenantId + ": " + me;
629             logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudSiteId,
630                     tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
631                     "Exception - queryDeployment", me);
632             logger.debug(error);
633
634             // Convert to a generic VnfException
635             me.addContext("CreateVFModule");
636             throw new VnfException(me);
637         }
638
639         // More precise handling/messaging if the Module already exists
640         if (cloudifyDeployment != null && !(cloudifyDeployment.getStatus() == DeploymentStatus.NOTFOUND)) {
641             // CREATED, INSTALLED, INSTALLING, FAILED, UNINSTALLING, UNKNOWN
642             DeploymentStatus status = cloudifyDeployment.getStatus();
643             logger.debug("Found Existing Deployment, status=" + status);
644
645             if (status == DeploymentStatus.INSTALLED) {
646                 // fail - it exists
647                 if (failIfExists != null && failIfExists) {
648                     String error = "Create VF: Deployment " + vfModuleName + " already exists in " + cloudOwner + "/"
649                             + cloudSiteId + "/" + tenantId;
650                     logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
651                             cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
652                             "Deployment " + vfModuleName + " already exists");
653                     logger.debug(error);
654                     throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId,
655                             cloudifyDeployment.getId());
656                 } else {
657                     // Found existing deployment and client has not requested "failIfExists".
658                     // Populate the outputs from the existing deployment.
659
660                     vnfId.value = cloudifyDeployment.getId();
661                     outputs.value = copyStringOutputs(cloudifyDeployment.getOutputs());
662                     return;
663                 }
664             }
665             // Check through various detailed error cases
666             if (status == DeploymentStatus.INSTALLING || status == DeploymentStatus.UNINSTALLING) {
667                 // fail - it's in progress - return meaningful error
668                 String error = "Create VF: Deployment " + vfModuleName + " already exists and has status "
669                         + status.toString() + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
670                         + "; please wait for it to complete, or fix manually.";
671                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
672                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
673                         "Deployment " + vfModuleName + " already exists");
674                 logger.debug(error);
675                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
676             } else if (status == DeploymentStatus.FAILED) {
677                 // fail - it exists and is in a FAILED state
678                 String error = "Create VF: Deployment " + vfModuleName + " already exists and is in FAILED state in "
679                         + cloudOwner + "/" + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
680                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
681                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
682                         "Deployment " + vfModuleName + " already " + "exists and is in FAILED state");
683                 logger.debug(error);
684                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
685             } else if (status == DeploymentStatus.UNKNOWN || status == DeploymentStatus.CREATED) {
686                 // fail - it exists and is in a UNKNOWN state
687                 String error = "Create VF: Deployment " + vfModuleName + " already exists and has status "
688                         + status.toString() + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
689                         + "; requires manual intervention.";
690                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
691                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
692                         "Deployment " + vfModuleName + " already " + "exists and is in " + status.toString()
693                                 + " state");
694                 logger.debug(error);
695                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
696             } else {
697                 // Unexpected, since all known status values have been tested for
698                 String error = "Create VF: Deployment " + vfModuleName + " already exists with unexpected status "
699                         + status.toString() + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
700                         + "; requires manual intervention.";
701                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
702                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
703                         "Deployment " + vfModuleName + " already " + "exists and is in an unknown state");
704                 logger.debug(error);
705                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
706             }
707         }
708
709
710         // Collect outputs from Base Modules and Volume Modules
711         Map<String, Object> baseModuleOutputs = null;
712         Map<String, Object> volumeGroupOutputs = null;
713
714         // If a Volume Group was provided, query its outputs for inclusion in Module input parameters
715         if (volumeGroupId != null) {
716             long subStartTime2 = System.currentTimeMillis();
717             DeploymentInfo volumeDeployment = null;
718             try {
719                 volumeDeployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, volumeGroupId);
720             } catch (MsoException me) {
721                 // Failed to query the Volume GroupDeployment due to a cloudify exception.
722                 String error = "Create VF Module: Query Volume Group " + volumeGroupId + " in " + cloudOwner + "/"
723                         + cloudSiteId + "/" + tenantId + ": " + me;
724                 logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId, cloudOwner, cloudSiteId,
725                         tenantId, CLOUDIFY, "queryDeployment(volume)", ErrorCode.DataError.getValue(),
726                         "Exception - queryDeployment(volume)", me);
727                 logger.debug(error);
728                 // Convert to a generic VnfException
729                 me.addContext("CreateVFModule(QueryVolume)");
730                 throw new VnfException(me);
731             }
732
733             if (volumeDeployment == null || volumeDeployment.getStatus() == DeploymentStatus.NOTFOUND) {
734                 String error = "Create VFModule: Attached Volume Group DOES NOT EXIST " + volumeGroupId + " in "
735                         + cloudSiteId + "/" + tenantId + " USER ERROR";
736                 logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId, cloudSiteId, tenantId,
737                         error, CLOUDIFY, "queryDeployment(volume)", ErrorCode.BusinessProcesssError.getValue(),
738                         "Create VFModule: Attached Volume Group DOES NOT EXIST");
739                 logger.debug(error);
740                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
741             } else {
742                 logger.debug("Found nested volume group");
743                 volumeGroupOutputs = volumeDeployment.getOutputs();
744                 this.sendMapToDebug(volumeGroupOutputs, "volumeGroupOutputs");
745             }
746         }
747
748         // If this is an Add-On Module, query the Base Module outputs
749         // Note: This will be performed whether or not the current request is for an
750         // Add-On Volume Group or Add-On VF Module
751
752         if (vf.getIsBase()) {
753             logger.debug("This is a BASE Module request");
754             vfRollback.setIsBase(true);
755         } else {
756             logger.debug("This is an Add-On Module request");
757
758             // Add-On Modules should always have a Base, but just treat as a warning if not provided.
759             // Add-on Volume requests may or may not specify a base.
760             if (!isVolumeRequest && baseVfModuleId == null) {
761                 logger.debug("WARNING:  Add-on Module request - no Base Module ID provided");
762             }
763
764             if (baseVfModuleId != null) {
765                 long subStartTime2 = System.currentTimeMillis();
766                 DeploymentInfo baseDeployment = null;
767                 try {
768                     baseDeployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, baseVfModuleId);
769                 } catch (MsoException me) {
770                     // Failed to query the Volume GroupDeployment due to a cloudify exception.
771                     String error = "Create VF Module: Query Base " + baseVfModuleId + " in " + cloudOwner + "/"
772                             + cloudSiteId + "/" + tenantId + ": " + me;
773                     logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId, cloudOwner,
774                             cloudSiteId, tenantId, CLOUDIFY, "queryDeployment(Base)", ErrorCode.DataError.getValue(),
775                             "Exception - queryDeployment(Base)", me);
776                     logger.debug(error);
777                     // Convert to a generic VnfException
778                     me.addContext("CreateVFModule(QueryBase)");
779                     throw new VnfException(me);
780                 }
781
782                 if (baseDeployment == null || baseDeployment.getStatus() == DeploymentStatus.NOTFOUND) {
783                     String error = "Create VFModule: Base Module DOES NOT EXIST " + baseVfModuleId + " in "
784                             + cloudSiteId + "/" + tenantId + " USER ERROR";
785                     logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId, cloudSiteId,
786                             tenantId, error, CLOUDIFY, "queryDeployment(Base)",
787                             ErrorCode.BusinessProcesssError.getValue(),
788                             "Create VFModule: Base " + "Module DOES NOT EXIST");
789                     logger.debug(error);
790                     throw new VnfException(error, MsoExceptionCategory.USERDATA);
791                 } else {
792                     logger.debug("Found base module");
793                     baseModuleOutputs = baseDeployment.getOutputs();
794                     this.sendMapToDebug(baseModuleOutputs, "baseModuleOutputs");
795                 }
796             }
797         }
798
799
800         // Ready to deploy the new VNF
801
802         // NOTE: For this section, heatTemplate is used for both HEAT templates and Cloudify blueprints.
803         // In final implementation (post-POC), the template object would either be generic or there would
804         // be a separate DB Table/Object for Blueprints.
805
806
807         // NOTE: The template is fixed for the VF Module. The environment is part of the customization.
808         HeatTemplate heatTemplate = null;
809         HeatEnvironment heatEnvironment = null;
810         if (isVolumeRequest) {
811             heatTemplate = vf.getVolumeHeatTemplate();
812             heatEnvironment = vfmc.getVolumeHeatEnv();
813         } else {
814             heatTemplate = vf.getModuleHeatTemplate();
815             heatEnvironment = vfmc.getHeatEnvironment();
816         }
817
818         if (heatTemplate == null) {
819             String error = "UpdateVF: No Heat Template ID defined in catalog database for " + vfModuleType
820                     + ", reqType=" + requestType;
821             logger.error("{} {} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Template ID",
822                     vfModuleType, "OpenStack", ErrorCode.DataError.getValue(), error);
823             throw new VnfException(error, MsoExceptionCategory.INTERNAL);
824         } else {
825             logger.debug("Got HEAT Template from DB: {}", heatTemplate.getHeatTemplate());
826         }
827
828         if (heatEnvironment == null) {
829             String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType;
830             logger.error("{} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Environment ID",
831                     "OpenStack", ErrorCode.DataError.getValue(), error);
832             // Alarm on this error, configuration must be fixed
833             throw new VnfException(error, MsoExceptionCategory.INTERNAL);
834         } else {
835             logger.debug("Got Heat Environment from DB: {}", heatEnvironment.getEnvironment());
836         }
837
838
839         try {
840             // All variables converted to their native object types
841             HashMap<String, Object> goldenInputs = new HashMap<String, Object>();
842             List<String> extraInputs = new ArrayList<String>();
843
844             // NOTE: SKIP THIS FOR CLOUDIFY for now. Just use what was passed in.
845             // This whole section needs to be rewritten.
846             Boolean skipInputChecks = false;
847
848             if (skipInputChecks) {
849                 goldenInputs = new HashMap<String, Object>();
850                 for (String key : inputs.keySet()) {
851                     goldenInputs.put(key, inputs.get(key));
852                 }
853             } else {
854                 // Build maps for the parameters (including aliases) to simplify checks
855                 HashMap<String, HeatTemplateParam> params = new HashMap<String, HeatTemplateParam>();
856
857                 Set<HeatTemplateParam> paramSet = heatTemplate.getParameters();
858                 logger.debug("paramSet has {} entries", paramSet.size());
859
860                 for (HeatTemplateParam htp : paramSet) {
861                     params.put(htp.getParamName(), htp);
862
863                     // Include aliases.
864                     String alias = htp.getParamAlias();
865                     if (alias != null && !alias.equals("") && !params.containsKey(alias)) {
866                         params.put(alias, htp);
867                     }
868                 }
869
870                 // First, convert all inputs to their "template" type
871                 for (String key : inputs.keySet()) {
872                     if (params.containsKey(key)) {
873                         Object value = cloudifyUtils.convertInputValue(inputs.get(key), params.get(key));
874                         if (value != null) {
875                             goldenInputs.put(key, value);
876                         } else {
877                             logger.debug("Failed to convert input " + key + "='" + inputs.get(key) + "' to "
878                                     + params.get(key).getParamType());
879                         }
880                     } else {
881                         extraInputs.add(key);
882                     }
883                 }
884
885                 if (!extraInputs.isEmpty()) {
886                     logger.debug("Ignoring extra inputs: " + extraInputs);
887                 }
888
889                 // Next add in Volume Group Outputs if there are any. Copy directly without conversions.
890                 if (volumeGroupOutputs != null && !volumeGroupOutputs.isEmpty()) {
891                     for (String key : volumeGroupOutputs.keySet()) {
892                         if (params.containsKey(key) && !goldenInputs.containsKey(key)) {
893                             goldenInputs.put(key, volumeGroupOutputs.get(key));
894                         }
895                     }
896                 }
897
898                 // Next add in Base Module Outputs if there are any. Copy directly without conversions.
899                 if (baseModuleOutputs != null && !baseModuleOutputs.isEmpty()) {
900                     for (String key : baseModuleOutputs.keySet()) {
901                         if (params.containsKey(key) && !goldenInputs.containsKey(key)) {
902                             goldenInputs.put(key, baseModuleOutputs.get(key));
903                         }
904                     }
905                 }
906
907                 // Last, add in values from the "environment" file.
908                 // These are added to the inputs, since Cloudify doesn't pass an environment file like Heat.
909
910                 // TODO: This may take a different form for Cloudify, but for now process it
911                 // with Heat environment file syntax
912                 StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());
913                 MsoHeatEnvironmentEntry mhee = new MsoHeatEnvironmentEntry(sb);
914
915                 if (mhee.getParameters() != null) {
916                     for (MsoHeatEnvironmentParameter envParam : mhee.getParameters()) {
917                         // If this is a template input, copy to golden inputs
918                         String envKey = envParam.getName();
919                         if (params.containsKey(envKey) && !goldenInputs.containsKey(envKey)) {
920                             Object value = cloudifyUtils.convertInputValue(envParam.getValue(), params.get(envKey));
921                             if (value != null) {
922                                 goldenInputs.put(envKey, value);
923                             } else {
924                                 logger.debug("Failed to convert environment parameter " + envKey + "='"
925                                         + envParam.getValue() + "' to " + params.get(envKey).getParamType());
926                             }
927                         }
928                     }
929                 }
930
931                 this.sendMapToDebug(goldenInputs, "Final inputs sent to Cloudify");
932
933
934                 // Check that required parameters have been supplied from any of the sources
935                 String missingParams = null;
936                 boolean checkRequiredParameters = true;
937                 try {
938                     String propertyString = this.environment.getProperty(MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
939                     if ("false".equalsIgnoreCase(propertyString) || "n".equalsIgnoreCase(propertyString)) {
940                         checkRequiredParameters = false;
941                         logger.debug("CheckRequiredParameters is FALSE. Will still check but then skip blocking... {}",
942                                 MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
943                     }
944                 } catch (Exception e) {
945                     // No problem - default is true
946                     logger.debug("An exception occured trying to get property {}",
947                             MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS, e);
948                 }
949
950
951                 for (HeatTemplateParam parm : heatTemplate.getParameters()) {
952                     if (parm.isRequired() && (!goldenInputs.containsKey(parm.getParamName()))) {
953                         logger.debug("adding to missing parameters list: {}", parm.getParamName());
954                         if (missingParams == null) {
955                             missingParams = parm.getParamName();
956                         } else {
957                             missingParams += "," + parm.getParamName();
958                         }
959                     }
960                 }
961
962                 if (missingParams != null) {
963                     if (checkRequiredParameters) {
964                         // Problem - missing one or more required parameters
965                         String error = "Create VFModule: Missing Required inputs: " + missingParams;
966                         logger.error("{} {} {} {} {}", MessageEnum.RA_MISSING_PARAM.toString(), missingParams, CLOUDIFY,
967                                 ErrorCode.DataError.getValue(), "Create VFModule: Missing Required inputs");
968                         logger.debug(error);
969                         throw new VnfException(error, MsoExceptionCategory.USERDATA);
970                     } else {
971                         logger.debug("found missing parameters [" + missingParams
972                                 + "] - but checkRequiredParameters is false -" + " will not block");
973                     }
974                 } else {
975                     logger.debug("No missing parameters found - ok to proceed");
976                 }
977
978             } // NOTE: END PARAMETER CHECKING
979
980             // Ready to deploy the VF Module.
981             // *First step - make sure the blueprint is loaded into Cloudify.
982             String blueprintName = heatTemplate.getTemplateName();
983             String blueprint = heatTemplate.getTemplateBody();
984             String blueprintId = blueprintName;
985
986             // Use the main blueprint name as the blueprint ID (strip yaml extensions).
987             if (blueprintId.endsWith(".yaml"))
988                 blueprintId = blueprintId.substring(0, blueprintId.lastIndexOf(".yaml"));
989
990             try {
991                 if (!cloudifyUtils.isBlueprintLoaded(cloudSiteId, blueprintId)) {
992                     logger.debug("Blueprint " + blueprintId + " is not loaded.  Will upload it now.");
993
994                     Map<String, byte[]> blueprintFiles = new HashMap<String, byte[]>();
995
996                     blueprintFiles.put(blueprintName, blueprint.getBytes());
997
998                     // TODO: Implement nested blueprint logic based on Cloudify structures.
999                     // For now, just use the Heat structures.
1000                     // The query returns a map of String->Object, where the map keys provide one layer of
1001                     // indirection from the Heat template names. For this case, assume the map key matches
1002                     // the nested blueprint name.
1003                     List<HeatTemplate> nestedBlueprints = heatTemplate.getChildTemplates();
1004                     if (nestedBlueprints != null) {
1005                         for (HeatTemplate nestedBlueprint : nestedBlueprints) {
1006                             blueprintFiles.put(nestedBlueprint.getTemplateName(),
1007                                     nestedBlueprint.getTemplateBody().getBytes());
1008                         }
1009                     }
1010
1011                     // TODO: Implement file artifact logic based on Cloudify structures.
1012                     // For now, just use the Heat structures.
1013                     List<HeatFiles> heatFiles = vf.getHeatFiles();
1014                     if (heatFiles != null) {
1015                         for (HeatFiles heatFile : heatFiles) {
1016                             blueprintFiles.put(heatFile.getFileName(), heatFile.getFileBody().getBytes());
1017                         }
1018                     }
1019
1020                     // Upload the blueprint package
1021                     cloudifyUtils.uploadBlueprint(cloudSiteId, blueprintId, blueprintName, blueprintFiles, false);
1022
1023                 }
1024             }
1025
1026             catch (MsoException me) {
1027                 me.addContext("CreateVFModule");
1028                 String error = "Create VF Module: Upload blueprint failed.  Blueprint=" + blueprintName + ": " + me;
1029                 logger.error("{} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType,
1030                         cloudSiteId, tenantId, CLOUDIFY, ErrorCode.DataError.getValue(),
1031                         "MsoException - uploadBlueprint", me);
1032                 logger.debug(error);
1033                 throw new VnfException(me);
1034             }
1035
1036             // Ignore MsoTenantNotFound and MsoStackAlreadyExists exceptions
1037             // because we already checked for those.
1038             long createDeploymentStarttime = System.currentTimeMillis();
1039             try {
1040                 // KLUDGE - Cloudify requires Tenant Name for Openstack. We have the ID.
1041                 // Go directly to Keystone until APIs could be updated to supply the name.
1042                 MsoTenant msoTenant = keystoneUtils.queryTenant(tenantId, cloudSiteId);
1043                 String tenantName = (msoTenant != null ? msoTenant.getTenantName() : tenantId);
1044
1045                 if (backout == null) {
1046                     backout = true;
1047                 }
1048
1049                 cloudifyDeployment = cloudifyUtils.createAndInstallDeployment(cloudSiteId, tenantName, vfModuleName,
1050                         blueprintId, goldenInputs, true, heatTemplate.getTimeoutMinutes(), backout.booleanValue());
1051
1052             } catch (MsoException me) {
1053                 me.addContext("CreateVFModule");
1054                 String error = "Create VF Module " + vfModuleType + " in " + cloudOwner + "/" + cloudSiteId + "/"
1055                         + tenantId + ": " + me;
1056                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType,
1057                         cloudOwner, cloudSiteId, tenantId, CLOUDIFY, ErrorCode.DataError.getValue(),
1058                         "MsoException - createDeployment", me);
1059                 logger.debug(error);
1060                 throw new VnfException(me);
1061             } catch (NullPointerException npe) {
1062                 String error = "Create VFModule " + vfModuleType + " in " + cloudOwner + "/" + cloudSiteId + "/"
1063                         + tenantId + ": " + npe;
1064                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType,
1065                         cloudOwner, cloudSiteId, tenantId, CLOUDIFY, ErrorCode.DataError.getValue(),
1066                         "NullPointerException - createDeployment", npe);
1067                 logger.debug(error);
1068                 logger.debug("NULL POINTER EXCEPTION at cloudify.createAndInstallDeployment");
1069                 // npe.addContext ("CreateVNF");
1070                 throw new VnfException("NullPointerException during cloudify.createAndInstallDeployment");
1071             } catch (Exception e) {
1072                 logger.debug("unhandled exception at cloudify.createAndInstallDeployment");
1073                 throw new VnfException("Exception during cloudify.createAndInstallDeployment! " + e.getMessage());
1074             }
1075         } catch (Exception e) {
1076             logger.debug("unhandled exception in create VF");
1077             throw new VnfException("Exception during create VF " + e.getMessage());
1078
1079         }
1080
1081         // Reach this point if create is successful.
1082         // Populate remaining rollback info and response parameters.
1083         vfRollback.setVnfCreated(true);
1084         vfRollback.setVnfId(cloudifyDeployment.getId());
1085         vnfId.value = cloudifyDeployment.getId();
1086         outputs.value = copyStringOutputs(cloudifyDeployment.getOutputs());
1087
1088         rollback.value = vfRollback;
1089
1090         logger.debug("VF Module successfully created", vfModuleName);
1091         return;
1092     }
1093
1094     public void deleteVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfName,
1095             MsoRequest msoRequest, Holder<Map<String, String>> outputs) throws VnfException {
1096         logger.debug("Deleting VF " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId);
1097         // Will capture execution time for metrics
1098         long startTime = System.currentTimeMillis();
1099
1100         // 1702 capture the output parameters on a delete
1101         // so we'll need to query first
1102         DeploymentInfo deployment = null;
1103         try {
1104             deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
1105         } catch (MsoException me) {
1106             // Failed to query the deployment. Convert to a generic VnfException
1107             me.addContext("DeleteVFModule");
1108             String error = "Delete VFModule: Query to get outputs: " + vnfName + " in " + cloudOwner + "/" + cloudSiteId
1109                     + "/" + tenantId + ": " + me;
1110             logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner, cloudSiteId, tenantId,
1111                     CLOUDIFY, "QueryDeployment", ErrorCode.DataError.getValue(), "Exception - QueryDeployment", me);
1112             logger.debug(error);
1113             throw new VnfException(me);
1114         }
1115         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected
1116         // Object types
1117         outputs.value = convertMapStringObjectToStringString(deployment.getOutputs());
1118
1119         // Use the MsoHeatUtils to delete the stack. Set the polling flag to true.
1120         // The possible outcomes of deleteStack are a StackInfo object with status
1121         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
1122         // could be thrown.
1123         long subStartTime = System.currentTimeMillis();
1124         try {
1125             cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantId, vnfName, 5);
1126         } catch (MsoException me) {
1127             me.addContext("DeleteVfModule");
1128             // Convert to a generic VnfException
1129             String error =
1130                     "Delete VF: " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId + ": " + me;
1131             logger.error(BRACKETS, MessageEnum.RA_DELETE_VNF_ERR.toString(), vnfName, cloudOwner, cloudSiteId, tenantId,
1132                     "DeleteDeployment", "DeleteDeployment", ErrorCode.DataError.getValue(),
1133                     "Exception - DeleteDeployment: " + me.getMessage());
1134             logger.debug(error);
1135             throw new VnfException(me);
1136         }
1137
1138         // On success, nothing is returned.
1139         return;
1140     }
1141
1142     // TODO: Should Update be supported for Cloudify? What would this look like?
1143     @Override
1144     public void updateVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfType,
1145             String vnfVersion, String vnfName, String requestType, String volumeGroupHeatStackId,
1146             String baseVfHeatStackId, String vfModuleStackId, String modelCustomizationUuid, Map<String, Object> inputs,
1147             MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
1148             throws VnfException {
1149         // This operation is not currently supported for Cloudify-orchestrated VF Modules.
1150         logger.debug("Update VF Module command attempted but not supported");
1151         throw new VnfException("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1152     }
1153
1154 }