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