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