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