d092b49d8c2bad74667615df6f029abc5b6e21d1
[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     private static final String BRACKETS = "{} {} {} {} {} {} {} {} {}";
87     private static final String OPENSTACK = "OpenStack";
88
89     @Autowired
90     protected CloudConfig cloudConfig;
91
92     @Autowired
93     private VFModuleCustomizationRepository vfModuleCustomRepo;
94
95     @Autowired
96     private Environment environment;
97
98     @Autowired
99     protected MsoKeystoneUtils keystoneUtils;
100
101     @Autowired
102     protected MsoCloudifyUtils cloudifyUtils;
103
104     /**
105      * DO NOT use that constructor to instantiate this class, the msoPropertiesfactory will be NULL.
106      *
107      * @see MsoVnfCloudifyAdapterImpl#MsoVnfAdapterImpl(MsoPropertiesFactory, CloudConfigFactory)
108      */
109     public MsoVnfCloudifyAdapterImpl() {
110
111     }
112
113     /**
114      * Health Check web method. Does nothing but return to show the adapter is deployed.
115      */
116     @Override
117     public void healthCheck() {
118         logger.debug("Health check call in VNF Cloudify Adapter");
119     }
120
121     /**
122      * This is the "Create VNF" web service implementation. This function is now unsupported and will return an error.
123      *
124      */
125     @Override
126     public void createVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfType, String vnfVersion,
127             String vnfName, String requestType, String volumeGroupHeatStackId, Map<String, Object> inputs,
128             Boolean failIfExists, Boolean backout, Boolean enableBridge, MsoRequest msoRequest, Holder<String> vnfId,
129             Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback) throws VnfException {
130         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
131         logger.debug("CreateVNF command attempted but not supported");
132         throw new VnfException("CreateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
133     }
134
135     /**
136      * This is the "Update VNF" web service implementation. This function is now unsupported and will return an error.
137      *
138      */
139     @Override
140     public void updateVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfType, String vnfVersion,
141             String vnfName, String requestType, String volumeGroupHeatStackId, Map<String, Object> inputs,
142             MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
143             throws VnfException {
144         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
145         logger.debug("UpdateVNF command attempted but not supported");
146         throw new VnfException("UpdateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
147     }
148
149     /**
150      * This is the "Query VNF" web service implementation.
151      *
152      * This really should be QueryVfModule, but nobody ever changed it.
153      *
154      * For Cloudify, this will look up a deployment by its deployment ID, which is really the same as deployment name,
155      * since it assigned by the client when a deployment is created. Also, the input cloudSiteId is used only to
156      * identify which Cloudify instance to query, and the tenantId is ignored (since that really only applies for
157      * Openstack/Heat).
158      *
159      * The method returns an indicator that the VNF exists, along with its status and outputs. The input "vnfName" will
160      * also be reflected back as its ID.
161      *
162      * @param cloudSiteId CLLI code of the cloud site in which to query
163      * @param cloudOwner cloud owner of the cloud site in which to query
164      * @param tenantId Openstack tenant identifier - ignored for Cloudify
165      * @param vnfName VNF Name (should match a deployment ID)
166      * @param msoRequest Request tracking information for logs
167      * @param vnfExists Flag reporting the result of the query
168      * @param vnfId Holder for output VNF ID
169      * @param outputs Holder for Map of VNF outputs from Cloudify deployment (assigned IPs, etc)
170      */
171     @Override
172     public void queryVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfName, MsoRequest msoRequest,
173             Holder<Boolean> vnfExists, Holder<String> vnfId, Holder<VnfStatus> status,
174             Holder<Map<String, String>> outputs) throws VnfException {
175         logger.debug("Querying VNF {} in {}", vnfName, cloudSiteId + "/" + tenantId);
176
177         // Will capture execution time for metrics
178         long startTime = System.currentTimeMillis();
179         long subStartTime = System.currentTimeMillis();
180
181         DeploymentInfo deployment = null;
182
183         try {
184             deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
185         } catch (MsoCloudifyManagerNotFound e) {
186             // This site does not have a Cloudify Manager.
187             // This isn't an error, just means we won't find the VNF here.
188             deployment = null;
189         } catch (MsoException me) {
190             // Failed to query the Deployment due to a cloudify exception.
191             // Convert to a generic VnfException
192             me.addContext("QueryVNF");
193             String error = "Query VNF (Cloudify): " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
194                     + ": " + me;
195             logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner, cloudSiteId, tenantId,
196                     CLOUDIFY, "QueryVNF", ErrorCode.DataError.getValue(), "Exception - queryDeployment", me);
197             logger.debug(error);
198             throw new VnfException(me);
199         }
200
201         if (deployment != null && deployment.getStatus() != DeploymentStatus.NOTFOUND) {
202             vnfExists.value = Boolean.TRUE;
203             status.value = deploymentStatusToVnfStatus(deployment);
204             vnfId.value = deployment.getId();
205             outputs.value = copyStringOutputs(deployment.getOutputs());
206
207             logger.debug("VNF {} found in Cloudify, ID = {}", vnfName, vnfId.value);
208         } else {
209             vnfExists.value = Boolean.FALSE;
210             status.value = VnfStatus.NOTFOUND;
211             vnfId.value = null;
212             outputs.value = new HashMap<String, String>(); // Return as an empty map
213
214             logger.debug("VNF {} not found", vnfName);
215         }
216         return;
217     }
218
219
220     /**
221      * This is the "Delete VNF" web service implementation. This function is now unsupported and will return an error.
222      *
223      */
224     @Override
225     public void deleteVnf(String cloudSiteId, String cloudOwner, String tenantId, String vnfName, MsoRequest msoRequest)
226             throws VnfException {
227
228         // This operation is no longer supported at the VNF level. The adapter is only called to deploy modules.
229         logger.debug("DeleteVNF command attempted but not supported");
230         throw new VnfException("DeleteVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
231     }
232
233     /**
234      * This web service endpoint will rollback a previous Create VNF operation. A rollback object is returned to the
235      * client in a successful creation response. The client can pass that object as-is back to the rollbackVnf operation
236      * to undo the creation.
237      *
238      * TODO: This should be rollbackVfModule and/or rollbackVolumeGroup, but APIs were apparently never updated.
239      */
240     @Override
241     public void rollbackVnf(VnfRollback rollback) throws VnfException {
242         long startTime = System.currentTimeMillis();
243         // rollback may be null (e.g. if stack already existed when Create was called)
244         if (rollback == null) {
245             logger.info("{} {} {}", MessageEnum.RA_ROLLBACK_NULL.toString(), OPENSTACK, "rollbackVnf");
246             return;
247         }
248
249         // Don't rollback if nothing was done originally
250         if (!rollback.getVnfCreated()) {
251             return;
252         }
253
254         // Get the elements of the VnfRollback object for easier access
255         String cloudSiteId = rollback.getCloudSiteId();
256         String cloudOwner = rollback.getCloudOwner();
257         String tenantId = rollback.getTenantId();
258         String vfModuleId = rollback.getVfModuleStackId();
259
260         logger.debug("Rolling Back VF Module {} in {}", vfModuleId, cloudOwner + "/" + cloudSiteId + "/" + tenantId);
261
262         DeploymentInfo deployment = null;
263
264         // Use the MsoCloudifyUtils to delete the deployment. Set the polling flag to true.
265         // The possible outcomes of deleteStack are a StackInfo object with status
266         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
267         // could be thrown.
268         long subStartTime = System.currentTimeMillis();
269         try {
270             // KLUDGE - Cloudify requires Tenant Name for Openstack. We have the ID.
271             // Go directly to Keystone until APIs could be updated to supply the name.
272             MsoTenant msoTenant = keystoneUtils.queryTenant(tenantId, cloudSiteId);
273             String tenantName = (msoTenant != null ? msoTenant.getTenantName() : tenantId);
274
275             // TODO: Get a reasonable timeout. Use a global property, or store the creation timeout in rollback object
276             // and use that.
277             deployment = cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantName, vfModuleId, 5);
278             logger.debug("Rolled back deployment: {}", deployment.getId());
279         } catch (MsoException me) {
280             // Failed to rollback the VNF due to a cloudify exception.
281             // Convert to a generic VnfException
282             me.addContext("RollbackVNF");
283             String error = "Rollback VF Module: " + vfModuleId + " in " + cloudOwner + "/" + cloudSiteId + "/"
284                     + tenantId + ": " + me;
285             logger.error(BRACKETS, MessageEnum.RA_DELETE_VNF_ERR.toString(), vfModuleId, cloudOwner, cloudSiteId,
286                     tenantId, CLOUDIFY, "DeleteDeployment", ErrorCode.DataError.getValue(),
287                     "Exception - DeleteDeployment", me);
288             logger.debug(error);
289             throw new VnfException(me);
290         }
291         return;
292     }
293
294
295     private VnfStatus deploymentStatusToVnfStatus(DeploymentInfo deployment) {
296         // Determine the status based on last action & status
297         // DeploymentInfo object should be enhanced to report a better status internally.
298         DeploymentStatus status = deployment.getStatus();
299         String lastAction = deployment.getLastAction();
300
301         if (status == null || lastAction == null) {
302             return VnfStatus.UNKNOWN;
303         } else if (status == DeploymentStatus.NOTFOUND) {
304             return VnfStatus.NOTFOUND;
305         } else if (status == DeploymentStatus.INSTALLED) {
306             return VnfStatus.ACTIVE;
307         } else if (status == DeploymentStatus.CREATED) {
308             // Should have an INACTIVE status for this case. Shouldn't really happen, but
309             // Install was never run, or Uninstall was done but deployment didn't get deleted.
310             return VnfStatus.UNKNOWN;
311         } else if (status == DeploymentStatus.FAILED) {
312             return VnfStatus.FAILED;
313         }
314
315         return VnfStatus.UNKNOWN;
316     }
317
318     private Map<String, String> copyStringOutputs(Map<String, Object> stackOutputs) {
319         Map<String, String> stringOutputs = new HashMap<String, String>();
320         for (String key : stackOutputs.keySet()) {
321             if (stackOutputs.get(key) instanceof String) {
322                 stringOutputs.put(key, (String) stackOutputs.get(key));
323             } else if (stackOutputs.get(key) instanceof Integer) {
324                 try {
325                     String str = "" + stackOutputs.get(key);
326                     stringOutputs.put(key, str);
327                 } catch (Exception e) {
328                     logger.debug("Unable to add " + key + " to outputs");
329                 }
330             } else if (stackOutputs.get(key) instanceof JsonNode) {
331                 try {
332                     String str = this.convertNode((JsonNode) stackOutputs.get(key));
333                     stringOutputs.put(key, str);
334                 } catch (Exception e) {
335                     logger.debug("Unable to add " + key + " to outputs - exception converting JsonNode");
336                 }
337             } else if (stackOutputs.get(key) instanceof java.util.LinkedHashMap) {
338                 try {
339                     String str = JSON_MAPPER.writeValueAsString(stackOutputs.get(key));
340                     stringOutputs.put(key, str);
341                 } catch (Exception e) {
342                     logger.debug("Unable to add " + key + " to outputs - exception converting LinkedHashMap");
343                 }
344             } else {
345                 try {
346                     String str = stackOutputs.get(key).toString();
347                     stringOutputs.put(key, str);
348                 } catch (Exception e) {
349                     logger.debug("Unable to add " + key + " to outputs - unable to call .toString() " + e.getMessage());
350                 }
351             }
352         }
353         return stringOutputs;
354     }
355
356
357     private void sendMapToDebug(Map<String, Object> inputs, String optionalName) {
358         int i = 0;
359         StringBuilder sb = new StringBuilder(optionalName == null ? "\ninputs" : "\n" + optionalName);
360         if (inputs == null) {
361             sb.append("\tNULL");
362         } else if (inputs.size() < 1) {
363             sb.append("\tEMPTY");
364         } else {
365             for (String str : inputs.keySet()) {
366                 String outputString;
367                 try {
368                     outputString = inputs.get(str).toString();
369                 } catch (Exception e) {
370                     outputString = "Unable to call toString() on the value for " + str;
371                 }
372                 sb.append("\t\nitem " + i++ + ": '" + str + "'='" + outputString + "'");
373             }
374         }
375         logger.debug(sb.toString());
376         return;
377     }
378
379     private void sendMapToDebug(Map<String, Object> inputs) {
380         int i = 0;
381         StringBuilder sb = new StringBuilder("inputs:");
382         if (inputs == null) {
383             sb.append("\tNULL");
384         } else if (inputs.size() < 1) {
385             sb.append("\tEMPTY");
386         } else {
387             for (String str : inputs.keySet()) {
388                 sb.append("\titem " + i++ + ": " + str + "=" + inputs.get(str));
389             }
390         }
391         logger.debug(sb.toString());
392         return;
393     }
394
395     private String convertNode(final JsonNode node) {
396         try {
397             final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
398             final String json = JSON_MAPPER.writeValueAsString(obj);
399             return json;
400         } catch (JsonParseException jpe) {
401             logger.debug("Error converting json to string " + jpe.getMessage());
402         } catch (Exception e) {
403             logger.debug("Error converting json to string " + e.getMessage());
404         }
405         return "[Error converting json to string]";
406     }
407
408     private Map<String, String> convertMapStringObjectToStringString(Map<String, Object> objectMap) {
409         if (objectMap == null) {
410             return null;
411         }
412         Map<String, String> stringMap = new HashMap<String, String>();
413         for (String key : objectMap.keySet()) {
414             if (!stringMap.containsKey(key)) {
415                 Object obj = objectMap.get(key);
416                 if (obj instanceof String) {
417                     stringMap.put(key, (String) objectMap.get(key));
418                 } else if (obj instanceof JsonNode) {
419                     // This is a bit of mess - but I think it's the least impacting
420                     // let's convert it BACK to a string - then it will get converted back later
421                     try {
422                         String str = this.convertNode((JsonNode) obj);
423                         stringMap.put(key, str);
424                     } catch (Exception e) {
425                         logger.debug("DANGER WILL ROBINSON: unable to convert value for JsonNode " + key);
426                         // okay in this instance - only string values (fqdn) are expected to be needed
427                     }
428                 } else if (obj instanceof java.util.LinkedHashMap) {
429                     logger.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
430                     try {
431                         String str = JSON_MAPPER.writeValueAsString(obj);
432                         stringMap.put(key, str);
433                     } catch (Exception e) {
434                         logger.debug("DANGER WILL ROBINSON: unable to convert value for LinkedHashMap " + key);
435                     }
436                 } else if (obj instanceof Integer) {
437                     try {
438                         String str = "" + obj;
439                         stringMap.put(key, str);
440                     } catch (Exception e) {
441                         logger.debug("DANGER WILL ROBINSON: unable to convert value for Integer " + key);
442                     }
443                 } else {
444                     try {
445                         String str = obj.toString();
446                         stringMap.put(key, str);
447                     } catch (Exception e) {
448                         logger.debug(
449                                 "DANGER WILL ROBINSON: unable to convert value " + key + " (" + e.getMessage() + ")");
450                     }
451                 }
452             }
453         }
454
455         return stringMap;
456     }
457
458     /**
459      * This is the "Create VF Module" web service implementation. It will instantiate a new VF Module of the requested
460      * type in the specified cloud and tenant. The tenant must exist before this service is called.
461      *
462      * If a VF Module with the same name already exists, this can be considered a success or failure, depending on the
463      * value of the 'failIfExists' parameter.
464      *
465      * All VF Modules are defined in the MSO catalog. The caller must request one of the pre-defined module types or an
466      * error will be returned. Within the catalog, each VF Module references (among other things) a cloud template which
467      * is used to deploy the required artifacts (VMs, networks, etc.) to the cloud. In this adapter implementation, that
468      * artifact is expected to be a Cloudify blueprint.
469      *
470      * Depending on the blueprint, a variable set of input parameters will be defined, some of which are required. The
471      * caller is responsible to pass the necessary input data for the module or an error will be thrown.
472      *
473      * The method returns the vfModuleId, a Map of output attributes, and a VnfRollback object. This last object can be
474      * passed as-is to the rollbackVnf operation to undo everything that was created for the Module. This is useful if a
475      * VF module is successfully created but the orchestration fails on a subsequent step.
476      *
477      * @param cloudSiteId CLLI code of the cloud site in which to create the VNF
478      * @param cloudOwner cloud owner of the cloud site in which to create the VNF
479      * @param tenantId Openstack tenant identifier
480      * @param vfModuleType VF Module type key, should match a VNF definition in catalog DB. Deprecated - should use
481      *        modelCustomizationUuid
482      * @param vnfVersion VNF version key, should match a VNF definition in catalog DB Deprecated - VF Module versions
483      *        also captured by modelCustomizationUuid
484      * @param genericVnfId Generic VNF ID
485      * @param vfModuleName Name to be assigned to the new VF Module
486      * @param vfModuleId Id of the new VF Module
487      * @param requestType Indicates if this is a Volume Group or Module request
488      * @param volumeGroupId Identifier (i.e. deployment ID) for a Volume Group to attach to a VF Module
489      * @param baseVfModuleId Identifier (i.e. deployment ID) of the Base Module if this is an Add-on module
490      * @param modelCustomizationUuid Unique ID for the VF Module's model. Replaces the use of vfModuleType.
491      * @param inputs Map of key=value inputs for VNF stack creation
492      * @param failIfExists Flag whether already existing VNF should be considered
493      * @param backout Flag whether to suppress automatic backout (for testing)
494      * @param msoRequest Request tracking information for logs
495      * @param vnfId Holder for output VNF Cloudify Deployment ID
496      * @param outputs Holder for Map of VNF outputs from Deployment (assigned IPs, etc)
497      * @param rollback Holder for returning VnfRollback object
498      */
499     @Override
500     public void createVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vfModuleType,
501             String vnfVersion, String genericVnfId, String vfModuleName, String vfModuleId, String requestType,
502             String volumeGroupId, String baseVfModuleId, String modelCustomizationUuid, Map<String, Object> inputs,
503             Boolean failIfExists, Boolean backout, Boolean enableBridge, MsoRequest msoRequest, Holder<String> vnfId,
504             Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback) throws VnfException {
505         // Will capture execution time for metrics
506         long startTime = System.currentTimeMillis();
507
508         // Require a model customization ID. Every VF Module definition must have one.
509         if (modelCustomizationUuid == null || modelCustomizationUuid.isEmpty()) {
510             logger.debug("Missing required input: modelCustomizationUuid");
511             String error = "Create vfModule error: Missing required input: modelCustomizationUuid";
512             logger.error("{} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
513                     "VF Module ModelCustomizationUuid", CLOUDIFY, ErrorCode.DataError.getValue(),
514                     "Create VF Module: Missing required input: modelCustomizationUuid");
515             logger.debug(error);
516             throw new VnfException(error, MsoExceptionCategory.USERDATA);
517         }
518
519         // Clean up some inputs to make comparisons easier
520         if (requestType == null)
521             requestType = "";
522
523         if ("".equals(volumeGroupId) || "null".equals(volumeGroupId))
524             volumeGroupId = null;
525
526         if ("".equals(baseVfModuleId) || "null".equals(baseVfModuleId))
527             baseVfModuleId = null;
528
529         if (inputs == null) {
530             // Create an empty set of inputs
531             inputs = new HashMap<>();
532             logger.debug("inputs == null - setting to empty");
533         } else {
534             this.sendMapToDebug(inputs);
535         }
536
537         // Check if this is for a "Volume" module
538         boolean isVolumeRequest = false;
539         if (requestType.startsWith("VOLUME")) {
540             isVolumeRequest = true;
541         }
542
543         logger.debug("requestType = " + requestType + ", volumeGroupStackId = " + volumeGroupId + ", baseStackId = "
544                 + baseVfModuleId);
545
546         // Build a default rollback object (no actions performed)
547         VnfRollback vfRollback = new VnfRollback();
548         vfRollback.setCloudSiteId(cloudSiteId);
549         vfRollback.setCloudOwner(cloudOwner);
550         vfRollback.setTenantId(tenantId);
551         vfRollback.setMsoRequest(msoRequest);
552         vfRollback.setRequestType(requestType);
553         vfRollback.setIsBase(false); // Until we know better
554         vfRollback.setVolumeGroupHeatStackId(volumeGroupId);
555         vfRollback.setBaseGroupHeatStackId(baseVfModuleId);
556         vfRollback.setModelCustomizationUuid(modelCustomizationUuid);
557         vfRollback.setMode("CFY");
558
559         rollback.value = vfRollback; // Default rollback - no updates performed
560
561         // Get the VNF/VF Module definition from the Catalog DB first.
562         // There are three relevant records: VfModule, VfModuleCustomization, VnfResource
563
564         VfModule vf = null;
565         VnfResource vnfResource = null;
566         VfModuleCustomization vfmc = null;
567
568         try {
569             vfmc = vfModuleCustomRepo.findFirstByModelCustomizationUUIDOrderByCreatedDesc(modelCustomizationUuid);
570
571             if (vfmc == null) {
572                 String error = "Create vfModule error: Unable to find vfModuleCust with modelCustomizationUuid="
573                         + modelCustomizationUuid;
574                 logger.debug(error);
575                 logger.error("{} {} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
576                         "VF Module " + "ModelCustomizationUuid", modelCustomizationUuid, "CatalogDb",
577                         ErrorCode.DataError.getValue(), error);
578                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
579             } else {
580                 logger.debug("Found vfModuleCust entry " + vfmc.toString());
581             }
582
583             // Get the vfModule and vnfResource records
584             vf = vfmc.getVfModule();
585             vnfResource = vfmc.getVfModule().getVnfResources();
586         } catch (Exception e) {
587
588             logger.debug("unhandled exception in create VF - [Query]" + e.getMessage());
589             throw new VnfException("Exception during create VF " + e.getMessage());
590         }
591
592         // Perform a version check against cloudSite
593         // Obtain the cloud site information where we will create the VF Module
594         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite(cloudSiteId);
595         if (!cloudSiteOp.isPresent()) {
596             throw new VnfException(new MsoCloudSiteNotFound(cloudSiteId));
597         }
598         CloudSite cloudSite = cloudSiteOp.get();
599         MavenLikeVersioning aicV = new MavenLikeVersioning();
600         aicV.setVersion(cloudSite.getCloudVersion());
601
602         String vnfMin = vnfResource.getAicVersionMin();
603         String vnfMax = vnfResource.getAicVersionMax();
604
605         if ((vnfMin != null && !(aicV.isMoreRecentThan(vnfMin) || aicV.isTheSameVersion(vnfMin)))
606                 || (vnfMax != null && aicV.isMoreRecentThan(vnfMax))) {
607             // ERROR
608             String error = "VNF Resource type: " + vnfResource.getModelName() + ", ModelUuid="
609                     + vnfResource.getModelUUID() + " VersionMin=" + vnfMin + " VersionMax:" + vnfMax
610                     + " NOT supported on Cloud: " + cloudSiteId + " with AIC_Version:" + cloudSite.getCloudVersion();
611             logger.error("{} {} {} {} {}", MessageEnum.RA_CONFIG_EXC.toString(), error, OPENSTACK,
612                     ErrorCode.BusinessProcesssError.getValue(), "Exception - setVersion");
613             logger.debug(error);
614             throw new VnfException(error, MsoExceptionCategory.USERDATA);
615         }
616         // End Version check
617
618
619         DeploymentInfo cloudifyDeployment = null;
620
621         // First, look up to see if the VF already exists.
622
623         long subStartTime1 = System.currentTimeMillis();
624         try {
625             cloudifyDeployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vfModuleName);
626         } catch (MsoException me) {
627             // Failed to query the Deployment due to a cloudify exception.
628             String error = "Create VF Module: Query " + vfModuleName + " in " + cloudOwner + "/" + cloudSiteId + "/"
629                     + tenantId + ": " + me;
630             logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudSiteId,
631                     tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
632                     "Exception - queryDeployment", me);
633             logger.debug(error);
634
635             // Convert to a generic VnfException
636             me.addContext("CreateVFModule");
637             throw new VnfException(me);
638         }
639
640         // More precise handling/messaging if the Module already exists
641         if (cloudifyDeployment != null && !(cloudifyDeployment.getStatus() == DeploymentStatus.NOTFOUND)) {
642             // CREATED, INSTALLED, INSTALLING, FAILED, UNINSTALLING, UNKNOWN
643             DeploymentStatus status = cloudifyDeployment.getStatus();
644             logger.debug("Found Existing Deployment, status=" + status);
645
646             if (status == DeploymentStatus.INSTALLED) {
647                 // fail - it exists
648                 if (failIfExists != null && failIfExists) {
649                     String error = "Create VF: Deployment " + vfModuleName + " already exists in " + cloudOwner + "/"
650                             + cloudSiteId + "/" + tenantId;
651                     logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
652                             cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
653                             "Deployment " + vfModuleName + " already exists");
654                     logger.debug(error);
655                     throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId,
656                             cloudifyDeployment.getId());
657                 } else {
658                     // Found existing deployment and client has not requested "failIfExists".
659                     // Populate the outputs from the existing deployment.
660
661                     vnfId.value = cloudifyDeployment.getId();
662                     outputs.value = copyStringOutputs(cloudifyDeployment.getOutputs());
663                     return;
664                 }
665             }
666             // Check through various detailed error cases
667             if (status == DeploymentStatus.INSTALLING || status == DeploymentStatus.UNINSTALLING) {
668                 // fail - it's in progress - return meaningful error
669                 String error = "Create VF: Deployment " + vfModuleName + " already exists and has status "
670                         + status.toString() + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
671                         + "; please wait for it to complete, or fix manually.";
672                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
673                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
674                         "Deployment " + vfModuleName + " already exists");
675                 logger.debug(error);
676                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
677             } else if (status == DeploymentStatus.FAILED) {
678                 // fail - it exists and is in a FAILED state
679                 String error = "Create VF: Deployment " + vfModuleName + " already exists and is in FAILED state in "
680                         + cloudOwner + "/" + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
681                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
682                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
683                         "Deployment " + vfModuleName + " already " + "exists and is in FAILED state");
684                 logger.debug(error);
685                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
686             } else if (status == DeploymentStatus.UNKNOWN || status == DeploymentStatus.CREATED) {
687                 // fail - it exists and is in a UNKNOWN state
688                 String error = "Create VF: Deployment " + vfModuleName + " already exists and has status "
689                         + status.toString() + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
690                         + "; requires manual intervention.";
691                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
692                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
693                         "Deployment " + vfModuleName + " already " + "exists and is in " + status.toString()
694                                 + " state");
695                 logger.debug(error);
696                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
697             } else {
698                 // Unexpected, since all known status values have been tested for
699                 String error = "Create VF: Deployment " + vfModuleName + " already exists with unexpected status "
700                         + status.toString() + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
701                         + "; requires manual intervention.";
702                 logger.error(BRACKETS, MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName, cloudOwner,
703                         cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", ErrorCode.DataError.getValue(),
704                         "Deployment " + vfModuleName + " already " + "exists and is in an unknown state");
705                 logger.debug(error);
706                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, cloudOwner, tenantId, cloudifyDeployment.getId());
707             }
708         }
709
710
711         // Collect outputs from Base Modules and Volume Modules
712         Map<String, Object> baseModuleOutputs = null;
713         Map<String, Object> volumeGroupOutputs = null;
714
715         // If a Volume Group was provided, query its outputs for inclusion in Module input parameters
716         if (volumeGroupId != null) {
717             long subStartTime2 = System.currentTimeMillis();
718             DeploymentInfo volumeDeployment = null;
719             try {
720                 volumeDeployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, volumeGroupId);
721             } catch (MsoException me) {
722                 // Failed to query the Volume GroupDeployment due to a cloudify exception.
723                 String error = "Create VF Module: Query Volume Group " + volumeGroupId + " in " + cloudOwner + "/"
724                         + cloudSiteId + "/" + tenantId + ": " + me;
725                 logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId, cloudOwner, cloudSiteId,
726                         tenantId, CLOUDIFY, "queryDeployment(volume)", ErrorCode.DataError.getValue(),
727                         "Exception - queryDeployment(volume)", me);
728                 logger.debug(error);
729                 // Convert to a generic VnfException
730                 me.addContext("CreateVFModule(QueryVolume)");
731                 throw new VnfException(me);
732             }
733
734             if (volumeDeployment == null || volumeDeployment.getStatus() == DeploymentStatus.NOTFOUND) {
735                 String error = "Create VFModule: Attached Volume Group DOES NOT EXIST " + volumeGroupId + " in "
736                         + cloudSiteId + "/" + tenantId + " USER ERROR";
737                 logger.error(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId, cloudSiteId, tenantId,
738                         error, CLOUDIFY, "queryDeployment(volume)", 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(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId, cloudOwner,
775                             cloudSiteId, tenantId, CLOUDIFY, "queryDeployment(Base)", ErrorCode.DataError.getValue(),
776                             "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(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId, cloudSiteId,
787                             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(BRACKETS, MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner, cloudSiteId, tenantId,
1112                     CLOUDIFY, "QueryDeployment", ErrorCode.DataError.getValue(), "Exception - QueryDeployment", me);
1113             logger.debug(error);
1114             throw new VnfException(me);
1115         }
1116         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected
1117         // Object types
1118         outputs.value = convertMapStringObjectToStringString(deployment.getOutputs());
1119
1120         // Use the MsoHeatUtils to delete the stack. Set the polling flag to true.
1121         // The possible outcomes of deleteStack are a StackInfo object with status
1122         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
1123         // could be thrown.
1124         long subStartTime = System.currentTimeMillis();
1125         try {
1126             cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantId, vnfName, 5);
1127         } catch (MsoException me) {
1128             me.addContext("DeleteVfModule");
1129             // Convert to a generic VnfException
1130             String error =
1131                     "Delete VF: " + vnfName + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId + ": " + me;
1132             logger.error(BRACKETS, MessageEnum.RA_DELETE_VNF_ERR.toString(), vnfName, cloudOwner, cloudSiteId, tenantId,
1133                     "DeleteDeployment", "DeleteDeployment", ErrorCode.DataError.getValue(),
1134                     "Exception - DeleteDeployment: " + me.getMessage());
1135             logger.debug(error);
1136             throw new VnfException(me);
1137         }
1138
1139         // On success, nothing is returned.
1140         return;
1141     }
1142
1143     // TODO: Should Update be supported for Cloudify? What would this look like?
1144     @Override
1145     public void updateVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfType,
1146             String vnfVersion, String vnfName, String requestType, String volumeGroupHeatStackId,
1147             String baseVfHeatStackId, String vfModuleStackId, String modelCustomizationUuid, Map<String, Object> inputs,
1148             MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
1149             throws VnfException {
1150         // This operation is not currently supported for Cloudify-orchestrated VF Modules.
1151         logger.debug("Update VF Module command attempted but not supported");
1152         throw new VnfException("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1153     }
1154
1155 }