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