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