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