41da3c2988cb72bca97887508d6be38b08a73e17
[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 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, String> 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<String,String>();
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                         alarmLogger.sendAlarm(MSO_CONFIGURATION_ERROR,
878                                         MsoAlarmLogger.CRITICAL, error);
879                         throw new VnfException(error, MsoExceptionCategory.INTERNAL);
880                 } else {
881                         LOGGER.debug ("Got HEAT Template from DB: " + heatTemplate.getHeatTemplate());
882                 }
883
884         if (heatEnvironment == null) {
885            String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType;
886                 LOGGER.error (MessageEnum.RA_VNF_UNKNOWN_PARAM, "Heat Environment ID", "OpenStack", "", MsoLogger.ErrorCode.DataError, error);
887                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
888                 // Alarm on this error, configuration must be fixed
889                 alarmLogger.sendAlarm (MSO_CONFIGURATION_ERROR, MsoAlarmLogger.CRITICAL, error);
890
891                 throw new VnfException (error, MsoExceptionCategory.INTERNAL);
892         } else {
893             LOGGER.debug ("Got Heat Environment from DB: " + heatEnvironment.getEnvironment());
894         }
895
896
897         try {
898             // All variables converted to their native object types
899             HashMap<String, Object> goldenInputs = new HashMap<String,Object>();
900             List<String> extraInputs = new ArrayList<String>();
901
902             // NOTE: SKIP THIS FOR CLOUDIFY for now.  Just use what was passed in.
903             //  This whole section needs to be rewritten.
904                         Boolean skipInputChecks = false;
905
906                         if (skipInputChecks) {
907                                 goldenInputs = new HashMap<String,Object>();
908                                 for (String key : inputs.keySet()) {
909                                         goldenInputs.put(key, inputs.get(key));
910                                 }
911                         }
912                         else {
913                                 // Build maps for the parameters (including aliases) to simplify checks
914                                 HashMap<String, HeatTemplateParam> params = new HashMap<String, HeatTemplateParam>();
915
916                                 Set<HeatTemplateParam> paramSet = heatTemplate.getParameters();
917                                 LOGGER.debug("paramSet has " + paramSet.size() + " entries");
918
919                                 for (HeatTemplateParam htp : paramSet) {
920                                         params.put(htp.getParamName(), htp);
921
922                                         // Include aliases.
923                                         String alias = htp.getParamAlias();
924                                         if (alias != null && !alias.equals("") && !params.containsKey(alias)) {
925                                                 params.put(alias, htp);
926                                         }
927                                 }
928
929                                 // First, convert all inputs to their "template" type
930                                 for (String key : inputs.keySet()) {
931                                         if (params.containsKey(key)) {
932                                                 Object value = cloudifyUtils.convertInputValue(inputs.get(key), params.get(key));
933                                                 if (value != null) {
934                                                         goldenInputs.put(key, value);
935                                                 }
936                                                 else {
937                                                         LOGGER.debug("Failed to convert input " + key + "='" + inputs.get(key) + "' to " + params.get(key).getParamType());
938                                                 }
939                                         } else {
940                                                 extraInputs.add(key);
941                                         }
942                                 }
943
944                                 if (!extraInputs.isEmpty()) {
945                                         LOGGER.debug("Ignoring extra inputs: " + extraInputs);
946                                 }
947
948                                 // Next add in Volume Group Outputs if there are any.  Copy directly without conversions.
949                                 if (volumeGroupOutputs != null  &&  !volumeGroupOutputs.isEmpty()) {
950                                         for (String key : volumeGroupOutputs.keySet()) {
951                                                 if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
952                                                         goldenInputs.put(key, volumeGroupOutputs.get(key));
953                                                 }
954                                         }
955                                 }
956
957                                 // Next add in Base Module Outputs if there are any.  Copy directly without conversions.
958                                 if (baseModuleOutputs != null  &&  !baseModuleOutputs.isEmpty()) {
959                                         for (String key : baseModuleOutputs.keySet()) {
960                                                 if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
961                                                         goldenInputs.put(key, baseModuleOutputs.get(key));
962                                                 }
963                                         }
964                                 }
965
966                                 // Last, add in values from the "environment" file.
967                                 // These are added to the inputs, since Cloudify doesn't pass an environment file like Heat.
968
969                                 // TODO: This may take a different form for Cloudify, but for now process it
970                                 //       with Heat environment file syntax
971                 StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());
972                                 MsoHeatEnvironmentEntry mhee = new MsoHeatEnvironmentEntry (sb);
973
974                                 if (mhee.getParameters() != null) {
975                                         for (MsoHeatEnvironmentParameter envParam : mhee.getParameters()) {
976                                                 // If this is a template input, copy to golden inputs
977                                                 String envKey = envParam.getName();
978                                                 if (params.containsKey(envKey)  &&  !goldenInputs.containsKey(envKey)) {
979                                                         Object value = cloudifyUtils.convertInputValue(envParam.getValue(), params.get(envKey));
980                                                         if (value != null) {
981                                                                 goldenInputs.put(envKey, value);
982                                                         }
983                                                         else {
984                                                                 LOGGER.debug("Failed to convert environment parameter " + envKey + "='" + envParam.getValue() + "' to " + params.get(envKey).getParamType());
985                                                         }
986                                                 }
987                                         }
988                                 }
989
990                     this.sendMapToDebug(goldenInputs, "Final inputs sent to Cloudify");
991
992
993                     // Check that required parameters have been supplied from any of the sources
994                     String missingParams = null;
995                     boolean checkRequiredParameters = true;
996                     try {
997                         String propertyString = this.environment.getProperty(MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
998                         if ("false".equalsIgnoreCase (propertyString) || "n".equalsIgnoreCase (propertyString)) {
999                             checkRequiredParameters = false;
1000                             LOGGER.debug ("CheckRequiredParameters is FALSE. Will still check but then skip blocking..."
1001                                           + MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS);
1002                         }
1003                     } catch (Exception e) {
1004                         // No problem - default is true
1005                         LOGGER.debug ("An exception occured trying to get property " + MsoVnfCloudifyAdapterImpl.CHECK_REQD_PARAMS, e);
1006                     }
1007
1008
1009                     for (HeatTemplateParam parm : heatTemplate.getParameters ()) {
1010                         if (parm.isRequired () && (!goldenInputs.containsKey (parm.getParamName ()))) {
1011                         LOGGER.debug ("adding to missing parameters list: " + parm.getParamName ());
1012                         if (missingParams == null) {
1013                             missingParams = parm.getParamName ();
1014                         } else {
1015                             missingParams += "," + parm.getParamName ();
1016                         }
1017                         }
1018                     }
1019
1020                     if (missingParams != null) {
1021                         if (checkRequiredParameters) {
1022                                 // Problem - missing one or more required parameters
1023                                 String error = "Create VFModule: Missing Required inputs: " + missingParams;
1024                                 LOGGER.error (MessageEnum.RA_MISSING_PARAM, missingParams, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Create VFModule: Missing Required inputs");
1025                             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.BadRequest, error);
1026                                 throw new VnfException (error, MsoExceptionCategory.USERDATA);
1027                         } else {
1028                                 LOGGER.debug ("found missing parameters [" + missingParams + "] - but checkRequiredParameters is false - will not block");
1029                         }
1030                     } else {
1031                         LOGGER.debug ("No missing parameters found - ok to proceed");
1032                     }
1033
1034                         } // NOTE: END PARAMETER CHECKING
1035
1036                         // Ready to deploy the VF Module.
1037                         // *First step - make sure the blueprint is loaded into Cloudify.
1038                         String blueprintName = heatTemplate.getTemplateName();
1039                         String blueprint = heatTemplate.getTemplateBody();
1040                         String blueprintId = blueprintName;
1041
1042                         // Use the main blueprint name as the blueprint ID (strip yaml extensions).
1043             if (blueprintId.endsWith(".yaml"))
1044                 blueprintId = blueprintId.substring(0,blueprintId.lastIndexOf(".yaml"));
1045
1046                         try {
1047                                 if (! cloudifyUtils.isBlueprintLoaded (cloudSiteId, blueprintId)) {
1048                                         LOGGER.debug ("Blueprint " + blueprintId + " is not loaded.  Will upload it now.");
1049
1050                                         Map<String,byte[]> blueprintFiles = new HashMap<String,byte[]>();
1051
1052                                         blueprintFiles.put(blueprintName, blueprint.getBytes());
1053
1054                             // TODO:  Implement nested blueprint logic based on Cloudify structures.
1055                                         //        For now, just use the Heat structures.
1056                                         //        The query returns a map of String->Object, where the map keys provide one layer of
1057                                         //        indirection from the Heat template names.  For this case, assume the map key matches
1058                                         //        the nested blueprint name.
1059                             List<HeatTemplate> nestedBlueprints = heatTemplate.getChildTemplates();
1060                             if (nestedBlueprints != null) {
1061                                     for (HeatTemplate nestedBlueprint: nestedBlueprints) {
1062                                         blueprintFiles.put(nestedBlueprint.getTemplateName(), nestedBlueprint.getTemplateBody().getBytes());
1063                                     }
1064                             }
1065
1066                             // TODO:  Implement file artifact logic based on Cloudify structures.
1067                             //        For now, just use the Heat structures.
1068                             List<HeatFiles> heatFiles = vf.getHeatFiles();
1069                             if (heatFiles != null) {
1070                                     for (HeatFiles heatFile: heatFiles) {
1071                                         blueprintFiles.put(heatFile.getFileName(), heatFile.getFileBody().getBytes());
1072                                     }
1073                             }
1074
1075                             // Upload the blueprint package
1076                                         cloudifyUtils.uploadBlueprint(cloudSiteId, blueprintId, blueprintName, blueprintFiles, false);
1077
1078                                 }
1079                         }
1080
1081                         catch (MsoException me) {
1082                 me.addContext ("CreateVFModule");
1083                 String error = "Create VF Module: Upload blueprint failed.  Blueprint=" + blueprintName + ": " + me;
1084                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "MsoException - uploadBlueprint", me);
1085                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1086                 throw new VnfException (me);
1087
1088                         }
1089
1090             // Ignore MsoTenantNotFound and MsoStackAlreadyExists exceptions
1091             // because we already checked for those.
1092             long createDeploymentStarttime = System.currentTimeMillis ();
1093             try {
1094                 // KLUDGE - Cloudify requires Tenant Name for Openstack.  We have the ID.
1095                 //          Go directly to Keystone until APIs could be updated to supply the name.
1096                 MsoTenant msoTenant = keystoneUtils.queryTenant(tenantId, cloudSiteId);
1097                 String tenantName = (msoTenant != null? msoTenant.getTenantName() : tenantId);
1098
1099                 if (backout == null) {
1100                         backout = true;
1101                 }
1102
1103                 cloudifyDeployment = cloudifyUtils.createAndInstallDeployment (cloudSiteId,
1104                                               tenantName,
1105                                               vfModuleName,
1106                                               blueprintId,
1107                                               goldenInputs,
1108                                               true,
1109                                               heatTemplate.getTimeoutMinutes (),
1110                                               backout.booleanValue());
1111
1112                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, CLOUDIFY_RESPONSE_SUCCESS, CLOUDIFY, "CreateDeployment", vfModuleName);
1113             } catch (MsoException me) {
1114                 me.addContext ("CreateVFModule");
1115                 String error = "Create VF Module " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1116                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "CreateDeployment", vfModuleName);
1117                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "MsoException - createDeployment", me);
1118                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1119                 throw new VnfException (me);
1120             } catch (NullPointerException npe) {
1121                 String error = "Create VFModule " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + npe;
1122                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "CreateDeployment", vfModuleName);
1123                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "NullPointerException - createDeployment", npe);
1124                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1125                 LOGGER.debug("NULL POINTER EXCEPTION at cloudify.createAndInstallDeployment");
1126                 //npe.addContext ("CreateVNF");
1127                 throw new VnfException ("NullPointerException during cloudify.createAndInstallDeployment");
1128             } catch (Exception e) {
1129                 LOGGER.recordMetricEvent (createDeploymentStarttime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, "Exception while creating deployment with Cloudify", CLOUDIFY, "CreateDeployment", vfModuleName);
1130                 LOGGER.debug("unhandled exception at cloudify.createAndInstallDeployment");
1131                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, "Exception while creating deployment with Cloudify");
1132                 throw new VnfException("Exception during cloudify.createAndInstallDeployment! " + e.getMessage());
1133             }
1134         } catch (Exception e) {
1135                 LOGGER.debug("unhandled exception in create VF");
1136                 throw new VnfException("Exception during create VF " + e.getMessage());
1137
1138         }
1139
1140         // Reach this point if create is successful.
1141         // Populate remaining rollback info and response parameters.
1142         vfRollback.setVnfCreated (true);
1143         vfRollback.setVnfId (cloudifyDeployment.getId());
1144         vnfId.value = cloudifyDeployment.getId();
1145         outputs.value = copyStringOutputs (cloudifyDeployment.getOutputs ());
1146
1147         rollback.value = vfRollback;
1148
1149         LOGGER.debug ("VF Module " + vfModuleName + " successfully created");
1150         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully create VF Module");
1151         return;
1152     }
1153
1154     public void deleteVfModule (String cloudSiteId,
1155                            String tenantId,
1156                            String vnfName,
1157                            MsoRequest msoRequest,
1158                            Holder <Map <String, String>> outputs) throws VnfException {
1159         MsoLogger.setLogContext (msoRequest);
1160         MsoLogger.setServiceName ("DeleteVf");
1161         LOGGER.debug ("Deleting VF " + vnfName + " in " + cloudSiteId + "/" + tenantId);
1162         // Will capture execution time for metrics
1163         long startTime = System.currentTimeMillis ();
1164
1165         // 1702 capture the output parameters on a delete
1166         // so we'll need to query first
1167         DeploymentInfo deployment = null;
1168         try {
1169                 deployment = cloudifyUtils.queryDeployment(cloudSiteId, tenantId, vnfName);
1170         } catch (MsoException me) {
1171             // Failed to query the deployment.  Convert to a generic VnfException
1172             me.addContext ("DeleteVFModule");
1173             String error = "Delete VFModule: Query to get outputs: " + vnfName + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1174             LOGGER.recordMetricEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, CLOUDIFY, "QueryDeployment", null);
1175             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, vnfName, cloudSiteId, tenantId, CLOUDIFY, "QueryDeployment", MsoLogger.ErrorCode.DataError, "Exception - QueryDeployment", me);
1176             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1177             throw new VnfException (me);
1178         }
1179         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected Object types
1180         outputs.value = convertMapStringObjectToStringString(deployment.getOutputs());
1181
1182         // Use the MsoHeatUtils to delete the stack. Set the polling flag to true.
1183         // The possible outcomes of deleteStack are a StackInfo object with status
1184         // of NOTFOUND (on success) or FAILED (on error). Also, MsoOpenstackException
1185         // could be thrown.
1186         long subStartTime = System.currentTimeMillis ();
1187         try {
1188             cloudifyUtils.uninstallAndDeleteDeployment(cloudSiteId, tenantId, vnfName, 5);
1189             LOGGER.recordMetricEvent (subStartTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully received response from DeleteDeployment", CLOUDIFY, "DeleteDeployment", vnfName);
1190         } catch (MsoException me) {
1191             me.addContext ("DeleteVfModule");
1192             // Convert to a generic VnfException
1193             String error = "Delete VF: " + vnfName + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1194             LOGGER.recordMetricEvent (subStartTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "DeleteDeployment", "DeleteDeployment", vnfName);
1195             LOGGER.error (MessageEnum.RA_DELETE_VNF_ERR, vnfName, cloudSiteId, tenantId, "DeleteDeployment", "DeleteDeployment", MsoLogger.ErrorCode.DataError, "Exception - DeleteDeployment: " + me.getMessage());
1196             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1197             throw new VnfException (me);
1198         }
1199
1200         // On success, nothing is returned.
1201         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully delete VF");
1202         return;
1203     }
1204
1205     // TODO:  Should Update be supported for Cloudify?  What would this look like?
1206     @Override
1207     public void updateVfModule (String cloudSiteId,
1208                            String tenantId,
1209                            String vnfType,
1210                            String vnfVersion,
1211                            String vnfName,
1212                            String requestType,
1213                            String volumeGroupHeatStackId,
1214                            String baseVfHeatStackId,
1215                            String vfModuleStackId,
1216                            String modelCustomizationUuid,
1217                            Map <String, String> inputs,
1218                            MsoRequest msoRequest,
1219                            Holder <Map <String, String>> outputs,
1220                            Holder <VnfRollback> rollback) throws VnfException
1221         {
1222                 // This operation is not currently supported for Cloudify-orchestrated VF Modules.
1223                 LOGGER.debug ("Update VF Module command attempted but not supported");
1224                 throw new VnfException ("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1225         }
1226
1227 }