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