9d6c94474733c45ed5cace2a47c97a2b93b24337
[so.git] /
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
87     @Autowired
88     protected CloudConfig cloudConfig;
89
90     @Autowired
91     private VFModuleCustomizationRepository vfModuleCustomRepo;
92
93     @Autowired
94     private Environment environment;
95
96     @Autowired
97     protected MsoKeystoneUtils keystoneUtils;
98
99     @Autowired
100     protected MsoCloudifyUtils cloudifyUtils;
101
102     /**
103      * DO NOT use that constructor to instantiate this class, the msoPropertiesfactory will be NULL.
104      *
105      * @see MsoVnfCloudifyAdapterImpl#MsoVnfAdapterImpl(MsoPropertiesFactory, CloudConfigFactory)
106      */
107     public MsoVnfCloudifyAdapterImpl() {
108
109     }
110
111     /**
112      * Health Check web method. Does nothing but return to show the adapter is deployed.
113      */
114     @Override
115     public void healthCheck() {
116         logger.debug("Health check call in VNF Cloudify Adapter");
117     }
118
119     /**
120      * This is the "Create VNF" web service implementation. This function is now unsupported and will return an error.
121      *
122      */
123     @Override
124     public void createVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfType, String vnfVersion,
125             String vnfName, String requestType, String volumeGroupHeatStackId, Map<String, Object> inputs,
126             Boolean failIfExists, Boolean backout, Boolean enableBridge, MsoRequest msoRequest, Holder<String> vnfId,
127             Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback) throws VnfException {
128         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
129         logger.debug("CreateVNF command attempted but not supported");
130         throw new VnfException("CreateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
131     }
132
133     /**
134      * This is the "Update VNF" web service implementation. This function is now unsupported and will return an error.
135      *
136      */
137     @Override
138     public void updateVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfType, String vnfVersion,
139             String vnfName, String requestType, String volumeGroupHeatStackId, Map<String, Object> inputs,
140             MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
141             throws VnfException {
142         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
143         logger.debug("UpdateVNF command attempted but not supported");
144         throw new VnfException("UpdateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
145     }
146
147     /**
148      * This is the "Query VNF" web service implementation.
149      *
150      * This really should be QueryVfModule, but nobody ever changed it.
151      *
152      * For Cloudify, this will look up a deployment by its deployment ID, which is really the same as deployment name,
153      * since it assigned by the client when a deployment is created. Also, the input cloudSiteId is used only to
154      * identify which Cloudify instance to query, and the tenantId is ignored (since that really only applies for
155      * Openstack/Heat).
156      *
157      * The method returns an indicator that the VNF exists, along with its status and outputs. The input "vnfName" will
158      * also be reflected back as its ID.
159      *
160      * @param cloudSiteId CLLI code of the cloud site in which to query
161      * @param cloudOwner cloud owner of the cloud site in which to query
162      * @param tenantId Openstack tenant identifier - ignored for Cloudify
163      * @param vnfName VNF Name (should match a deployment ID)
164      * @param msoRequest Request tracking information for logs
165      * @param vnfExists Flag reporting the result of the query
166      * @param vnfId Holder for output VNF ID
167      * @param outputs Holder for Map of VNF outputs from Cloudify deployment (assigned IPs, etc)
168      */
169     @Override
170     public void queryVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfName, MsoRequest msoRequest,
171             Holder<Boolean> vnfExists, Holder<String> vnfId, Holder<VnfStatus> status,
172             Holder<Map<String, String>> outputs) throws VnfException {
173         logger.debug("Querying VNF {} in {}", vnfName, cloudSiteId + "/" + tenantId);
174
175         // Will capture execution time for metrics
176         long startTime = System.currentTimeMillis();
177         long subStartTime = System.currentTimeMillis();
178
179         DeploymentInfo deployment = null;
180
181         try {
182             deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
183         } catch (MsoCloudifyManagerNotFound e) {
184             // This site does not have a Cloudify Manager.
185             // This isn't an error, just means we won't find the VNF here.
186             deployment = null;
187         } catch (MsoException me) {
188             // Failed to query the Deployment due to a cloudify exception.
189             // Convert to a generic VnfException
190             me.addContext("QueryVNF");
191             String error = "Query VNF (Cloudify): " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
192                     + ": " + me;
193             logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner,
194                     cloudSiteId, tenantId, CLOUDIFY, "QueryVNF", ErrorCode.DataError.getValue(),
195                     "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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_DELETE_VNF_ERR.toString(), vfModuleId, cloudOwner,
285                     cloudSiteId, 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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(),
651                             vfModuleName, cloudOwner, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment",
652                             ErrorCode.DataError.getValue(), "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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
672                         cloudOwner, 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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
681                         cloudOwner, 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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
691                         cloudOwner, 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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
702                         cloudOwner, 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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId,
725                         cloudOwner, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment(volume)",
726                         ErrorCode.DataError.getValue(), "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("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId,
737                         cloudSiteId, tenantId, error, CLOUDIFY, "queryDeployment(volume)",
738                         ErrorCode.BusinessProcesssError.getValue(),
739                         "Create VFModule: Attached Volume Group DOES NOT EXIST");
740                 logger.debug(error);
741                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
742             } else {
743                 logger.debug("Found nested volume group");
744                 volumeGroupOutputs = volumeDeployment.getOutputs();
745                 this.sendMapToDebug(volumeGroupOutputs, "volumeGroupOutputs");
746             }
747         }
748
749         // If this is an Add-On Module, query the Base Module outputs
750         // Note: This will be performed whether or not the current request is for an
751         // Add-On Volume Group or Add-On VF Module
752
753         if (vf.getIsBase()) {
754             logger.debug("This is a BASE Module request");
755             vfRollback.setIsBase(true);
756         } else {
757             logger.debug("This is an Add-On Module request");
758
759             // Add-On Modules should always have a Base, but just treat as a warning if not provided.
760             // Add-on Volume requests may or may not specify a base.
761             if (!isVolumeRequest && baseVfModuleId == null) {
762                 logger.debug("WARNING:  Add-on Module request - no Base Module ID provided");
763             }
764
765             if (baseVfModuleId != null) {
766                 long subStartTime2 = System.currentTimeMillis();
767                 DeploymentInfo baseDeployment = null;
768                 try {
769                     baseDeployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, baseVfModuleId);
770                 } catch (MsoException me) {
771                     // Failed to query the Volume GroupDeployment due to a cloudify exception.
772                     String error = "Create VF Module: Query Base " + baseVfModuleId + " in " + cloudOwner + "/"
773                             + cloudSiteId + "/" + tenantId + ": " + me;
774                     logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId,
775                             cloudOwner, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment(Base)",
776                             ErrorCode.DataError.getValue(), "Exception - queryDeployment(Base)", me);
777                     logger.debug(error);
778                     // Convert to a generic VnfException
779                     me.addContext("CreateVFModule(QueryBase)");
780                     throw new VnfException(me);
781                 }
782
783                 if (baseDeployment == null || baseDeployment.getStatus() == DeploymentStatus.NOTFOUND) {
784                     String error = "Create VFModule: Base Module DOES NOT EXIST " + baseVfModuleId + " in "
785                             + cloudSiteId + "/" + tenantId + " USER ERROR";
786                     logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId,
787                             cloudSiteId, tenantId, error, CLOUDIFY, "queryDeployment(Base)",
788                             ErrorCode.BusinessProcesssError.getValue(),
789                             "Create VFModule: Base " + "Module DOES NOT EXIST");
790                     logger.debug(error);
791                     throw new VnfException(error, MsoExceptionCategory.USERDATA);
792                 } else {
793                     logger.debug("Found base module");
794                     baseModuleOutputs = baseDeployment.getOutputs();
795                     this.sendMapToDebug(baseModuleOutputs, "baseModuleOutputs");
796                 }
797             }
798         }
799
800
801         // Ready to deploy the new VNF
802
803         // NOTE: For this section, heatTemplate is used for both HEAT templates and Cloudify blueprints.
804         // In final implementation (post-POC), the template object would either be generic or there would
805         // be a separate DB Table/Object for Blueprints.
806
807
808         // NOTE: The template is fixed for the VF Module. The environment is part of the customization.
809         HeatTemplate heatTemplate = null;
810         HeatEnvironment heatEnvironment = null;
811         if (isVolumeRequest) {
812             heatTemplate = vf.getVolumeHeatTemplate();
813             heatEnvironment = vfmc.getVolumeHeatEnv();
814         } else {
815             heatTemplate = vf.getModuleHeatTemplate();
816             heatEnvironment = vfmc.getHeatEnvironment();
817         }
818
819         if (heatTemplate == null) {
820             String error = "UpdateVF: No Heat Template ID defined in catalog database for " + vfModuleType
821                     + ", reqType=" + requestType;
822             logger.error("{} {} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Template ID",
823                     vfModuleType, "OpenStack", ErrorCode.DataError.getValue(), error);
824             throw new VnfException(error, MsoExceptionCategory.INTERNAL);
825         } else {
826             logger.debug("Got HEAT Template from DB: {}", heatTemplate.getHeatTemplate());
827         }
828
829         if (heatEnvironment == null) {
830             String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType;
831             logger.error("{} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Environment ID",
832                     "OpenStack", ErrorCode.DataError.getValue(), error);
833             // Alarm on this error, configuration must be fixed
834             throw new VnfException(error, MsoExceptionCategory.INTERNAL);
835         } else {
836             logger.debug("Got Heat Environment from DB: {}", heatEnvironment.getEnvironment());
837         }
838
839
840         try {
841             // All variables converted to their native object types
842             HashMap<String, Object> goldenInputs = new HashMap<String, Object>();
843             List<String> extraInputs = new ArrayList<String>();
844
845             // NOTE: SKIP THIS FOR CLOUDIFY for now. Just use what was passed in.
846             // This whole section needs to be rewritten.
847             Boolean skipInputChecks = false;
848
849             if (skipInputChecks) {
850                 goldenInputs = new HashMap<String, Object>();
851                 for (String key : inputs.keySet()) {
852                     goldenInputs.put(key, inputs.get(key));
853                 }
854             } else {
855                 // Build maps for the parameters (including aliases) to simplify checks
856                 HashMap<String, HeatTemplateParam> params = new HashMap<String, HeatTemplateParam>();
857
858                 Set<HeatTemplateParam> paramSet = heatTemplate.getParameters();
859                 logger.debug("paramSet has {} entries", paramSet.size());
860
861                 for (HeatTemplateParam htp : paramSet) {
862                     params.put(htp.getParamName(), htp);
863
864                     // Include aliases.
865                     String alias = htp.getParamAlias();
866                     if (alias != null && !alias.equals("") && !params.containsKey(alias)) {
867                         params.put(alias, htp);
868                     }
869                 }
870
871                 // First, convert all inputs to their "template" type
872                 for (String key : inputs.keySet()) {
873                     if (params.containsKey(key)) {
874                         Object value = cloudifyUtils.convertInputValue(inputs.get(key), params.get(key));
875                         if (value != null) {
876                             goldenInputs.put(key, value);
877                         } else {
878                             logger.debug("Failed to convert input " + key + "='" + inputs.get(key) + "' to "
879                                     + params.get(key).getParamType());
880                         }
881                     } else {
882                         extraInputs.add(key);
883                     }
884                 }
885
886                 if (!extraInputs.isEmpty()) {
887                     logger.debug("Ignoring extra inputs: " + extraInputs);
888                 }
889
890                 // Next add in Volume Group Outputs if there are any. Copy directly without conversions.
891                 if (volumeGroupOutputs != null && !volumeGroupOutputs.isEmpty()) {
892                     for (String key : volumeGroupOutputs.keySet()) {
893                         if (params.containsKey(key) && !goldenInputs.containsKey(key)) {
894                             goldenInputs.put(key, volumeGroupOutputs.get(key));
895                         }
896                     }
897                 }
898
899                 // Next add in Base Module Outputs if there are any. Copy directly without conversions.
900                 if (baseModuleOutputs != null && !baseModuleOutputs.isEmpty()) {
901                     for (String key : baseModuleOutputs.keySet()) {
902                         if (params.containsKey(key) && !goldenInputs.containsKey(key)) {
903                             goldenInputs.put(key, baseModuleOutputs.get(key));
904                         }
905                     }
906                 }
907
908                 // Last, add in values from the "environment" file.
909                 // These are added to the inputs, since Cloudify doesn't pass an environment file like Heat.
910
911                 // TODO: This may take a different form for Cloudify, but for now process it
912                 // with Heat environment file syntax
913                 StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());
914                 MsoHeatEnvironmentEntry mhee = new MsoHeatEnvironmentEntry(sb);
915
916                 if (mhee.getParameters() != null) {
917                     for (MsoHeatEnvironmentParameter envParam : mhee.getParameters()) {
918                         // If this is a template input, copy to golden inputs
919                         String envKey = envParam.getName();
920                         if (params.containsKey(envKey) && !goldenInputs.containsKey(envKey)) {
921                             Object value = cloudifyUtils.convertInputValue(envParam.getValue(), params.get(envKey));
922                             if (value != null) {
923                                 goldenInputs.put(envKey, value);
924                             } else {
925                                 logger.debug("Failed to convert environment parameter " + envKey + "='"
926                                         + envParam.getValue() + "' to " + params.get(envKey).getParamType());
927                             }
928                         }
929                     }
930                 }
931
932                 this.sendMapToDebug(goldenInputs, "Final inputs sent to Cloudify");
933
934
935                 // Check that required parameters have been supplied from any of the sources
936                 String missingParams = null;
937                 boolean checkRequiredParameters = true;
938                 try {
939                     String propertyString = this.environment.getProperty(MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
940                     if ("false".equalsIgnoreCase(propertyString) || "n".equalsIgnoreCase(propertyString)) {
941                         checkRequiredParameters = false;
942                         logger.debug("CheckRequiredParameters is FALSE. Will still check but then skip blocking... {}",
943                                 MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
944                     }
945                 } catch (Exception e) {
946                     // No problem - default is true
947                     logger.debug("An exception occured trying to get property {}",
948                             MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS, e);
949                 }
950
951
952                 for (HeatTemplateParam parm : heatTemplate.getParameters()) {
953                     if (parm.isRequired() && (!goldenInputs.containsKey(parm.getParamName()))) {
954                         logger.debug("adding to missing parameters list: {}", parm.getParamName());
955                         if (missingParams == null) {
956                             missingParams = parm.getParamName();
957                         } else {
958                             missingParams += "," + parm.getParamName();
959                         }
960                     }
961                 }
962
963                 if (missingParams != null) {
964                     if (checkRequiredParameters) {
965                         // Problem - missing one or more required parameters
966                         String error = "Create VFModule: Missing Required inputs: " + missingParams;
967                         logger.error("{} {} {} {} {}", MessageEnum.RA_MISSING_PARAM.toString(), missingParams, CLOUDIFY,
968                                 ErrorCode.DataError.getValue(), "Create VFModule: Missing Required inputs");
969                         logger.debug(error);
970                         throw new VnfException(error, MsoExceptionCategory.USERDATA);
971                     } else {
972                         logger.debug("found missing parameters [" + missingParams
973                                 + "] - but checkRequiredParameters is false -" + " will not block");
974                     }
975                 } else {
976                     logger.debug("No missing parameters found - ok to proceed");
977                 }
978
979             } // NOTE: END PARAMETER CHECKING
980
981             // Ready to deploy the VF Module.
982             // *First step - make sure the blueprint is loaded into Cloudify.
983             String blueprintName = heatTemplate.getTemplateName();
984             String blueprint = heatTemplate.getTemplateBody();
985             String blueprintId = blueprintName;
986
987             // Use the main blueprint name as the blueprint ID (strip yaml extensions).
988             if (blueprintId.endsWith(".yaml"))
989                 blueprintId = blueprintId.substring(0, blueprintId.lastIndexOf(".yaml"));
990
991             try {
992                 if (!cloudifyUtils.isBlueprintLoaded(cloudSiteId, blueprintId)) {
993                     logger.debug("Blueprint " + blueprintId + " is not loaded.  Will upload it now.");
994
995                     Map<String, byte[]> blueprintFiles = new HashMap<String, byte[]>();
996
997                     blueprintFiles.put(blueprintName, blueprint.getBytes());
998
999                     // TODO: Implement nested blueprint logic based on Cloudify structures.
1000                     // For now, just use the Heat structures.
1001                     // The query returns a map of String->Object, where the map keys provide one layer of
1002                     // indirection from the Heat template names. For this case, assume the map key matches
1003                     // the nested blueprint name.
1004                     List<HeatTemplate> nestedBlueprints = heatTemplate.getChildTemplates();
1005                     if (nestedBlueprints != null) {
1006                         for (HeatTemplate nestedBlueprint : nestedBlueprints) {
1007                             blueprintFiles.put(nestedBlueprint.getTemplateName(),
1008                                     nestedBlueprint.getTemplateBody().getBytes());
1009                         }
1010                     }
1011
1012                     // TODO: Implement file artifact logic based on Cloudify structures.
1013                     // For now, just use the Heat structures.
1014                     List<HeatFiles> heatFiles = vf.getHeatFiles();
1015                     if (heatFiles != null) {
1016                         for (HeatFiles heatFile : heatFiles) {
1017                             blueprintFiles.put(heatFile.getFileName(), heatFile.getFileBody().getBytes());
1018                         }
1019                     }
1020
1021                     // Upload the blueprint package
1022                     cloudifyUtils.uploadBlueprint(cloudSiteId, blueprintId, blueprintName, blueprintFiles, false);
1023
1024                 }
1025             }
1026
1027             catch (MsoException me) {
1028                 me.addContext("CreateVFModule");
1029                 String error = "Create VF Module: Upload blueprint failed.  Blueprint=" + blueprintName + ": " + me;
1030                 logger.error("{} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType,
1031                         cloudSiteId, tenantId, CLOUDIFY, ErrorCode.DataError.getValue(),
1032                         "MsoException - uploadBlueprint", me);
1033                 logger.debug(error);
1034                 throw new VnfException(me);
1035             }
1036
1037             // Ignore MsoTenantNotFound and MsoStackAlreadyExists exceptions
1038             // because we already checked for those.
1039             long createDeploymentStarttime = System.currentTimeMillis();
1040             try {
1041                 // KLUDGE - Cloudify requires Tenant Name for Openstack. We have the ID.
1042                 // Go directly to Keystone until APIs could be updated to supply the name.
1043                 MsoTenant msoTenant = keystoneUtils.queryTenant(tenantId, cloudSiteId);
1044                 String tenantName = (msoTenant != null ? msoTenant.getTenantName() : tenantId);
1045
1046                 if (backout == null) {
1047                     backout = true;
1048                 }
1049
1050                 cloudifyDeployment = cloudifyUtils.createAndInstallDeployment(cloudSiteId, tenantName, vfModuleName,
1051                         blueprintId, goldenInputs, true, heatTemplate.getTimeoutMinutes(), backout.booleanValue());
1052
1053             } catch (MsoException me) {
1054                 me.addContext("CreateVFModule");
1055                 String error = "Create VF Module " + vfModuleType + " in " + cloudOwner + "/" + cloudSiteId + "/"
1056                         + tenantId + ": " + me;
1057                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType,
1058                         cloudOwner, cloudSiteId, tenantId, CLOUDIFY, ErrorCode.DataError.getValue(),
1059                         "MsoException - createDeployment", me);
1060                 logger.debug(error);
1061                 throw new VnfException(me);
1062             } catch (NullPointerException npe) {
1063                 String error = "Create VFModule " + vfModuleType + " in " + cloudOwner + "/" + cloudSiteId + "/"
1064                         + tenantId + ": " + npe;
1065                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType,
1066                         cloudOwner, cloudSiteId, tenantId, CLOUDIFY, ErrorCode.DataError.getValue(),
1067                         "NullPointerException - createDeployment", npe);
1068                 logger.debug(error);
1069                 logger.debug("NULL POINTER EXCEPTION at cloudify.createAndInstallDeployment");
1070                 // npe.addContext ("CreateVNF");
1071                 throw new VnfException("NullPointerException during cloudify.createAndInstallDeployment");
1072             } catch (Exception e) {
1073                 logger.debug("unhandled exception at cloudify.createAndInstallDeployment");
1074                 throw new VnfException("Exception during cloudify.createAndInstallDeployment! " + e.getMessage());
1075             }
1076         } catch (Exception e) {
1077             logger.debug("unhandled exception in create VF");
1078             throw new VnfException("Exception during create VF " + e.getMessage());
1079
1080         }
1081
1082         // Reach this point if create is successful.
1083         // Populate remaining rollback info and response parameters.
1084         vfRollback.setVnfCreated(true);
1085         vfRollback.setVnfId(cloudifyDeployment.getId());
1086         vnfId.value = cloudifyDeployment.getId();
1087         outputs.value = copyStringOutputs(cloudifyDeployment.getOutputs());
1088
1089         rollback.value = vfRollback;
1090
1091         logger.debug("VF Module successfully created", vfModuleName);
1092         return;
1093     }
1094
1095     public void deleteVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfName,
1096             MsoRequest msoRequest, Holder<Map<String, String>> outputs) throws VnfException {
1097         logger.debug("Deleting VF " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId);
1098         // Will capture execution time for metrics
1099         long startTime = System.currentTimeMillis();
1100
1101         // 1702 capture the output parameters on a delete
1102         // so we'll need to query first
1103         DeploymentInfo deployment = null;
1104         try {
1105             deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
1106         } catch (MsoException me) {
1107             // Failed to query the deployment. Convert to a generic VnfException
1108             me.addContext("DeleteVFModule");
1109             String error = "Delete VFModule: Query to get outputs: " + vnfName + " in " + cloudOwner + "/" + cloudSiteId
1110                     + "/" + tenantId + ": " + me;
1111             logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner,
1112                     cloudSiteId, tenantId, CLOUDIFY, "QueryDeployment", ErrorCode.DataError.getValue(),
1113                     "Exception - QueryDeployment", me);
1114             logger.debug(error);
1115             throw new VnfException(me);
1116         }
1117         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected
1118         // Object types
1119         outputs.value = convertMapStringObjectToStringString(deployment.getOutputs());
1120
1121         // Use the MsoHeatUtils to delete the stack. Set the polling flag to true.
1122         // The possible outcomes of deleteStack are a StackInfo object with status
1123         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
1124         // could be thrown.
1125         long subStartTime = System.currentTimeMillis();
1126         try {
1127             cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantId, vnfName, 5);
1128         } catch (MsoException me) {
1129             me.addContext("DeleteVfModule");
1130             // Convert to a generic VnfException
1131             String error =
1132                     "Delete VF: " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId + ": " + me;
1133             logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_DELETE_VNF_ERR.toString(), vnfName, cloudOwner,
1134                     cloudSiteId, tenantId, "DeleteDeployment", "DeleteDeployment", ErrorCode.DataError.getValue(),
1135                     "Exception - DeleteDeployment: " + me.getMessage());
1136             logger.debug(error);
1137             throw new VnfException(me);
1138         }
1139
1140         // On success, nothing is returned.
1141         return;
1142     }
1143
1144     // TODO: Should Update be supported for Cloudify? What would this look like?
1145     @Override
1146     public void updateVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfType,
1147             String vnfVersion, String vnfName, String requestType, String volumeGroupHeatStackId,
1148             String baseVfHeatStackId, String vfModuleStackId, String modelCustomizationUuid, Map<String, Object> inputs,
1149             MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
1150             throws VnfException {
1151         // This operation is not currently supported for Cloudify-orchestrated VF Modules.
1152         logger.debug("Update VF Module command attempted but not supported");
1153         throw new VnfException("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1154     }
1155
1156 }