459ff2aebaf6d88adf1b9b0b01df19dea45f0668
[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  * ================================================================================
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 import org.onap.so.logger.MsoAlarmLogger;
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     private static MsoAlarmLogger alarmLogger = new MsoAlarmLogger ();
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, String> 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, String> 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, String> 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 vfModuleName Name to be assigned to the new VF Module
541      * @param requestType Indicates if this is a Volume Group or Module request
542      * @param volumeGroupId Identifier (i.e. deployment ID) for a Volume Group
543      *        to attach to a VF Module
544      * @param baseVfModuleId Identifier (i.e. deployment ID) of the Base Module if
545      *        this is an Add-on module
546      * @param modelCustomizationUuid Unique ID for the VF Module's model.  Replaces
547      *        the use of vfModuleType.
548      * @param inputs Map of key=value inputs for VNF stack creation
549      * @param failIfExists Flag whether already existing VNF should be considered
550      * @param backout Flag whether to suppress automatic backout (for testing)
551      * @param msoRequest Request tracking information for logs
552      * @param vnfId Holder for output VNF Cloudify Deployment ID
553      * @param outputs Holder for Map of VNF outputs from Deployment (assigned IPs, etc)
554      * @param rollback Holder for returning VnfRollback object
555      */
556     @Override
557     public void createVfModule(String cloudSiteId,
558             String tenantId,
559             String vfModuleType,
560             String vnfVersion,
561             String vfModuleName,
562             String requestType,
563             String volumeGroupId,
564             String baseVfModuleId,
565             String modelCustomizationUuid,
566             Map <String, String> inputs,
567             Boolean failIfExists,
568             Boolean backout,
569             Boolean enableBridge,
570             MsoRequest msoRequest,
571             Holder <String> vnfId,
572             Holder <Map <String, String>> outputs,
573             Holder <VnfRollback> rollback)
574         throws VnfException
575     {
576         // Will capture execution time for metrics
577         long startTime = System.currentTimeMillis ();
578         
579         MsoLogger.setLogContext (msoRequest);
580         MsoLogger.setServiceName ("CreateVfModule");
581
582         // Require a model customization ID.  Every VF Module definition must have one.
583         if (modelCustomizationUuid == null  ||  modelCustomizationUuid.isEmpty()) {
584                         LOGGER.debug("Missing required input: modelCustomizationUuid");
585                         String error = "Create vfModule error: Missing required input: modelCustomizationUuid";
586             LOGGER.error(MessageEnum.RA_VNF_UNKNOWN_PARAM,
587                     "VF Module ModelCustomizationUuid", "null", CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Create VF Module: Missing required input: modelCustomizationUuid");
588             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
589             throw new VnfException(error, MsoExceptionCategory.USERDATA);
590         }
591         
592         // Clean up some inputs to make comparisons easier
593         if (requestType == null)
594                 requestType = "";
595         
596         if ("".equals(volumeGroupId) || "null".equals(volumeGroupId))
597                 volumeGroupId = null;  
598         
599         if ("".equals(baseVfModuleId) || "null".equals(baseVfModuleId))
600                 baseVfModuleId = null;
601
602         if (inputs == null) {
603                 // Create an empty set of inputs
604                 inputs = new HashMap<String,String>();
605                 LOGGER.debug("inputs == null - setting to empty");
606         } else {
607                 this.sendMapToDebug(inputs);
608         }
609         
610         // Check if this is for a "Volume" module
611         boolean isVolumeRequest = false;
612         if (requestType.startsWith("VOLUME")) {
613                 isVolumeRequest = true;
614         }
615
616         LOGGER.debug("requestType = " + requestType + ", volumeGroupStackId = " + volumeGroupId + ", baseStackId = " + baseVfModuleId);
617
618         // Build a default rollback object (no actions performed)
619         VnfRollback vfRollback = new VnfRollback();
620         vfRollback.setCloudSiteId(cloudSiteId);
621         vfRollback.setTenantId(tenantId);
622         vfRollback.setMsoRequest(msoRequest);
623         vfRollback.setRequestType(requestType);
624         vfRollback.setIsBase(false);    // Until we know better
625         vfRollback.setVolumeGroupHeatStackId(volumeGroupId);
626         vfRollback.setBaseGroupHeatStackId(baseVfModuleId);
627         vfRollback.setModelCustomizationUuid(modelCustomizationUuid);
628         vfRollback.setMode("CFY");
629         
630                 rollback.value = vfRollback; // Default rollback - no updates performed
631
632         // Get the VNF/VF Module definition from the Catalog DB first.
633         // There are three relevant records:  VfModule, VfModuleCustomization, VnfResource
634
635         VfModule vf = null;
636         VnfResource vnfResource = null;
637         VfModuleCustomization vfmc = null;
638
639         try {
640                 vfmc = vfModuleCustomRepo.findByModelCustomizationUUID(modelCustomizationUuid);
641                 
642             if (vfmc == null) {
643                         String error = "Create vfModule error: Unable to find vfModuleCust with modelCustomizationUuid=" + modelCustomizationUuid;
644                         LOGGER.debug(error);
645                 LOGGER.error(MessageEnum.RA_VNF_UNKNOWN_PARAM,
646                             "VF Module ModelCustomizationUuid", modelCustomizationUuid, "CatalogDb", "", MsoLogger.ErrorCode.DataError, error);
647                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
648                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
649             } else {
650                         LOGGER.debug("Found vfModuleCust entry " + vfmc.toString());
651             }
652
653             // Get the vfModule and vnfResource records
654                 vf = vfmc.getVfModule();
655                 vnfResource = vfmc.getVfModule().getVnfResources();
656         }
657         catch (Exception e) {
658            
659                 LOGGER.debug("unhandled exception in create VF - [Query]" + e.getMessage());
660                 throw new VnfException("Exception during create VF " + e.getMessage());
661         }
662
663         //  Perform a version check against cloudSite
664         // Obtain the cloud site information where we will create the VF Module
665         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite (cloudSiteId);
666         if (!cloudSiteOp.isPresent()) {
667             throw new VnfException (new MsoCloudSiteNotFound (cloudSiteId));
668         }
669         CloudSite cloudSite = cloudSiteOp.get();
670                 MavenLikeVersioning aicV = new MavenLikeVersioning();
671                 aicV.setVersion(cloudSite.getCloudVersion());
672     
673                 String vnfMin = vnfResource.getAicVersionMin();
674                 String vnfMax = vnfResource.getAicVersionMax();
675                 
676                 if ( (vnfMin != null && !(aicV.isMoreRecentThan(vnfMin) || aicV.isTheSameVersion(vnfMin))) ||
677                      (vnfMax != null && aicV.isMoreRecentThan(vnfMax)))
678                 {
679                         // ERROR
680                         String error = "VNF Resource type: " + vnfResource.getModelName() + ", ModelUuid=" + vnfResource.getModelUUID() + " VersionMin=" + vnfMin + " VersionMax:" + vnfMax + " NOT supported on Cloud: " + cloudSiteId + " with AIC_Version:" + cloudSite.getCloudVersion();
681                         LOGGER.error(MessageEnum.RA_CONFIG_EXC, error, "OpenStack", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception - setVersion");
682                         LOGGER.debug(error);
683                         throw new VnfException(error, MsoExceptionCategory.USERDATA);
684                 }
685                 // End Version check
686         
687         
688         DeploymentInfo cloudifyDeployment = null;
689         
690         // First, look up to see if the VF already exists.
691
692         long subStartTime1 = System.currentTimeMillis ();
693         try {
694             cloudifyDeployment = cloudifyUtils.queryDeployment (cloudSiteId, tenantId, vfModuleName);
695             LOGGER.recordMetricEvent (subStartTime1, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, CLOUDIFY_RESPONSE_SUCCESS, CLOUDIFY, "QueryDeployment", vfModuleName);
696         }
697         catch (MsoException me) {
698             // Failed to query the Deployment due to a cloudify exception.
699             String error = "Create VF Module: Query " + vfModuleName + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
700             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, vfModuleName, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", MsoLogger.ErrorCode.DataError, "Exception - queryDeployment", me);
701             LOGGER.recordMetricEvent (subStartTime1, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "QueryDeployment", vfModuleName);
702             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
703
704             // Convert to a generic VnfException
705             me.addContext ("CreateVFModule");
706             throw new VnfException (me);
707         }
708         
709         // More precise handling/messaging if the Module already exists
710         if (cloudifyDeployment != null && !(cloudifyDeployment.getStatus () == DeploymentStatus.NOTFOUND)) {
711                 // CREATED, INSTALLED, INSTALLING, FAILED, UNINSTALLING, UNKNOWN
712                 DeploymentStatus status = cloudifyDeployment.getStatus();
713                         LOGGER.debug ("Found Existing Deployment, status=" + status);
714                         
715                 if (status == DeploymentStatus.INSTALLED) {
716                         // fail - it exists
717                         if (failIfExists != null && failIfExists) {
718                                 String error = "Create VF: Deployment " + vfModuleName + " already exists in " + cloudSiteId + "/" + tenantId;
719                                 LOGGER.error (MessageEnum.RA_VNF_ALREADY_EXIST, vfModuleName, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", MsoLogger.ErrorCode.DataError, "Deployment " + vfModuleName + " already exists");
720                     LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
721                                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, cloudifyDeployment.getId());
722                         } else {
723                                 // Found existing deployment and client has not requested "failIfExists".
724                                 // Populate the outputs from the existing deployment.
725
726                                 vnfId.value = cloudifyDeployment.getId();
727                                 outputs.value = copyStringOutputs (cloudifyDeployment.getOutputs ());
728                     LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully create VF Module (found existing)");
729                     return;
730                         }
731                 }
732                 // Check through various detailed error cases
733                 if (status == DeploymentStatus.INSTALLING || status == DeploymentStatus.UNINSTALLING) {
734                         // fail - it's in progress - return meaningful error
735                 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.";
736                 LOGGER.error (MessageEnum.RA_VNF_ALREADY_EXIST, vfModuleName, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", MsoLogger.ErrorCode.DataError, "Deployment " + vfModuleName + " already exists");
737                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
738                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, cloudifyDeployment.getId());
739                 }
740                 else if (status == DeploymentStatus.FAILED) {
741                         // fail - it exists and is in a FAILED state
742                 String error = "Create VF: Deployment " + vfModuleName + " already exists and is in FAILED state in " + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
743                 LOGGER.error (MessageEnum.RA_VNF_ALREADY_EXIST, vfModuleName, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment", MsoLogger.ErrorCode.DataError, "Deployment " + vfModuleName + " already exists and is in FAILED state");
744                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
745                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, cloudifyDeployment.getId());
746                 }
747                 else if (status == DeploymentStatus.UNKNOWN || status == DeploymentStatus.CREATED) {
748                         // fail - it exists and is in a UNKNOWN state
749                 String error = "Create VF: Deployment " + vfModuleName + " already exists and has status " + status.toString() + " in " + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
750                 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");
751                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
752                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, cloudifyDeployment.getId());
753                 }
754                 else {
755                         // Unexpected, since all known status values have been tested for
756                 String error = "Create VF: Deployment " + vfModuleName + " already exists with unexpected status " + status.toString() + " in " + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
757                 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");
758                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
759                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, cloudifyDeployment.getId());
760                 }
761         }
762    
763         
764         // Collect outputs from Base Modules and Volume Modules
765         Map<String, Object> baseModuleOutputs = null;
766         Map<String, Object> volumeGroupOutputs = null;
767
768         // If a Volume Group was provided, query its outputs for inclusion in Module input parameters
769         if (volumeGroupId != null) {
770             long subStartTime2 = System.currentTimeMillis ();
771             DeploymentInfo volumeDeployment = null;
772             try {
773                 volumeDeployment = cloudifyUtils.queryDeployment (cloudSiteId, tenantId, volumeGroupId);
774                 LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Success response from Cloudify", CLOUDIFY, "QueryDeployment", volumeGroupId);
775             }
776             catch (MsoException me) {
777                 // Failed to query the Volume GroupDeployment due to a cloudify exception.
778                 String error = "Create VF Module: Query Volume Group " + volumeGroupId + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
779                 LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, volumeGroupId, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment(volume)", MsoLogger.ErrorCode.DataError, "Exception - queryDeployment(volume)", me);
780                 LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "QueryDeployment(volume)", volumeGroupId);
781                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
782
783                 // Convert to a generic VnfException
784                 me.addContext ("CreateVFModule(QueryVolume)");
785                 throw new VnfException (me);
786             }
787             
788                 if (volumeDeployment == null || volumeDeployment.getStatus() == DeploymentStatus.NOTFOUND) {
789                     String error = "Create VFModule: Attached Volume Group DOES NOT EXIST " + volumeGroupId + " in " + cloudSiteId + "/" + tenantId + " USER ERROR"  ;
790                     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");
791                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
792                     LOGGER.debug(error);
793                     throw new VnfException (error, MsoExceptionCategory.USERDATA);
794                 } else {
795                         LOGGER.debug("Found nested volume group");
796                         volumeGroupOutputs = volumeDeployment.getOutputs();
797                         this.sendMapToDebug(volumeGroupOutputs, "volumeGroupOutputs");
798                 }
799         }
800         
801         // If this is an Add-On Module, query the Base Module outputs
802         // Note: This will be performed whether or not the current request is for an
803         //       Add-On Volume Group or Add-On VF Module
804
805         if (vf.getIsBase()) {
806             LOGGER.debug("This is a BASE Module request");
807             vfRollback.setIsBase(true);
808         } else {
809             LOGGER.debug("This is an Add-On Module request");
810             
811             // Add-On Modules should always have a Base, but just treat as a warning if not provided.
812             // Add-on Volume requests may or may not specify a base.
813             if (!isVolumeRequest && baseVfModuleId == null) {
814                 LOGGER.debug ("WARNING:  Add-on Module request - no Base Module ID provided");
815             }
816
817             if (baseVfModuleId != null) {
818                     long subStartTime2 = System.currentTimeMillis ();
819                     DeploymentInfo baseDeployment = null;
820                     try {
821                         baseDeployment = cloudifyUtils.queryDeployment (cloudSiteId, tenantId, baseVfModuleId);
822                         LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Success response from Cloudify", CLOUDIFY, "QueryDeployment(Base)", baseVfModuleId);
823                     }
824                     catch (MsoException me) {
825                         // Failed to query the Volume GroupDeployment due to a cloudify exception.
826                         String error = "Create VF Module: Query Base " + baseVfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
827                         LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, baseVfModuleId, cloudSiteId, tenantId, CLOUDIFY, "queryDeployment(Base)", MsoLogger.ErrorCode.DataError, "Exception - queryDeployment(Base)", me);
828                         LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "QueryDeployment(Base)", baseVfModuleId);
829                         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
830         
831                         // Convert to a generic VnfException
832                         me.addContext ("CreateVFModule(QueryBase)");
833                         throw new VnfException (me);
834                     }
835                     
836                         if (baseDeployment == null || baseDeployment.getStatus() == DeploymentStatus.NOTFOUND) {
837                             String error = "Create VFModule: Base Module DOES NOT EXIST " + baseVfModuleId + " in " + cloudSiteId + "/" + tenantId + " USER ERROR"  ;
838                             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, baseVfModuleId, cloudSiteId, tenantId, error, CLOUDIFY, "queryDeployment(Base)", MsoLogger.ErrorCode.BusinessProcesssError, "Create VFModule: Base Module DOES NOT EXIST");
839                         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
840                             LOGGER.debug(error);
841                             throw new VnfException (error, MsoExceptionCategory.USERDATA);
842                         } else {
843                                 LOGGER.debug("Found base module");
844                                 baseModuleOutputs = baseDeployment.getOutputs();
845                                 this.sendMapToDebug(baseModuleOutputs, "baseModuleOutputs");
846                         }
847             }
848         }
849         
850
851         // Ready to deploy the new VNF
852         
853         // NOTE:  For this section, heatTemplate is used for both HEAT templates and Cloudify blueprints.
854         // In final implementation (post-POC), the template object would either be generic or there would
855         // be a separate DB Table/Object for Blueprints.
856
857       
858                 // NOTE: The template is fixed for the VF Module.  The environment is part of the customization.
859         HeatTemplate heatTemplate = null;
860         HeatEnvironment heatEnvironment = null;
861         if (isVolumeRequest) {
862                         heatTemplate = vf.getVolumeHeatTemplate();
863                         heatEnvironment = vfmc.getVolumeHeatEnv();
864                 } else {
865                         heatTemplate = vf.getModuleHeatTemplate();
866                         heatEnvironment = vfmc.getHeatEnvironment();
867                 }
868         
869                 if (heatTemplate == null) {
870                         String error = "UpdateVF: No Heat Template ID defined in catalog database for " + vfModuleType + ", reqType=" + requestType;
871                         LOGGER.error(MessageEnum.RA_VNF_UNKNOWN_PARAM, "Heat Template ID", vfModuleType, "OpenStack", "", MsoLogger.ErrorCode.DataError, error);
872             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
873                         alarmLogger.sendAlarm(MSO_CONFIGURATION_ERROR,
874                                         MsoAlarmLogger.CRITICAL, error);
875                         throw new VnfException(error, MsoExceptionCategory.INTERNAL);
876                 } else {
877                         LOGGER.debug ("Got HEAT Template from DB: " + heatTemplate.getHeatTemplate());
878                 }
879                 
880         if (heatEnvironment == null) {
881            String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType;
882                 LOGGER.error (MessageEnum.RA_VNF_UNKNOWN_PARAM, "Heat Environment ID", "OpenStack", "", MsoLogger.ErrorCode.DataError, error);
883                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
884                 // Alarm on this error, configuration must be fixed
885                 alarmLogger.sendAlarm (MSO_CONFIGURATION_ERROR, MsoAlarmLogger.CRITICAL, error);
886
887                 throw new VnfException (error, MsoExceptionCategory.INTERNAL);
888         } else {
889             LOGGER.debug ("Got Heat Environment from DB: " + heatEnvironment.getEnvironment());
890         }
891
892             
893         try {
894             // All variables converted to their native object types
895             HashMap<String, Object> goldenInputs = new HashMap<String,Object>();
896             List<String> extraInputs = new ArrayList<String>();
897
898             // NOTE: SKIP THIS FOR CLOUDIFY for now.  Just use what was passed in.
899             //  This whole section needs to be rewritten.
900                         Boolean skipInputChecks = false;
901             
902                         if (skipInputChecks) {
903                                 goldenInputs = new HashMap<String,Object>();
904                                 for (String key : inputs.keySet()) {
905                                         goldenInputs.put(key, inputs.get(key));
906                                 }
907                         }
908                         else {
909                                 // Build maps for the parameters (including aliases) to simplify checks
910                                 HashMap<String, HeatTemplateParam> params = new HashMap<String, HeatTemplateParam>();
911                                 
912                                 Set<HeatTemplateParam> paramSet = heatTemplate.getParameters();
913                                 LOGGER.debug("paramSet has " + paramSet.size() + " entries");
914                                 
915                                 for (HeatTemplateParam htp : paramSet) {
916                                         params.put(htp.getParamName(), htp);
917
918                                         // Include aliases.
919                                         String alias = htp.getParamAlias();
920                                         if (alias != null && !alias.equals("") && !params.containsKey(alias)) {
921                                                 params.put(alias, htp);
922                                         }
923                                 }
924                                 
925                                 // First, convert all inputs to their "template" type
926                                 for (String key : inputs.keySet()) {
927                                         if (params.containsKey(key)) {
928                                                 Object value = cloudifyUtils.convertInputValue(inputs.get(key), params.get(key));
929                                                 if (value != null) {
930                                                         goldenInputs.put(key, value);
931                                                 }
932                                                 else {
933                                                         LOGGER.debug("Failed to convert input " + key + "='" + inputs.get(key) + "' to " + params.get(key).getParamType());
934                                                 }
935                                         } else {
936                                                 extraInputs.add(key);
937                                         }
938                                 }
939                                 
940                                 if (!extraInputs.isEmpty()) {
941                                         LOGGER.debug("Ignoring extra inputs: " + extraInputs);
942                                 }
943                                 
944                                 // Next add in Volume Group Outputs if there are any.  Copy directly without conversions.
945                                 if (volumeGroupOutputs != null  &&  !volumeGroupOutputs.isEmpty()) {
946                                         for (String key : volumeGroupOutputs.keySet()) {
947                                                 if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
948                                                         goldenInputs.put(key, volumeGroupOutputs.get(key));
949                                                 }
950                                         }
951                                 }
952                                 
953                                 // Next add in Base Module Outputs if there are any.  Copy directly without conversions.
954                                 if (baseModuleOutputs != null  &&  !baseModuleOutputs.isEmpty()) {
955                                         for (String key : baseModuleOutputs.keySet()) {
956                                                 if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
957                                                         goldenInputs.put(key, baseModuleOutputs.get(key));
958                                                 }
959                                         }
960                                 }
961                                 
962                                 // Last, add in values from the "environment" file.
963                                 // These are added to the inputs, since Cloudify doesn't pass an environment file like Heat.
964                         
965                                 // TODO: This may take a different form for Cloudify, but for now process it
966                                 //       with Heat environment file syntax
967                 StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());
968                                 MsoHeatEnvironmentEntry mhee = new MsoHeatEnvironmentEntry (sb);
969                                 
970                                 if (mhee.getParameters() != null) {
971                                         for (MsoHeatEnvironmentParameter envParam : mhee.getParameters()) {
972                                                 // If this is a template input, copy to golden inputs
973                                                 String envKey = envParam.getName();
974                                                 if (params.containsKey(envKey)  &&  !goldenInputs.containsKey(envKey)) {
975                                                         Object value = cloudifyUtils.convertInputValue(envParam.getValue(), params.get(envKey));
976                                                         if (value != null) {
977                                                                 goldenInputs.put(envKey, value);
978                                                         }
979                                                         else {
980                                                                 LOGGER.debug("Failed to convert environment parameter " + envKey + "='" + envParam.getValue() + "' to " + params.get(envKey).getParamType());
981                                                         }
982                                                 }
983                                         }
984                                 }
985
986                     this.sendMapToDebug(goldenInputs, "Final inputs sent to Cloudify");
987
988                                 
989                     // Check that required parameters have been supplied from any of the sources
990                     String missingParams = null;
991                     boolean checkRequiredParameters = true;
992                     try {
993                         String propertyString = this.environment.getProperty(MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
994                         if ("false".equalsIgnoreCase (propertyString) || "n".equalsIgnoreCase (propertyString)) {
995                             checkRequiredParameters = false;
996                             LOGGER.debug ("CheckRequiredParameters is FALSE. Will still check but then skip blocking..."
997                                           + MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
998                         }
999                     } catch (Exception e) {
1000                         // No problem - default is true
1001                         LOGGER.debug ("An exception occured trying to get property " + MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS, e);
1002                     }
1003                     
1004                     
1005                     for (HeatTemplateParam parm : heatTemplate.getParameters ()) {
1006                         if (parm.isRequired () && (!goldenInputs.containsKey (parm.getParamName ()))) {
1007                         LOGGER.debug ("adding to missing parameters list: " + parm.getParamName ());
1008                         if (missingParams == null) {
1009                             missingParams = parm.getParamName ();
1010                         } else {
1011                             missingParams += "," + parm.getParamName ();
1012                         }
1013                         }
1014                     }
1015                         
1016                     if (missingParams != null) {
1017                         if (checkRequiredParameters) {
1018                                 // Problem - missing one or more required parameters
1019                                 String error = "Create VFModule: Missing Required inputs: " + missingParams;
1020                                 LOGGER.error (MessageEnum.RA_MISSING_PARAM, missingParams, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Create VFModule: Missing Required inputs");
1021                             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.BadRequest, error);
1022                                 throw new VnfException (error, MsoExceptionCategory.USERDATA);
1023                         } else {
1024                                 LOGGER.debug ("found missing parameters [" + missingParams + "] - but checkRequiredParameters is false - will not block");
1025                         }
1026                     } else {
1027                         LOGGER.debug ("No missing parameters found - ok to proceed");
1028                     }
1029         
1030                         } // NOTE: END PARAMETER CHECKING
1031
1032                         // Ready to deploy the VF Module.
1033                         // *First step - make sure the blueprint is loaded into Cloudify.
1034                         String blueprintName = heatTemplate.getTemplateName();
1035                         String blueprint = heatTemplate.getTemplateBody();
1036                         String blueprintId = blueprintName;
1037                         
1038                         // Use the main blueprint name as the blueprint ID (strip yaml extensions).
1039             if (blueprintId.endsWith(".yaml"))
1040                 blueprintId = blueprintId.substring(0,blueprintId.lastIndexOf(".yaml"));
1041
1042                         try {
1043                                 if (! cloudifyUtils.isBlueprintLoaded (cloudSiteId, blueprintId)) {
1044                                         LOGGER.debug ("Blueprint " + blueprintId + " is not loaded.  Will upload it now.");
1045
1046                                         Map<String,byte[]> blueprintFiles = new HashMap<String,byte[]>();
1047
1048                                         blueprintFiles.put(blueprintName, blueprint.getBytes());
1049                                         
1050                             // TODO:  Implement nested blueprint logic based on Cloudify structures.
1051                                         //        For now, just use the Heat structures.
1052                                         //        The query returns a map of String->Object, where the map keys provide one layer of
1053                                         //        indirection from the Heat template names.  For this case, assume the map key matches
1054                                         //        the nested blueprint name.
1055                             List<HeatTemplate> nestedBlueprints = heatTemplate.getChildTemplates();
1056                             if (nestedBlueprints != null) {
1057                                     for (HeatTemplate nestedBlueprint: nestedBlueprints) {
1058                                         blueprintFiles.put(nestedBlueprint.getTemplateName(), nestedBlueprint.getTemplateBody().getBytes());
1059                                     }
1060                             }
1061
1062                             // TODO:  Implement file artifact logic based on Cloudify structures.  
1063                             //        For now, just use the Heat structures.
1064                             List<HeatFiles> heatFiles = vf.getHeatFiles();
1065                             if (heatFiles != null) {
1066                                     for (HeatFiles heatFile: heatFiles) {
1067                                         blueprintFiles.put(heatFile.getFileName(), heatFile.getFileBody().getBytes());
1068                                     }
1069                             }
1070                             
1071                             // Upload the blueprint package
1072                                         cloudifyUtils.uploadBlueprint(cloudSiteId, blueprintId, blueprintName, blueprintFiles, false);
1073
1074                                 }
1075                         }
1076                 
1077                         catch (MsoException me) {
1078                 me.addContext ("CreateVFModule");
1079                 String error = "Create VF Module: Upload blueprint failed.  Blueprint=" + blueprintName + ": " + me;
1080                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "MsoException - uploadBlueprint", me);
1081                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1082                 throw new VnfException (me);
1083                                 
1084                         }
1085                         
1086             // Ignore MsoTenantNotFound and MsoStackAlreadyExists exceptions
1087             // because we already checked for those.
1088             long createDeploymentStarttime = System.currentTimeMillis ();
1089             try {
1090                 // KLUDGE - Cloudify requires Tenant Name for Openstack.  We have the ID.
1091                 //          Go directly to Keystone until APIs could be updated to supply the name.
1092                 MsoTenant msoTenant = keystoneUtils.queryTenant(tenantId, cloudSiteId);
1093                 String tenantName = (msoTenant != null? msoTenant.getTenantName() : tenantId);
1094                 
1095                 if (backout == null) {
1096                         backout = true;
1097                 }
1098                 
1099                 cloudifyDeployment = cloudifyUtils.createAndInstallDeployment (cloudSiteId,
1100                                               tenantName,
1101                                               vfModuleName,
1102                                               blueprintId,
1103                                               goldenInputs,
1104                                               true,
1105                                               heatTemplate.getTimeoutMinutes (),
1106                                               backout.booleanValue());
1107                 
1108                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, CLOUDIFY_RESPONSE_SUCCESS, CLOUDIFY, "CreateDeployment", vfModuleName);
1109             } catch (MsoException me) {
1110                 me.addContext ("CreateVFModule");
1111                 String error = "Create VF Module " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1112                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "CreateDeployment", vfModuleName);
1113                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "MsoException - createDeployment", me);
1114                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1115                 throw new VnfException (me);
1116             } catch (NullPointerException npe) {
1117                 String error = "Create VFModule " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + npe;
1118                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "CreateDeployment", vfModuleName);
1119                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "NullPointerException - createDeployment", npe);
1120                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1121                 LOGGER.debug("NULL POINTER EXCEPTION at cloudify.createAndInstallDeployment");
1122                 //npe.addContext ("CreateVNF");
1123                 throw new VnfException ("NullPointerException during cloudify.createAndInstallDeployment");
1124             } catch (Exception e) {
1125                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, "Exception while creating deployment with Cloudify", CLOUDIFY, "CreateDeployment", vfModuleName);
1126                 LOGGER.debug("unhandled exception at cloudify.createAndInstallDeployment");
1127                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, "Exception while creating deployment with Cloudify");
1128                 throw new VnfException("Exception during cloudify.createAndInstallDeployment! " + e.getMessage());
1129             }           
1130         } catch (Exception e) {
1131                 LOGGER.debug("unhandled exception in create VF");
1132                 throw new VnfException("Exception during create VF " + e.getMessage());
1133
1134         }
1135
1136         // Reach this point if create is successful.
1137         // Populate remaining rollback info and response parameters.
1138         vfRollback.setVnfCreated (true);
1139         vfRollback.setVnfId (cloudifyDeployment.getId());
1140         vnfId.value = cloudifyDeployment.getId();
1141         outputs.value = copyStringOutputs (cloudifyDeployment.getOutputs ());           
1142
1143         rollback.value = vfRollback;
1144
1145         LOGGER.debug ("VF Module " + vfModuleName + " successfully created");
1146         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully create VF Module");
1147         return;
1148     }
1149
1150     public void deleteVfModule (String cloudSiteId,
1151                            String tenantId,
1152                            String vnfName,
1153                            MsoRequest msoRequest,
1154                            Holder <Map <String, String>> outputs) throws VnfException {
1155         MsoLogger.setLogContext (msoRequest);
1156         MsoLogger.setServiceName ("DeleteVf");
1157         LOGGER.debug ("Deleting VF " + vnfName + " in " + cloudSiteId + "/" + tenantId);
1158         // Will capture execution time for metrics
1159         long startTime = System.currentTimeMillis ();
1160         
1161         // 1702 capture the output parameters on a delete
1162         // so we'll need to query first
1163         DeploymentInfo deployment = null;
1164         try {
1165                 deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
1166         } catch (MsoException me) {
1167             // Failed to query the deployment.  Convert to a generic VnfException
1168             me.addContext ("DeleteVFModule");
1169             String error = "Delete VFModule: Query to get outputs: " + vnfName + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1170             LOGGER.recordMetricEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "QueryDeployment", null);
1171             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, vnfName, cloudSiteId, tenantId, CLOUDIFY, "QueryDeployment", MsoLogger.ErrorCode.DataError, "Exception - QueryDeployment", me);
1172             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1173             throw new VnfException (me);
1174         }
1175         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected Object types
1176         outputs.value = convertMapStringObjectToStringString(deployment.getOutputs());
1177
1178         // Use the MsoHeatUtils to delete the stack. Set the polling flag to true.
1179         // The possible outcomes of deleteStack are a StackInfo object with status
1180         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
1181         // could be thrown.
1182         long subStartTime = System.currentTimeMillis ();
1183         try {
1184             cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantId, vnfName, 5);
1185             LOGGER.recordMetricEvent (subStartTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully received response from DeleteDeployment", CLOUDIFY, "DeleteDeployment", vnfName);
1186         } catch (MsoException me) {
1187             me.addContext ("DeleteVfModule");
1188             // Convert to a generic VnfException
1189             String error = "Delete VF: " + vnfName + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1190             LOGGER.recordMetricEvent (subStartTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "DeleteDeployment", "DeleteDeployment", vnfName);
1191             LOGGER.error (MessageEnum.RA_DELETE_VNF_ERR, vnfName, cloudSiteId, tenantId, "DeleteDeployment", "DeleteDeployment", MsoLogger.ErrorCode.DataError, "Exception - DeleteDeployment: " + me.getMessage());
1192             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1193             throw new VnfException (me);
1194         }
1195
1196         // On success, nothing is returned.
1197         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully delete VF");
1198         return;
1199     }
1200
1201     // TODO:  Should Update be supported for Cloudify?  What would this look like?
1202     @Override
1203     public void updateVfModule (String cloudSiteId,
1204                            String tenantId,
1205                            String vnfType,
1206                            String vnfVersion,
1207                            String vnfName,
1208                            String requestType,
1209                            String volumeGroupHeatStackId,
1210                            String baseVfHeatStackId,
1211                            String vfModuleStackId,
1212                            String modelCustomizationUuid,
1213                            Map <String, String> inputs,
1214                            MsoRequest msoRequest,
1215                            Holder <Map <String, String>> outputs,
1216                            Holder <VnfRollback> rollback) throws VnfException
1217         {
1218                 // This operation is not currently supported for Cloudify-orchestrated VF Modules.
1219                 LOGGER.debug ("Update VF Module command attempted but not supported");
1220                 throw new VnfException ("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1221         }
1222
1223 }