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