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