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