704d54c9185fb148e30e2ee41010aaf746cdc950
[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.MessageEnum;
76
77 import org.onap.so.logger.MsoLogger;
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 tenantId,
157                            String vnfType,
158                            String vnfVersion,
159                            String vnfName,
160                            String requestType,
161                            String volumeGroupHeatStackId,
162                            Map <String, Object> inputs,
163                            Boolean failIfExists,
164                            Boolean backout,
165                            Boolean enableBridge,
166                            MsoRequest msoRequest,
167                            Holder <String> vnfId,
168                            Holder <Map <String, String>> outputs,
169                            Holder <VnfRollback> rollback)
170         throws VnfException
171     {
172         // This operation is no longer supported at the VNF level.  The adapter is only called to deploy modules.
173         logger.debug("CreateVNF command attempted but not supported");
174         throw new VnfException("CreateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
175     }
176
177     /**
178      * This is the "Update VNF" web service implementation.
179      * This function is now unsupported and will return an error.
180      *
181      */
182     @Override
183     public void updateVnf (String cloudSiteId,
184                            String tenantId,
185                            String vnfType,
186                            String vnfVersion,
187                            String vnfName,
188                            String requestType,
189                            String volumeGroupHeatStackId,
190                            Map <String, Object> inputs,
191                            MsoRequest msoRequest,
192                            Holder <Map <String, String>> outputs,
193                            Holder <VnfRollback> rollback)
194                 throws VnfException
195         {
196         // This operation is no longer supported at the VNF level.  The adapter is only called to deploy modules.
197         logger.debug("UpdateVNF command attempted but not supported");
198         throw new VnfException ("UpdateVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
199     }
200
201     /**
202      * This is the "Query VNF" web service implementation.
203      *
204      * This really should be QueryVfModule, but nobody ever changed it.
205      *
206      * The method returns an indicator that the VNF exists, along with its status and outputs.
207      * The input "vnfName" will also be reflected back as its ID.
208      *
209      * @param cloudSiteId CLLI code of the cloud site in which to query
210      * @param tenantId Openstack tenant identifier
211      * @param vnfNameOrId VNF Name or ID to query
212      * @param msoRequest Request tracking information for logs
213      * @param vnfExists Flag reporting the result of the query
214      * @param vnfId Holder for output VNF ID
215      * @param outputs Holder for Map of outputs from the deployed VF Module (assigned IPs, etc)
216      */
217     @Override
218     public void queryVnf (String cloudSiteId,
219                           String tenantId,
220                           String vnfNameOrId,
221                           MsoRequest msoRequest,
222                           Holder <Boolean> vnfExists,
223                           Holder <String> vnfId,
224                           Holder <VnfStatus> status,
225                           Holder <Map <String, String>> outputs)
226         throws VnfException
227     {
228         MsoLogger.setLogContext (msoRequest);
229         logger.debug("Querying VNF " + vnfNameOrId + " in " + cloudSiteId + "/" + tenantId);
230
231         // Will capture execution time for metrics
232         long startTime = System.currentTimeMillis ();
233         long subStartTime = System.currentTimeMillis ();
234
235         VduInstance vduInstance = null;
236         CloudInfo cloudInfo = new CloudInfo(cloudSiteId, tenantId, null);
237
238         VduPlugin vduPlugin = getVduPlugin(cloudSiteId);
239
240         try {
241                 vduInstance = vduPlugin.queryVdu (cloudInfo, vnfNameOrId);
242         }
243         catch (VduException e) {
244           // Failed to query the VDU due to a plugin exception.
245           // Convert to a generic VnfException
246           e.addContext("QueryVNF");
247           String error = "Query VNF (VDU): " + vnfNameOrId + " in " + cloudSiteId + "/" + tenantId + ": " + e;
248           logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfNameOrId, cloudSiteId,
249               tenantId, "VDU", "QueryVNF", MsoLogger.ErrorCode.DataError.getValue(), "Exception - queryVDU", e);
250           logger.debug(error);
251           throw new VnfException(e);
252       }
253
254         if (vduInstance != null  &&  vduInstance.getStatus().getState() != VduStateType.NOTFOUND) {
255           vnfExists.value = Boolean.TRUE;
256           status.value = vduStatusToVnfStatus(vduInstance);
257           vnfId.value = vduInstance.getVduInstanceId();
258           outputs.value = copyStringOutputs(vduInstance.getOutputs());
259
260           logger.debug("VNF {} found, ID = {}", vnfNameOrId, vnfId.value);
261         }
262         else {
263           vnfExists.value = Boolean.FALSE;
264           status.value = VnfStatus.NOTFOUND;
265           vnfId.value = null;
266           outputs.value = new HashMap<String, String>(); // Return as an empty map
267
268           logger.debug("VNF {} not found", vnfNameOrId);
269       }
270         return;
271     }
272
273
274     /**
275      * This is the "Delete VNF" web service implementation.
276      * This function is now unsupported and will return an error.
277      *
278      */
279     @Override
280     public void deleteVnf (String cloudSiteId,
281                            String tenantId,
282                            String vnfName,
283                            MsoRequest msoRequest) throws VnfException {
284         MsoLogger.setLogContext (msoRequest);
285
286         // This operation is no longer supported at the VNF level.  The adapter is only called to deploy modules.
287         logger.debug("DeleteVNF command attempted but not supported");
288         throw new VnfException ("DeleteVNF:  Unsupported command", MsoExceptionCategory.USERDATA);
289     }
290
291     /**
292      * This web service endpoint will rollback a previous Create VNF operation.
293      * A rollback object is returned to the client in a successful creation
294      * response. The client can pass that object as-is back to the rollbackVnf
295      * operation to undo the creation.
296      *
297      * TODO: This should be rollbackVfModule and/or rollbackVolumeGroup,
298      * but APIs were apparently never updated.
299      */
300     @Override
301     public void rollbackVnf (VnfRollback rollback) throws VnfException {
302         long startTime = System.currentTimeMillis ();
303         // rollback may be null (e.g. if stack already existed when Create was called)
304         if (rollback == null) {
305             logger.info("{} {} {} {}", MessageEnum.RA_ROLLBACK_NULL.toString(), "OpenStack", "rollbackVnf",
306                 MsoLogger.getServiceName());
307             return;
308         }
309
310         // Don't rollback if nothing was done originally
311         if (!rollback.getVnfCreated()) {
312             return;
313         }
314
315         // Get the elements of the VnfRollback object for easier access
316         String cloudSiteId = rollback.getCloudSiteId ();
317         String tenantId = rollback.getTenantId ();
318         CloudInfo cloudInfo = new CloudInfo (cloudSiteId, tenantId, null);
319
320         String vfModuleId = rollback.getVfModuleStackId ();
321
322         MsoLogger.setLogContext (rollback.getMsoRequest());
323
324         logger.debug("Rolling Back VF Module " + vfModuleId + " in " + cloudSiteId + "/" + tenantId);
325
326         VduInstance vduInstance = null;
327
328         // Use the VduPlugin to delete the VF Module.
329         VduPlugin vduPlugin = getVduPlugin(cloudSiteId);
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 " + cloudSiteId + "/" + tenantId + ": " + ve;
343             logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_DELETE_VNF_ERR.toString(), vfModuleId, cloudSiteId,
344                 tenantId, "VDU", "DeleteVdu", MsoLogger.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 " + 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
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 tenantId Openstack tenant identifier
574      * @param vfModuleType VF Module type key, should match a VNF definition in catalog DB.
575      *        Deprecated - should use modelCustomizationUuid
576      * @param vnfVersion VNF version key, should match a VNF definition in catalog DB
577      *        Deprecated - VF Module versions also captured by modelCustomizationUuid
578      * @param vnfId - VNF ID
579      * @param vfModuleName Name to be assigned to the new VF Module
580      * @param vfModuleId Id fo the new VF Module
581      * @param requestType Indicates if this is a Volume Group or Module request
582      * @param volumeGroupId Identifier (i.e. deployment ID) for a Volume Group
583      *        to attach to a VF Module
584      * @param baseVfModuleId Identifier (i.e. deployment ID) of the Base Module if
585      *        this is an Add-on module
586      * @param modelCustomizationUuid Unique ID for the VF Module's model.  Replaces
587      *        the use of vfModuleType.
588      * @param inputs Map of key=value inputs for VNF stack creation
589      * @param failIfExists Flag whether already existing VNF should be considered
590      * @param backout Flag whether to suppress automatic backout (for testing)
591      * @param msoRequest Request tracking information for logs
592      * @param vnfId Holder for output VF Module instance ID in the cloud
593      * @param outputs Holder for Map of VNF outputs from Deployment (assigned IPs, etc)
594      * @param rollback Holder for returning VnfRollback object
595      */
596     @Override
597     public void createVfModule(String cloudSiteId,
598             String tenantId,
599             String vfModuleType,
600             String vnfVersion,
601             String genericVnfId,
602             String vfModuleName,
603             String vfModuleId,
604             String requestType,
605             String volumeGroupId,
606             String baseVfModuleId,
607             String modelCustomizationUuid,
608             Map <String, Object> inputs,
609             Boolean failIfExists,
610             Boolean backout,
611             Boolean enableBridge,
612             MsoRequest msoRequest,
613             Holder <String> vnfId,
614             Holder <Map <String, String>> outputs,
615             Holder <VnfRollback> rollback)
616         throws VnfException
617     {
618         // Will capture execution time for metrics
619         long startTime = System.currentTimeMillis ();
620
621         MsoLogger.setLogContext (msoRequest);
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", MsoLogger.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.setTenantId(tenantId);
665         vfRollback.setMsoRequest(msoRequest);
666         vfRollback.setRequestType(requestType);
667         vfRollback.setIsBase(false);    // Until we know better
668         vfRollback.setVolumeGroupHeatStackId(volumeGroupId);
669         vfRollback.setBaseGroupHeatStackId(baseVfModuleId);
670         vfRollback.setModelCustomizationUuid(modelCustomizationUuid);
671         vfRollback.setMode("CFY");
672
673                 rollback.value = vfRollback; // Default rollback - no updates performed
674
675         // Get the VNF/VF Module definition from the Catalog DB first.
676         // There are three relevant records:  VfModule, VfModuleCustomization, VnfResource
677
678         VfModule vfModule = null;
679         VnfResource vnfResource = null;
680         VfModuleCustomization vfModuleCust = null;
681
682         try {
683             vfModuleCust = vfModuleCustomRepo.findByModelCustomizationUUID(modelCustomizationUuid);
684
685             if (vfModuleCust == null) {
686                 String error = "Create vfModule error: Unable to find vfModuleCust with modelCustomizationUuid="
687                     + modelCustomizationUuid;
688                 logger.debug(error);
689                 logger.error("{} {} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
690                     "VF Module ModelCustomizationUuid", modelCustomizationUuid, "CatalogDb",
691                     MsoLogger.ErrorCode.DataError, error);
692                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
693             } else {
694                 logger.debug("Found vfModuleCust entry {}", vfModuleCust.toString());
695             }
696
697             // Get the vfModule and vnfResource records
698                 vfModule = vfModuleCust.getVfModule();
699                 vnfResource = vfModuleCust.getVfModule().getVnfResources();
700         }
701         catch (Exception e) {
702
703                 logger.debug("unhandled exception in create VF - [Query]" + e.getMessage());
704                 throw new VnfException("Exception during create VF " + e.getMessage());
705         }
706
707         //  Perform a version check against cloudSite
708         // Obtain the cloud site information where we will create the VF Module
709         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite (cloudSiteId);
710         if (!cloudSiteOp.isPresent()) {
711             throw new VnfException (new MsoCloudSiteNotFound (cloudSiteId));
712         }
713         CloudSite cloudSite = cloudSiteOp.get();
714                 MavenLikeVersioning aicV = new MavenLikeVersioning();
715                 aicV.setVersion(cloudSite.getCloudVersion());
716                 Boolean usingMulticloud = getUsingMulticloud(cloudSite);
717
718                 String vnfMin = vnfResource.getAicVersionMin();
719                 String vnfMax = vnfResource.getAicVersionMax();
720
721         if ((vnfMin != null && !(aicV.isMoreRecentThan(vnfMin) || aicV.isTheSameVersion(vnfMin))) || (vnfMax != null
722             && aicV.isMoreRecentThan(vnfMax))) {
723             // ERROR
724             String error =
725                 "VNF Resource type: " + vnfResource.getModelName() + ", ModelUuid=" + vnfResource.getModelUUID()
726                     + " VersionMin=" + vnfMin + " VersionMax:" + vnfMax + " NOT supported on Cloud: " + cloudSiteId
727                     + " with AIC_Version:" + cloudSite.getCloudVersion();
728             logger.error("{} {} {} {} {}", MessageEnum.RA_CONFIG_EXC.toString(), error, "OpenStack",
729                 MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Exception - setVersion");
730             logger.debug(error);
731             throw new VnfException(error, MsoExceptionCategory.USERDATA);
732         }
733         // End Version check
734
735
736         VduInstance vduInstance = null;
737         CloudInfo cloudInfo = new CloudInfo (cloudSiteId, tenantId, null);
738
739         // Use the VduPlugin.
740         VduPlugin vduPlugin = getVduPlugin(cloudSiteId);
741
742         // First, look up to see if the VF already exists, unless using multicloud adapter
743
744         long subStartTime1 = System.currentTimeMillis ();
745         if (!usingMulticloud) {
746             try {
747                 vduInstance = vduPlugin.queryVdu (cloudInfo, vfModuleName);
748             }
749             catch (VduException me) {
750                 // Failed to query the VDU due to a plugin exception.
751                 String error = "Create VF Module: Query " + vfModuleName + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
752                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName,
753                     cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError.getValue(),
754                     "Exception - queryVdu", me);
755                 logger.debug(error);
756                 // Convert to a generic VnfException
757                 me.addContext ("CreateVFModule");
758                 throw new VnfException (me);
759             }
760         }
761
762         // More precise handling/messaging if the Module already exists
763         if (vduInstance != null && !(vduInstance.getStatus().getState() == VduStateType.NOTFOUND)) {
764                 VduStateType status = vduInstance.getStatus().getState();
765             logger.debug("Found Existing VDU, status=" + status);
766
767             if (status == VduStateType.INSTANTIATED) {
768                 if (failIfExists != null && failIfExists) {
769                     // fail - it exists
770                     String error =
771                         "Create VF: Deployment " + vfModuleName + " already exists in " + cloudSiteId + "/" + tenantId;
772                     logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
773                         cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError.getValue(),
774                         "VF Module " + vfModuleName + " already exists");
775                     logger.debug(error);
776                     throw new VnfAlreadyExists(vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
777                 } else {
778                     // Found existing deployment and client has not requested "failIfExists".
779                     // Populate the outputs from the existing deployment.
780
781                     vnfId.value = vduInstance.getVduInstanceId();
782                     outputs.value = copyStringOutputs(vduInstance.getOutputs());
783                     return;
784                 }
785             }
786             // Check through various detailed error cases
787             else if (status == VduStateType.INSTANTIATING || status == VduStateType.DELETING
788                 || status == VduStateType.UPDATING) {
789                 // fail - it's in progress - return meaningful error
790                 String error =
791                     "Create VF: Deployment " + vfModuleName + " already exists and has status " + status.toString()
792                         + " in " + cloudSiteId + "/" + tenantId + "; please wait for it to complete, or fix manually.";
793                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
794                     cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError.getValue(),
795                     "VF Module " + vfModuleName + " already exists");
796                 logger.debug(error);
797                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
798             } else if (status == VduStateType.FAILED) {
799                 // fail - it exists and is in a FAILED state
800                 String error =
801                     "Create VF: Deployment " + vfModuleName + " already exists and is in FAILED state in " + cloudSiteId
802                         + "/" + tenantId + "; requires manual intervention.";
803                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
804                     cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError.getValue(),
805                     "VF Module " + vfModuleName + " already exists and is in FAILED state");
806                 logger.debug(error);
807                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
808             } else if (status == VduStateType.UNKNOWN) {
809                 // fail - it exists and is in a UNKNOWN state
810                 String error =
811                     "Create VF: Deployment " + vfModuleName + " already exists and has status " + status.toString()
812                         + " in " + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
813                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
814                     cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError.getValue(),
815                     "VF Module " + vfModuleName + " already exists and is in " + status.toString() + " state");
816                 logger.debug(error);
817                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
818             } else {
819                 // Unexpected, since all known status values have been tested for
820                 String error =
821                     "Create VF: Deployment " + vfModuleName + " already exists with unexpected status " + status
822                         .toString() + " in " + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
823                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_VNF_ALREADY_EXIST.toString(), vfModuleName,
824                     cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError.getValue(),
825                     "VF Module " + vfModuleName + " already exists and is in an unknown state");
826                 logger.debug(error);
827                 throw new VnfAlreadyExists(vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
828             }
829         }
830
831
832         // Collect outputs from Base Modules and Volume Modules
833         Map<String, Object> baseModuleOutputs = null;
834         Map<String, Object> volumeGroupOutputs = null;
835
836         // If a Volume Group was provided, query its outputs for inclusion in Module input parameters
837         if (!usingMulticloud && volumeGroupId != null) {
838             long subStartTime2 = System.currentTimeMillis ();
839             VduInstance volumeVdu = null;
840             try {
841                 volumeVdu = vduPlugin.queryVdu (cloudInfo, volumeGroupId);
842             }
843             catch (VduException me) {
844                 // Failed to query the Volume Group VDU due to a plugin exception.
845                 String error = "Create VF Module: Query Volume Group " + volumeGroupId + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
846                 logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId,
847                     cloudSiteId, tenantId, "VDU", "queryVdu(volume)", MsoLogger.ErrorCode.DataError.getValue(),
848                     "Exception - queryVdu(volume)", me);
849                 logger.debug(error);
850                 // Convert to a generic VnfException
851                 me.addContext ("CreateVFModule(QueryVolume)");
852                 throw new VnfException (me);
853             }
854
855                 if (volumeVdu == null || volumeVdu.getStatus().getState() == VduStateType.NOTFOUND) {
856                     String error = "Create VFModule: Attached Volume Group DOES NOT EXIST " + volumeGroupId + " in " + cloudSiteId + "/" + tenantId + " USER ERROR"  ;
857               logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), volumeGroupId,
858                   cloudSiteId, tenantId, error, "VDU", "queryVdu(volume)",
859                   MsoLogger.ErrorCode.BusinessProcesssError.getValue(),
860                   "Create VFModule: Attached Volume Group " + "DOES NOT EXIST");
861               logger.debug(error);
862                     throw new VnfException (error, MsoExceptionCategory.USERDATA);
863                 } else {
864                         logger.debug("Found nested volume group");
865                         volumeGroupOutputs = volumeVdu.getOutputs();
866                         this.sendMapToDebug(volumeGroupOutputs, "volumeGroupOutputs");
867                 }
868         }
869
870         // If this is an Add-On Module, query the Base Module outputs
871         // Note: This will be performed whether or not the current request is for an
872         //       Add-On Volume Group or Add-On VF Module
873
874         if (vfModule.getIsBase()) {
875             logger.debug("This is a BASE Module request");
876             vfRollback.setIsBase(true);
877         } else {
878             logger.debug("This is an Add-On Module request");
879
880             // Add-On Modules should always have a Base, but just treat as a warning if not provided.
881             // Add-on Volume requests may or may not specify a base.
882             if (!isVolumeRequest && baseVfModuleId == null) {
883                 logger.debug("WARNING:  Add-on Module request - no Base Module ID provided");
884             }
885
886             // Need to verify if multicloud needs to have the vaseVfModuleId passed to it.  Ignoring this for now.
887             if (!usingMulticloud && baseVfModuleId != null) {
888                     long subStartTime2 = System.currentTimeMillis ();
889                     VduInstance baseVdu = null;
890                     try {
891                         baseVdu = vduPlugin.queryVdu (cloudInfo, baseVfModuleId);
892                     }
893                     catch (MsoException me) {
894                         // Failed to query the Base VF Module due to a Vdu Plugin exception.
895                         String error = "Create VF Module: Query Base " + baseVfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
896                   logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId,
897                       cloudSiteId, tenantId, "VDU", "queryVdu(Base)", MsoLogger.ErrorCode.DataError.getValue(),
898                       "Exception - queryVdu(Base)", me);
899                   logger.debug(error);
900                   // Convert to a generic VnfException
901                         me.addContext ("CreateVFModule(QueryBase)");
902                         throw new VnfException (me);
903                     }
904
905                 if (baseVdu == null || baseVdu.getStatus().getState() == VduStateType.NOTFOUND) {
906                     String error =
907                         "Create VFModule: Base Module DOES NOT EXIST " + baseVfModuleId + " in " + cloudSiteId + "/"
908                             + tenantId + " USER ERROR";
909                     logger.error("{} {} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), baseVfModuleId,
910                         cloudSiteId, tenantId, error, "VDU", "queryVdu(Base)",
911                         MsoLogger.ErrorCode.BusinessProcesssError.getValue(),
912                         "Create VFModule: Base Module DOES NOT EXIST");
913                     logger.debug(error);
914                     throw new VnfException(error, MsoExceptionCategory.USERDATA);
915                 } else {
916                     logger.debug("Found base module");
917                     baseModuleOutputs = baseVdu.getOutputs();
918                     this.sendMapToDebug(baseModuleOutputs, "baseModuleOutputs");
919                 }
920             }
921         }
922
923
924         // NOTE:  For this section, heatTemplate is used for all template artifacts.
925         // In final implementation (post-POC), the template object would either be generic or there would
926         // be a separate DB Table/Object for different sub-orchestrators.
927
928         // NOTE: The template is fixed for the VF Module.  The environment is part of the customization.
929
930         HeatTemplate heatTemplate = null;
931         HeatEnvironment heatEnvironment = null;
932         if (isVolumeRequest) {
933                         heatTemplate = vfModule.getVolumeHeatTemplate();
934                         heatEnvironment = vfModuleCust.getVolumeHeatEnv();
935                 } else {
936                         heatTemplate = vfModule.getModuleHeatTemplate();
937                         heatEnvironment = vfModuleCust.getHeatEnvironment();
938                 }
939
940                 if (heatTemplate == null) {
941         String error = "UpdateVF: No Heat Template ID defined in catalog database for " + vfModuleType + ", reqType="
942             + requestType;
943         logger.error("{} {} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Template ID", vfModuleType,
944             "VNF", MsoLogger.ErrorCode.DataError.getValue(), error);
945         logger.debug(error);
946         throw new VnfException(error, MsoExceptionCategory.INTERNAL);
947     } else {
948         logger.debug("Got HEAT Template from DB: " + heatTemplate.getHeatTemplate());
949     }
950
951         if (heatEnvironment == null) {
952             String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType;
953             logger.error("{} {} {} {} {}", MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Environment ID",
954                 "OpenStack", MsoLogger.ErrorCode.DataError.getValue(), error);
955             throw new VnfException(error, MsoExceptionCategory.INTERNAL);
956         } else {
957             logger.debug("Got Heat Environment from DB: " + heatEnvironment.getEnvironment());
958         }
959
960
961         // Create the combined set of parameters from the incoming request, base-module outputs,
962         // volume-module outputs.  Also, convert all variables to their native object types.
963
964         HashMap<String, Object> goldenInputs = new HashMap<String,Object>();
965         List<String> extraInputs = new ArrayList<String>();
966
967                 Boolean skipInputChecks = false;
968
969                 if (skipInputChecks) {
970                         goldenInputs = new HashMap<String,Object>();
971                         for (String key : inputs.keySet()) {
972                                 goldenInputs.put(key, inputs.get(key));
973                         }
974                 }
975                 else {
976                         // Build maps for the parameters (including aliases) to simplify checks
977                         HashMap<String, HeatTemplateParam> params = new HashMap<String, HeatTemplateParam>();
978
979                         Set<HeatTemplateParam> paramSet = heatTemplate.getParameters();
980                         logger.debug("paramSet has " + paramSet.size() + " entries");
981
982                         for (HeatTemplateParam htp : paramSet) {
983                                 params.put(htp.getParamName(), htp);
984
985                                 // Include aliases.
986                                 String alias = htp.getParamAlias();
987                                 if (alias != null && !alias.equals("") && !params.containsKey(alias)) {
988                                         params.put(alias, htp);
989                                 }
990                         }
991
992                         // First, convert all inputs to their "template" type
993                         for (String key : inputs.keySet()) {
994                                 if (params.containsKey(key)) {
995                                         Object value = convertInputValue(inputs.get(key), params.get(key));
996                                         if (value != null) {
997                                                 goldenInputs.put(key, value);
998                                         }
999                                         else {
1000                                                 logger.debug("Failed to convert input " + key + "='" + inputs.get(key) + "' to " + params.get(key)
1001                 .getParamType());
1002                                         }
1003                                 } else {
1004                                         extraInputs.add(key);
1005                                 }
1006                         }
1007
1008                         if (!extraInputs.isEmpty()) {
1009                                 // Add multicloud inputs
1010                             boolean multicloudInputs = false;
1011                                 for (String key : MsoMulticloudUtils.MULTICLOUD_INPUTS) {
1012                                         if (extraInputs.contains(key)) {
1013                                                 goldenInputs.put(key, inputs.get(key));
1014                                                 extraInputs.remove(key);
1015                                                 multicloudInputs = true;
1016                                                 if (extraInputs.isEmpty()) {
1017                                                         break;
1018                                                 }
1019                                         }
1020                                 }
1021                                 logger.debug("Ignoring extra inputs: " + extraInputs);
1022                         }
1023
1024                         // Next add in Volume Group Outputs if there are any.  Copy directly without conversions.
1025                         if (volumeGroupOutputs != null  &&  !volumeGroupOutputs.isEmpty()) {
1026                                 for (String key : volumeGroupOutputs.keySet()) {
1027                                         if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
1028                                                 goldenInputs.put(key, volumeGroupOutputs.get(key));
1029                                         }
1030                                 }
1031                         }
1032
1033                         // Next add in Base Module Outputs if there are any.  Copy directly without conversions.
1034                         if (baseModuleOutputs != null  &&  !baseModuleOutputs.isEmpty()) {
1035                                 for (String key : baseModuleOutputs.keySet()) {
1036                                         if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
1037                                                 goldenInputs.put(key, baseModuleOutputs.get(key));
1038                                         }
1039                                 }
1040                         }
1041
1042                         // TODO:  The model should support a mechanism to pre-assign default parameter values
1043                         // per "customization" (i.e. usage) of a given module.  In HEAT, this is specified by
1044                         // an Environment file.  There is not a general mechanism in the model to handle this.
1045                         // For the general case, any such parameter/values can be added dynamically to the
1046                         // inputs (only if not already specified).
1047
1048             // Check that required parameters have been supplied from any of the sources
1049             String missingParams = null;
1050             boolean checkRequiredParameters = true;
1051             try {
1052                 String propertyString = this.environment.getProperty(MsoVnfPluginAdapterImpl.CHECK_REQD_PARAMS);
1053                 if ("false".equalsIgnoreCase (propertyString) || "n".equalsIgnoreCase (propertyString)) {
1054                     checkRequiredParameters = false;
1055                     logger.debug("CheckRequiredParameters is FALSE. Will still check but then skip blocking..."
1056                                   + MsoVnfPluginAdapterImpl.CHECK_REQD_PARAMS);
1057                 }
1058             } catch (Exception e) {
1059                 // No problem - default is true
1060                 logger.debug ("An exception occured trying to get property " + MsoVnfPluginAdapterImpl.CHECK_REQD_PARAMS,
1061                     e);
1062             }
1063
1064             // Do the actual parameter checking.
1065             // Include looking at the ENV file as a valid definition of a parameter value.
1066             // TODO:  This handling of ENV applies only to Heat.  A general mechanism to
1067             // support pre-set parameter/values does not yet exist in the model.
1068             //
1069                         StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());
1070                         MsoHeatEnvironmentEntry mhee = new MsoHeatEnvironmentEntry (sb);
1071             for (HeatTemplateParam parm : heatTemplate.getParameters ()) {
1072                 if (parm.isRequired () && (!goldenInputs.containsKey (parm.getParamName ()))) {
1073                     if (mhee != null && mhee.containsParameter(parm.getParamName())) {
1074                         logger.debug ("Required parameter " + parm.getParamName ()
1075                                       + " appears to be in environment - do not count as missing");
1076                     } else {
1077                             logger.debug("adding to missing parameters list: " + parm.getParamName ());
1078                             if (missingParams == null) {
1079                                 missingParams = parm.getParamName ();
1080                             } else {
1081                                 missingParams += "," + parm.getParamName ();
1082                             }
1083                     }
1084                 }
1085             }
1086
1087         if (missingParams != null) {
1088             if (checkRequiredParameters) {
1089                 // Problem - missing one or more required parameters
1090                 String error = "Create VFModule: Missing Required inputs: " + missingParams;
1091                 logger.error("{} {} {} {} {}", MessageEnum.RA_MISSING_PARAM.toString(), missingParams, "VDU",
1092                     MsoLogger.ErrorCode.DataError.getValue(), "Create VFModule: Missing Required inputs");
1093                 logger.debug(error);
1094                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
1095             } else {
1096                 logger.debug(
1097                     "found missing parameters [" + missingParams + "] - but checkRequiredParameters is false - "
1098                         + "will not block");
1099             }
1100         } else {
1101             logger.debug("No missing parameters found - ok to proceed");
1102         }
1103
1104                 } // NOTE: END PARAMETER CHECKING
1105
1106
1107                 // Here we go...  ready to deploy the VF Module.
1108         long instantiateVduStartTime = System.currentTimeMillis ();
1109         if (backout == null) backout = true;
1110
1111         try {
1112             // Construct the VDU Model structure to pass to the targeted VduPlugin
1113             VduModelInfo vduModel = null;
1114             if (!isVolumeRequest) {
1115                 vduModel = vduMapper.mapVfModuleCustomizationToVdu(vfModuleCust);
1116             } else {
1117                 vduModel = vduMapper.mapVfModuleCustVolumeToVdu(vfModuleCust);
1118             }
1119
1120             // Invoke the VduPlugin to instantiate the VF Module
1121             vduInstance = vduPlugin.instantiateVdu(cloudInfo, vfModuleName, goldenInputs, vduModel, backout);
1122
1123         } catch (VduException me) {
1124             // Failed to instantiate the VDU.
1125             me.addContext("CreateVFModule");
1126             String error = "Create VF Module " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1127             logger.error("{} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType, cloudSiteId,
1128                 tenantId, "VDU", MsoLogger.ErrorCode.DataError.getValue(), "MsoException - instantiateVdu", me);
1129             logger.debug(error);
1130             // Convert to a generic VnfException
1131             throw new VnfException(me);
1132         } catch (NullPointerException npe) {
1133             String error = "Create VFModule " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + npe;
1134             logger.error("{} {} {} {} {} {} {}", MessageEnum.RA_CREATE_VNF_ERR.toString(), vfModuleType, cloudSiteId,
1135                 tenantId, "VDU", MsoLogger.ErrorCode.DataError.getValue(), "NullPointerException - instantiateVdu",
1136                 npe);
1137             logger.debug(error);
1138             logger.debug("NULL POINTER EXCEPTION at vduPlugin.instantiateVdu", npe);
1139             throw new VnfException("NullPointerException during instantiateVdu");
1140         } catch (Exception e) {
1141             String error = "Create VFModule " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + e;
1142             logger.debug("Unhandled exception at vduPlugin.instantiateVdu", e);
1143             logger.debug(error);
1144             throw new VnfException("Exception during instantiateVdu: " + e.getMessage());
1145         }
1146
1147
1148         // Reach this point if create is successful.
1149         // Populate remaining rollback info and response parameters.
1150         vfRollback.setVnfCreated (true);
1151         vfRollback.setVnfId (vduInstance.getVduInstanceId());
1152         vnfId.value = vduInstance.getVduInstanceId();
1153         outputs.value = copyStringOutputs (vduInstance.getOutputs ());
1154
1155         rollback.value = vfRollback;
1156
1157         logger.debug("VF Module " + vfModuleName + " successfully created");
1158         return;
1159     }
1160
1161
1162     public void deleteVfModule (String cloudSiteId,
1163                            String tenantId,
1164                            String vfModuleId,
1165                            MsoRequest msoRequest,
1166                            Holder <Map <String, String>> outputs) throws VnfException
1167     {
1168         MsoLogger.setLogContext (msoRequest);
1169
1170         logger.debug("Deleting VF Module " + vfModuleId + " in " + cloudSiteId + "/" + tenantId);
1171         // Will capture execution time for metrics
1172         long startTime = System.currentTimeMillis ();
1173
1174         // Capture the output parameters on a delete, so need to query first
1175         VduInstance vduInstance = null;
1176         CloudInfo cloudInfo = new CloudInfo(cloudSiteId, tenantId, null);
1177
1178         // Use the VduPlugin.
1179         VduPlugin vduPlugin = getVduPlugin(cloudSiteId);
1180
1181         try {
1182                 vduInstance = vduPlugin.queryVdu (cloudInfo, vfModuleId);
1183         }
1184         catch (VduException e) {
1185           // Failed to query the VDU due to a plugin exception.
1186           // Convert to a generic VnfException
1187           e.addContext("QueryVFModule");
1188           String error = "Query VfModule (VDU): " + vfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + e;
1189           logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleId, cloudSiteId,
1190               tenantId, "VDU", "QueryVFModule", MsoLogger.ErrorCode.DataError.getValue(), "Exception - queryVDU", e);
1191           logger.debug(error);
1192           throw new VnfException(e);
1193       }
1194
1195         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected Object types
1196         outputs.value = convertMapStringObjectToStringString(vduInstance.getOutputs());
1197
1198         // Use the VduPlugin to delete the VDU.
1199         // The possible outcomes of deleteVdu are
1200         // - a vnfInstance object with status of DELETED (success)
1201         // - a vnfInstance object with status of NOTFOUND (VDU did not exist, treat as success)
1202         // - a vnfInstance object with status of FAILED (error)
1203         // Also, VduException could be thrown.
1204         long subStartTime = System.currentTimeMillis ();
1205         try {
1206                 // TODO:  Get an appropriate timeout value - require access to the model
1207             vduPlugin.deleteVdu(cloudInfo, vfModuleId, 5);
1208         } catch (VduException me) {
1209             me.addContext ("DeleteVfModule");
1210             // Convert to a generic VnfException
1211             String error = "Delete VF: " + vfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1212             logger.error("{} {} {} {} {} {} {} {}", MessageEnum.RA_DELETE_VNF_ERR.toString(), vfModuleId, cloudSiteId,
1213                 tenantId, "VDU", "DeleteVdu", MsoLogger.ErrorCode.DataError.getValue(),
1214                 "Exception - DeleteVdu: " + me.getMessage());
1215             logger.debug(error);
1216             throw new VnfException (me);
1217         }
1218
1219         // On success, nothing is returned.
1220         return;
1221     }
1222
1223     // Update VF Module not yet implemented for generic VDU plug-in model.
1224     @Override
1225     public void updateVfModule (String cloudSiteId,
1226                            String tenantId,
1227                            String vnfType,
1228                            String vnfVersion,
1229                            String vnfName,
1230                            String requestType,
1231                            String volumeGroupHeatStackId,
1232                            String baseVfHeatStackId,
1233                            String vfModuleStackId,
1234                            String modelCustomizationUuid,
1235                            Map <String, Object> inputs,
1236                            MsoRequest msoRequest,
1237                            Holder <Map <String, String>> outputs,
1238                            Holder <VnfRollback> rollback) throws VnfException
1239         {
1240                 // This operation is not currently supported for VduPlugin-orchestrated VF Modules.
1241             logger.debug("Update VF Module command attempted but not supported");
1242             throw new VnfException ("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1243         }
1244
1245     /*
1246      * Dynamic selection of a VduPlugin version.  For initial tests, base on the "orchestrator"
1247      * defined for the target cloud.  Should really be looking at the VNF Model (ochestration_mode)
1248      * but we don't currently have access to that in Query and Delete cases.
1249      */
1250     private VduPlugin getVduPlugin (String cloudSiteId) {
1251         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite(cloudSiteId);
1252         if (cloudSiteOp.isPresent()) {
1253                 CloudSite cloudSite = cloudSiteOp.get();
1254                 String orchestrator = cloudSite.getOrchestrator();
1255
1256                 if (orchestrator.equalsIgnoreCase("CLOUDIFY")) {
1257                         return cloudifyUtils;
1258                 }
1259                 else if (orchestrator.equalsIgnoreCase("HEAT")) {
1260                         return heatUtils;
1261                 }
1262             if (orchestrator.equalsIgnoreCase("MULTICLOUD")) {
1263                 logger.debug ("Got MulticloudUtils for vduPlugin");
1264                 return multicloudUtils; }
1265         }
1266         // Default - return HEAT plugin, though will fail later
1267         return heatUtils;
1268     }
1269
1270     private Boolean getUsingMulticloud (CloudSite cloudSite) {
1271         if (cloudSite.getOrchestrator().equalsIgnoreCase("MULTICLOUD")) {
1272             return true;
1273         } else {
1274             return false;
1275         }
1276     }
1277 }