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