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