Merge "Add multicloud to VnfPluginAdapter"
[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 import org.onap.so.logger.MsoAlarmLogger;
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     private static MsoAlarmLogger alarmLogger = new MsoAlarmLogger ();
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 vfModuleName Name to be assigned to 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 tenantId,
600             String vfModuleType,
601             String vnfVersion,
602             String vfModuleName,
603             String requestType,
604             String volumeGroupId,
605             String baseVfModuleId,
606             String modelCustomizationUuid,
607             Map <String, String> inputs,
608             Boolean failIfExists,
609             Boolean backout,
610             Boolean enableBridge,
611             MsoRequest msoRequest,
612             Holder <String> vnfId,
613             Holder <Map <String, String>> outputs,
614             Holder <VnfRollback> rollback)
615         throws VnfException
616     {
617         // Will capture execution time for metrics
618         long startTime = System.currentTimeMillis ();
619
620         MsoLogger.setLogContext (msoRequest);
621         MsoLogger.setServiceName ("CreateVfModule");
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,
628                     "VF Module ModelCustomizationUuid", "null", "VDU", "", MsoLogger.ErrorCode.DataError, "Create VF Module: Missing required input: modelCustomizationUuid");
629             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
630             throw new VnfException(error, MsoExceptionCategory.USERDATA);
631         }
632
633         // Clean up some inputs to make comparisons easier
634         if (requestType == null)
635                 requestType = "";
636
637         if ("".equals(volumeGroupId) || "null".equals(volumeGroupId))
638                 volumeGroupId = null;
639
640         if ("".equals(baseVfModuleId) || "null".equals(baseVfModuleId))
641                 baseVfModuleId = null;
642
643         if (inputs == null) {
644                 // Create an empty set of inputs
645                 inputs = new HashMap<>();
646                 LOGGER.debug("inputs == null - setting to empty");
647         } else {
648                 this.sendMapToDebug(inputs);
649         }
650
651         // Check if this is for a "Volume" module
652         boolean isVolumeRequest = false;
653         if (requestType.startsWith("VOLUME")) {
654                 isVolumeRequest = true;
655         }
656
657         LOGGER.debug("requestType = " + requestType + ", volumeGroupStackId = " + volumeGroupId + ", baseStackId = " + baseVfModuleId);
658
659         // Build a default rollback object (no actions performed)
660         VnfRollback vfRollback = new VnfRollback();
661         vfRollback.setCloudSiteId(cloudSiteId);
662         vfRollback.setTenantId(tenantId);
663         vfRollback.setMsoRequest(msoRequest);
664         vfRollback.setRequestType(requestType);
665         vfRollback.setIsBase(false);    // Until we know better
666         vfRollback.setVolumeGroupHeatStackId(volumeGroupId);
667         vfRollback.setBaseGroupHeatStackId(baseVfModuleId);
668         vfRollback.setModelCustomizationUuid(modelCustomizationUuid);
669         vfRollback.setMode("CFY");
670
671                 rollback.value = vfRollback; // Default rollback - no updates performed
672
673         // Get the VNF/VF Module definition from the Catalog DB first.
674         // There are three relevant records:  VfModule, VfModuleCustomization, VnfResource
675
676         VfModule vfModule = null;
677         VnfResource vnfResource = null;
678         VfModuleCustomization vfModuleCust = null;
679
680         try {
681                 vfModuleCust = vfModuleCustomRepo.findByModelCustomizationUUID(modelCustomizationUuid);
682
683             if (vfModuleCust == null) {
684                         String error = "Create vfModule error: Unable to find vfModuleCust with modelCustomizationUuid=" + modelCustomizationUuid;
685                         LOGGER.debug(error);
686                 LOGGER.error(MessageEnum.RA_VNF_UNKNOWN_PARAM,
687                             "VF Module ModelCustomizationUuid", modelCustomizationUuid, "CatalogDb", "", MsoLogger.ErrorCode.DataError, error);
688                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
689                 throw new VnfException(error, MsoExceptionCategory.USERDATA);
690             } else {
691                         LOGGER.debug("Found vfModuleCust entry " + vfModuleCust.toString());
692             }
693
694             // Get the vfModule and vnfResource records
695                 vfModule = vfModuleCust.getVfModule();
696                 vnfResource = vfModuleCust.getVfModule().getVnfResources();
697         }
698         catch (Exception e) {
699
700                 LOGGER.debug("unhandled exception in create VF - [Query]" + e.getMessage());
701                 throw new VnfException("Exception during create VF " + e.getMessage());
702         }
703
704         //  Perform a version check against cloudSite
705         // Obtain the cloud site information where we will create the VF Module
706         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite (cloudSiteId);
707         if (!cloudSiteOp.isPresent()) {
708             throw new VnfException (new MsoCloudSiteNotFound (cloudSiteId));
709         }
710         CloudSite cloudSite = cloudSiteOp.get();
711                 MavenLikeVersioning aicV = new MavenLikeVersioning();
712                 aicV.setVersion(cloudSite.getCloudVersion());
713
714                 String vnfMin = vnfResource.getAicVersionMin();
715                 String vnfMax = vnfResource.getAicVersionMax();
716
717                 if ( (vnfMin != null && !(aicV.isMoreRecentThan(vnfMin) || aicV.isTheSameVersion(vnfMin))) ||
718                      (vnfMax != null && aicV.isMoreRecentThan(vnfMax)))
719                 {
720                         // ERROR
721                         String error = "VNF Resource type: " + vnfResource.getModelName() + ", ModelUuid=" + vnfResource.getModelUUID() + " VersionMin=" + vnfMin + " VersionMax:" + vnfMax + " NOT supported on Cloud: " + cloudSiteId + " with AIC_Version:" + cloudSite.getCloudVersion();
722                         LOGGER.error(MessageEnum.RA_CONFIG_EXC, error, "OpenStack", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception - setVersion");
723                         LOGGER.debug(error);
724                         throw new VnfException(error, MsoExceptionCategory.USERDATA);
725                 }
726                 // End Version check
727
728
729         VduInstance vduInstance = null;
730         CloudInfo cloudInfo = new CloudInfo (cloudSiteId, tenantId, null);
731
732         // Use the VduPlugin.
733         VduPlugin vduPlugin = getVduPlugin(cloudSiteId);
734
735         // First, look up to see if the VF already exists.
736
737         long subStartTime1 = System.currentTimeMillis ();
738         try {
739             vduInstance = vduPlugin.queryVdu (cloudInfo, vfModuleName);
740             LOGGER.recordMetricEvent (subStartTime1, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully received response from VduPlugin", "VDU", "QueryVDU", vfModuleName);
741         }
742         catch (VduException me) {
743             // Failed to query the VDU due to a plugin exception.
744             String error = "Create VF Module: Query " + vfModuleName + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
745             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, vfModuleName, cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError, "Exception - queryVdu", me);
746             LOGGER.recordMetricEvent (subStartTime1, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "VDU", "QueryVdu", vfModuleName);
747             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
748
749             // Convert to a generic VnfException
750             me.addContext ("CreateVFModule");
751             throw new VnfException (me);
752         }
753
754         // More precise handling/messaging if the Module already exists
755         if (vduInstance != null && !(vduInstance.getStatus().getState() == VduStateType.NOTFOUND)) {
756                 VduStateType status = vduInstance.getStatus().getState();
757                         LOGGER.debug ("Found Existing VDU, status=" + status);
758
759                 if (status == VduStateType.INSTANTIATED) {
760                         if (failIfExists != null && failIfExists) {
761                         // fail - it exists
762                                 String error = "Create VF: Deployment " + vfModuleName + " already exists in " + cloudSiteId + "/" + tenantId;
763                                 LOGGER.error (MessageEnum.RA_VNF_ALREADY_EXIST, vfModuleName, cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError, "VF Module " + vfModuleName + " already exists");
764                     LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
765                                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
766                         } else {
767                                 // Found existing deployment and client has not requested "failIfExists".
768                                 // Populate the outputs from the existing deployment.
769
770                                 vnfId.value = vduInstance.getVduInstanceId();
771                                 outputs.value = copyStringOutputs (vduInstance.getOutputs ());
772                     LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully create VF Module (found existing)");
773                     return;
774                         }
775                 }
776                 // Check through various detailed error cases
777                 else if (status == VduStateType.INSTANTIATING || status == VduStateType.DELETING || status == VduStateType.UPDATING) {
778                         // fail - it's in progress - return meaningful error
779                 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.";
780                 LOGGER.error (MessageEnum.RA_VNF_ALREADY_EXIST, vfModuleName, cloudSiteId, tenantId, "VDU", "queryVdu", MsoLogger.ErrorCode.DataError, "VF Module " + vfModuleName + " already exists");
781                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
782                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
783                 }
784                 else if (status == VduStateType.FAILED) {
785                         // fail - it exists and is in a FAILED state
786                 String error = "Create VF: Deployment " + vfModuleName + " already exists and is in FAILED state in " + cloudSiteId + "/" + tenantId + "; requires manual intervention.";
787                 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");
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.UNKNOWN) {
792                         // fail - it exists and is in a UNKNOWN state
793                 String error = "Create VF: Deployment " + vfModuleName + " already exists and has status " + status.toString() + " 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 " + status.toString() + " state");
795                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
796                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
797                 }
798                 else {
799                         // Unexpected, since all known status values have been tested for
800                 String error = "Create VF: Deployment " + vfModuleName + " already exists with unexpected 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 an unknown state");
802                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
803                 throw new VnfAlreadyExists (vfModuleName, cloudSiteId, tenantId, vduInstance.getVduInstanceId());
804                 }
805         }
806
807
808         // Collect outputs from Base Modules and Volume Modules
809         Map<String, Object> baseModuleOutputs = null;
810         Map<String, Object> volumeGroupOutputs = null;
811
812         // If a Volume Group was provided, query its outputs for inclusion in Module input parameters
813         if (volumeGroupId != null) {
814             long subStartTime2 = System.currentTimeMillis ();
815             VduInstance volumeVdu = null;
816             try {
817                 volumeVdu = vduPlugin.queryVdu (cloudInfo, volumeGroupId);
818                 LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Success response from VduPlugin", "VDU", "QueryVdu", volumeGroupId);
819             }
820             catch (VduException me) {
821                 // Failed to query the Volume Group VDU due to a plugin exception.
822                 String error = "Create VF Module: Query Volume Group " + volumeGroupId + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
823                 LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, volumeGroupId, cloudSiteId, tenantId, "VDU", "queryVdu(volume)", MsoLogger.ErrorCode.DataError, "Exception - queryVdu(volume)", me);
824                 LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "VDU", "QueryVdu(volume)", volumeGroupId);
825                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
826
827                 // Convert to a generic VnfException
828                 me.addContext ("CreateVFModule(QueryVolume)");
829                 throw new VnfException (me);
830             }
831
832                 if (volumeVdu == null || volumeVdu.getStatus().getState() == VduStateType.NOTFOUND) {
833                     String error = "Create VFModule: Attached Volume Group DOES NOT EXIST " + volumeGroupId + " in " + cloudSiteId + "/" + tenantId + " USER ERROR"  ;
834                     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");
835                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
836                     LOGGER.debug(error);
837                     throw new VnfException (error, MsoExceptionCategory.USERDATA);
838                 } else {
839                         LOGGER.debug("Found nested volume group");
840                         volumeGroupOutputs = volumeVdu.getOutputs();
841                         this.sendMapToDebug(volumeGroupOutputs, "volumeGroupOutputs");
842                 }
843         }
844
845         // If this is an Add-On Module, query the Base Module outputs
846         // Note: This will be performed whether or not the current request is for an
847         //       Add-On Volume Group or Add-On VF Module
848
849         if (vfModule.getIsBase()) {
850             LOGGER.debug("This is a BASE Module request");
851             vfRollback.setIsBase(true);
852         } else {
853             LOGGER.debug("This is an Add-On Module request");
854
855             // Add-On Modules should always have a Base, but just treat as a warning if not provided.
856             // Add-on Volume requests may or may not specify a base.
857             if (!isVolumeRequest && baseVfModuleId == null) {
858                 LOGGER.debug ("WARNING:  Add-on Module request - no Base Module ID provided");
859             }
860
861             if (baseVfModuleId != null) {
862                     long subStartTime2 = System.currentTimeMillis ();
863                     VduInstance baseVdu = null;
864                     try {
865                         baseVdu = vduPlugin.queryVdu (cloudInfo, baseVfModuleId);
866                         LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Success response from VduPlugin", "VDU", "QueryVdu(Base)", baseVfModuleId);
867                     }
868                     catch (MsoException me) {
869                         // Failed to query the Base VF Module due to a Vdu Plugin exception.
870                         String error = "Create VF Module: Query Base " + baseVfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + me ;
871                         LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, baseVfModuleId, cloudSiteId, tenantId, "VDU", "queryVdu(Base)", MsoLogger.ErrorCode.DataError, "Exception - queryVdu(Base)", me);
872                         LOGGER.recordMetricEvent (subStartTime2, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "VDU", "QueryVdu(Base)", baseVfModuleId);
873                         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
874
875                         // Convert to a generic VnfException
876                         me.addContext ("CreateVFModule(QueryBase)");
877                         throw new VnfException (me);
878                     }
879
880                         if (baseVdu == null || baseVdu.getStatus().getState() == VduStateType.NOTFOUND) {
881                             String error = "Create VFModule: Base Module DOES NOT EXIST " + baseVfModuleId + " in " + cloudSiteId + "/" + tenantId + " USER ERROR"  ;
882                             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, baseVfModuleId, cloudSiteId, tenantId, error, "VDU", "queryVdu(Base)", MsoLogger.ErrorCode.BusinessProcesssError, "Create VFModule: Base Module DOES NOT EXIST");
883                         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.Conflict, error);
884                             LOGGER.debug(error);
885                             throw new VnfException (error, MsoExceptionCategory.USERDATA);
886                         } else {
887                                 LOGGER.debug("Found base module");
888                                 baseModuleOutputs = baseVdu.getOutputs();
889                                 this.sendMapToDebug(baseModuleOutputs, "baseModuleOutputs");
890                         }
891             }
892         }
893
894
895         // NOTE:  For this section, heatTemplate is used for all template artifacts.
896         // In final implementation (post-POC), the template object would either be generic or there would
897         // be a separate DB Table/Object for different sub-orchestrators.
898
899         // NOTE: The template is fixed for the VF Module.  The environment is part of the customization.
900
901         HeatTemplate heatTemplate = null;
902         HeatEnvironment heatEnvironment = null;
903         if (isVolumeRequest) {
904                         heatTemplate = vfModule.getVolumeHeatTemplate();
905                         heatEnvironment = vfModuleCust.getVolumeHeatEnv();
906                 } else {
907                         heatTemplate = vfModule.getModuleHeatTemplate();
908                         heatEnvironment = vfModuleCust.getHeatEnvironment();
909                 }
910
911                 if (heatTemplate == null) {
912                         String error = "UpdateVF: No Heat Template ID defined in catalog database for " + vfModuleType + ", reqType=" + requestType;
913                         LOGGER.error(MessageEnum.RA_VNF_UNKNOWN_PARAM, "Heat Template ID", vfModuleType, "VNF", "", MsoLogger.ErrorCode.DataError, error);
914             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
915                         alarmLogger.sendAlarm(MSO_CONFIGURATION_ERROR,
916                                         MsoAlarmLogger.CRITICAL, error);
917                         throw new VnfException(error, MsoExceptionCategory.INTERNAL);
918                 } else {
919                         LOGGER.debug ("Got HEAT Template from DB: " + heatTemplate.getHeatTemplate());
920                 }
921
922         if (heatEnvironment == null) {
923            String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType;
924                 LOGGER.error (MessageEnum.RA_VNF_UNKNOWN_PARAM, "Heat Environment ID", "OpenStack", "", MsoLogger.ErrorCode.DataError, error);
925                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.DataNotFound, error);
926                 // Alarm on this error, configuration must be fixed
927                 alarmLogger.sendAlarm (MSO_CONFIGURATION_ERROR, MsoAlarmLogger.CRITICAL, error);
928
929                 throw new VnfException (error, MsoExceptionCategory.INTERNAL);
930         } else {
931             LOGGER.debug ("Got Heat Environment from DB: " + heatEnvironment.getEnvironment());
932         }
933
934
935         // Create the combined set of parameters from the incoming request, base-module outputs,
936         // volume-module outputs.  Also, convert all variables to their native object types.
937
938         HashMap<String, Object> goldenInputs = new HashMap<String,Object>();
939         List<String> extraInputs = new ArrayList<String>();
940
941                 Boolean skipInputChecks = false;
942
943                 if (skipInputChecks) {
944                         goldenInputs = new HashMap<String,Object>();
945                         for (String key : inputs.keySet()) {
946                                 goldenInputs.put(key, inputs.get(key));
947                         }
948                 }
949                 else {
950                         // Build maps for the parameters (including aliases) to simplify checks
951                         HashMap<String, HeatTemplateParam> params = new HashMap<String, HeatTemplateParam>();
952
953                         Set<HeatTemplateParam> paramSet = heatTemplate.getParameters();
954                         LOGGER.debug("paramSet has " + paramSet.size() + " entries");
955
956                         for (HeatTemplateParam htp : paramSet) {
957                                 params.put(htp.getParamName(), htp);
958
959                                 // Include aliases.
960                                 String alias = htp.getParamAlias();
961                                 if (alias != null && !alias.equals("") && !params.containsKey(alias)) {
962                                         params.put(alias, htp);
963                                 }
964                         }
965
966                         // First, convert all inputs to their "template" type
967                         for (String key : inputs.keySet()) {
968                                 if (params.containsKey(key)) {
969                                         Object value = convertInputValue(inputs.get(key), params.get(key));
970                                         if (value != null) {
971                                                 goldenInputs.put(key, value);
972                                         }
973                                         else {
974                                                 LOGGER.debug("Failed to convert input " + key + "='" + inputs.get(key) + "' to " + params.get(key).getParamType());
975                                         }
976                                 } else {
977                                         extraInputs.add(key);
978                                 }
979                         }
980
981                         if (!extraInputs.isEmpty()) {
982                                 // Add directive inputs
983                                 String[] directives = { "oof_directives", "sdnc_directives" };
984                                 for (String key : directives) {
985                                         if (extraInputs.contains(key)) {
986                                                 goldenInputs.put(key, inputs.get(key));
987                                                 extraInputs.remove(key);
988                                                 if (extraInputs.isEmpty()) {
989                                                         break;
990                                                 }
991                                         }
992                                 }
993                                 LOGGER.debug("Ignoring extra inputs: " + extraInputs);
994                         }
995
996                         // Next add in Volume Group Outputs if there are any.  Copy directly without conversions.
997                         if (volumeGroupOutputs != null  &&  !volumeGroupOutputs.isEmpty()) {
998                                 for (String key : volumeGroupOutputs.keySet()) {
999                                         if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
1000                                                 goldenInputs.put(key, volumeGroupOutputs.get(key));
1001                                         }
1002                                 }
1003                         }
1004
1005                         // Next add in Base Module Outputs if there are any.  Copy directly without conversions.
1006                         if (baseModuleOutputs != null  &&  !baseModuleOutputs.isEmpty()) {
1007                                 for (String key : baseModuleOutputs.keySet()) {
1008                                         if (params.containsKey(key)  &&  !goldenInputs.containsKey(key)) {
1009                                                 goldenInputs.put(key, baseModuleOutputs.get(key));
1010                                         }
1011                                 }
1012                         }
1013
1014                         // TODO:  The model should support a mechanism to pre-assign default parameter values
1015                         // per "customization" (i.e. usage) of a given module.  In HEAT, this is specified by
1016                         // an Environment file.  There is not a general mechanism in the model to handle this.
1017                         // For the general case, any such parameter/values can be added dynamically to the
1018                         // inputs (only if not already specified).
1019
1020             // Check that required parameters have been supplied from any of the sources
1021             String missingParams = null;
1022             boolean checkRequiredParameters = true;
1023             try {
1024                 String propertyString = this.environment.getProperty(MsoVnfPluginAdapterImpl.CHECK_REQD_PARAMS);
1025                 if ("false".equalsIgnoreCase (propertyString) || "n".equalsIgnoreCase (propertyString)) {
1026                     checkRequiredParameters = false;
1027                     LOGGER.debug ("CheckRequiredParameters is FALSE. Will still check but then skip blocking..."
1028                                   + MsoVnfPluginAdapterImpl.CHECK_REQD_PARAMS);
1029                 }
1030             } catch (Exception e) {
1031                 // No problem - default is true
1032                 LOGGER.debug ("An exception occured trying to get property " + MsoVnfPluginAdapterImpl.CHECK_REQD_PARAMS, e);
1033             }
1034
1035             // Do the actual parameter checking.
1036             // Include looking at the ENV file as a valid definition of a parameter value.
1037             // TODO:  This handling of ENV applies only to Heat.  A general mechanism to
1038             // support pre-set parameter/values does not yet exist in the model.
1039             //
1040                         StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());
1041                         MsoHeatEnvironmentEntry mhee = new MsoHeatEnvironmentEntry (sb);
1042             for (HeatTemplateParam parm : heatTemplate.getParameters ()) {
1043                 if (parm.isRequired () && (!goldenInputs.containsKey (parm.getParamName ()))) {
1044                     if (mhee != null && mhee.containsParameter(parm.getParamName())) {
1045                         LOGGER.debug ("Required parameter " + parm.getParamName ()
1046                                       + " appears to be in environment - do not count as missing");
1047                     } else {
1048                             LOGGER.debug ("adding to missing parameters list: " + parm.getParamName ());
1049                             if (missingParams == null) {
1050                                 missingParams = parm.getParamName ();
1051                             } else {
1052                                 missingParams += "," + parm.getParamName ();
1053                             }
1054                     }
1055                 }
1056             }
1057
1058             if (missingParams != null) {
1059                 if (checkRequiredParameters) {
1060                         // Problem - missing one or more required parameters
1061                         String error = "Create VFModule: Missing Required inputs: " + missingParams;
1062                         LOGGER.error (MessageEnum.RA_MISSING_PARAM, missingParams, "VDU", "", MsoLogger.ErrorCode.DataError, "Create VFModule: Missing Required inputs");
1063                     LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.BadRequest, error);
1064                         throw new VnfException (error, MsoExceptionCategory.USERDATA);
1065                 } else {
1066                         LOGGER.debug ("found missing parameters [" + missingParams + "] - but checkRequiredParameters is false - will not block");
1067                 }
1068             } else {
1069                 LOGGER.debug ("No missing parameters found - ok to proceed");
1070             }
1071
1072                 } // NOTE: END PARAMETER CHECKING
1073
1074
1075                 // Here we go...  ready to deploy the VF Module.
1076         long instantiateVduStartTime = System.currentTimeMillis ();
1077         if (backout == null) backout = true;
1078
1079                 try {
1080                         // Construct the VDU Model structure to pass to the targeted VduPlugin
1081                         VduModelInfo vduModel = null;
1082                         if (! isVolumeRequest) {
1083                                 vduModel = vduMapper.mapVfModuleCustomizationToVdu(vfModuleCust);
1084                         } else {
1085                                 vduModel = vduMapper.mapVfModuleCustVolumeToVdu(vfModuleCust);
1086                         }
1087
1088                         // Invoke the VduPlugin to instantiate the VF Module
1089                         vduInstance = vduPlugin.instantiateVdu(cloudInfo, vfModuleName, goldenInputs, vduModel, backout);
1090
1091             LOGGER.recordMetricEvent (instantiateVduStartTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully received response from VduPlugin", "VDU", "instantiateVdu", vfModuleName);
1092                 }
1093                 catch (VduException me) {
1094             // Failed to instantiate the VDU.
1095             me.addContext ("CreateVFModule");
1096             String error = "Create VF Module " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1097             LOGGER.recordMetricEvent (instantiateVduStartTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "VDU", "instantiateVdu", vfModuleName);
1098             LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, "VDU", "", MsoLogger.ErrorCode.DataError, "MsoException - instantiateVdu", me);
1099             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1100             // Convert to a generic VnfException
1101             throw new VnfException (me);
1102         }
1103             catch (NullPointerException npe) {
1104                 String error = "Create VFModule " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + npe;
1105                 LOGGER.recordMetricEvent (instantiateVduStartTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.InternalError, error, "VDU", "instantiateVdu", vfModuleName);
1106                 LOGGER.error (MessageEnum.RA_CREATE_VNF_ERR, vfModuleType, cloudSiteId, tenantId, "VDU", "", MsoLogger.ErrorCode.DataError, "NullPointerException - instantiateVdu", npe);
1107                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.InternalError, error);
1108                 LOGGER.debug("NULL POINTER EXCEPTION at vduPlugin.instantiateVdu", npe);
1109                 throw new VnfException ("NullPointerException during instantiateVdu");
1110             }
1111                 catch (Exception e) {
1112                 String error = "Create VFModule " + vfModuleType + " in " + cloudSiteId + "/" + tenantId + ": " + e;
1113                 LOGGER.recordMetricEvent (instantiateVduStartTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.UnknownError, error, "VDU", "instantiateVdu", vfModuleName);
1114                 LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.UnknownError, error);
1115                 LOGGER.debug("Unhandled exception at vduPlugin.instantiateVdu", e);
1116                 throw new VnfException("Exception during instantiateVdu: " + e.getMessage());
1117             }
1118
1119
1120         // Reach this point if create is successful.
1121         // Populate remaining rollback info and response parameters.
1122         vfRollback.setVnfCreated (true);
1123         vfRollback.setVnfId (vduInstance.getVduInstanceId());
1124         vnfId.value = vduInstance.getVduInstanceId();
1125         outputs.value = copyStringOutputs (vduInstance.getOutputs ());
1126
1127         rollback.value = vfRollback;
1128
1129         LOGGER.debug ("VF Module " + vfModuleName + " successfully created");
1130         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully create VF Module");
1131         return;
1132     }
1133
1134
1135     public void deleteVfModule (String cloudSiteId,
1136                            String tenantId,
1137                            String vfModuleId,
1138                            MsoRequest msoRequest,
1139                            Holder <Map <String, String>> outputs) throws VnfException
1140     {
1141         MsoLogger.setLogContext (msoRequest);
1142         MsoLogger.setServiceName ("DeleteVfModule");
1143
1144         LOGGER.debug ("Deleting VF Module " + vfModuleId + " in " + cloudSiteId + "/" + tenantId);
1145         // Will capture execution time for metrics
1146         long startTime = System.currentTimeMillis ();
1147
1148         // Capture the output parameters on a delete, so need to query first
1149         VduInstance vduInstance = null;
1150         CloudInfo cloudInfo = new CloudInfo(cloudSiteId, tenantId, null);
1151
1152         // Use the VduPlugin.
1153         VduPlugin vduPlugin = getVduPlugin(cloudSiteId);
1154
1155         try {
1156                 vduInstance = vduPlugin.queryVdu (cloudInfo, vfModuleId);
1157             LOGGER.recordMetricEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully received VDU Query response", "VDU", "QueryVDU", vfModuleId);
1158         }
1159         catch (VduException e) {
1160             // Failed to query the VDU due to a plugin exception.
1161             // Convert to a generic VnfException
1162             e.addContext ("QueryVFModule");
1163             String error = "Query VfModule (VDU): " + vfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + e;
1164             LOGGER.recordMetricEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "VDU", "QueryVNF", vfModuleId);
1165             LOGGER.error (MessageEnum.RA_QUERY_VNF_ERR, vfModuleId, cloudSiteId, tenantId, "VDU", "QueryVFModule", MsoLogger.ErrorCode.DataError, "Exception - queryVDU", e);
1166             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1167             throw new VnfException (e);
1168         }
1169
1170         // call method which handles the conversion from Map<String,Object> to Map<String,String> for our expected Object types
1171         outputs.value = convertMapStringObjectToStringString(vduInstance.getOutputs());
1172
1173         // Use the VduPlugin to delete the VDU.
1174         // The possible outcomes of deleteVdu are
1175         // - a vnfInstance object with status of DELETED (success)
1176         // - a vnfInstance object with status of NOTFOUND (VDU did not exist, treat as success)
1177         // - a vnfInstance object with status of FAILED (error)
1178         // Also, VduException could be thrown.
1179         long subStartTime = System.currentTimeMillis ();
1180         try {
1181                 // TODO:  Get an appropriate timeout value - require access to the model
1182             vduPlugin.deleteVdu(cloudInfo, vfModuleId, 5);
1183             LOGGER.recordMetricEvent (subStartTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully received response from deleteVdu", "VDU", "DeleteVdu", vfModuleId);
1184         } catch (VduException me) {
1185             me.addContext ("DeleteVfModule");
1186             // Convert to a generic VnfException
1187             String error = "Delete VF: " + vfModuleId + " in " + cloudSiteId + "/" + tenantId + ": " + me;
1188             LOGGER.recordMetricEvent (subStartTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error, "DeleteVdu", "DeleteVdu", vfModuleId);
1189             LOGGER.error (MessageEnum.RA_DELETE_VNF_ERR, vfModuleId, cloudSiteId, tenantId, "VDU", "DeleteVdu", MsoLogger.ErrorCode.DataError, "Exception - DeleteVdu: " + me.getMessage());
1190             LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.ERROR, MsoLogger.ResponseCode.CommunicationError, error);
1191             throw new VnfException (me);
1192         }
1193
1194         // On success, nothing is returned.
1195         LOGGER.recordAuditEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, "Successfully delete VF");
1196         return;
1197     }
1198
1199     // Update VF Module not yet implemented for generic VDU plug-in model.
1200     @Override
1201     public void updateVfModule (String cloudSiteId,
1202                            String tenantId,
1203                            String vnfType,
1204                            String vnfVersion,
1205                            String vnfName,
1206                            String requestType,
1207                            String volumeGroupHeatStackId,
1208                            String baseVfHeatStackId,
1209                            String vfModuleStackId,
1210                            String modelCustomizationUuid,
1211                            Map <String, String> inputs,
1212                            MsoRequest msoRequest,
1213                            Holder <Map <String, String>> outputs,
1214                            Holder <VnfRollback> rollback) throws VnfException
1215         {
1216                 // This operation is not currently supported for VduPlugin-orchestrated VF Modules.
1217                 LOGGER.debug ("Update VF Module command attempted but not supported");
1218                 throw new VnfException ("UpdateVfModule:  Unsupported command", MsoExceptionCategory.USERDATA);
1219         }
1220
1221     /*
1222      * Dynamic selection of a VduPlugin version.  For initial tests, base on the "orchestrator"
1223      * defined for the target cloud.  Should really be looking at the VNF Model (ochestration_mode)
1224      * but we don't currently have access to that in Query and Delete cases.
1225      */
1226     private VduPlugin getVduPlugin (String cloudSiteId) {
1227         Optional<CloudSite> cloudSiteOp = cloudConfig.getCloudSite(cloudSiteId);
1228         if (cloudSiteOp.isPresent()) {
1229                 CloudSite cloudSite = cloudSiteOp.get();
1230                 String orchestrator = cloudSite.getOrchestrator();
1231
1232                 if (orchestrator.equalsIgnoreCase("CLOUDIFY")) {
1233                         return cloudifyUtils;
1234                 }
1235                 else if (orchestrator.equalsIgnoreCase("HEAT")) {
1236                         return heatUtils;
1237                 }
1238             if (orchestrator.equalsIgnoreCase("MULTICLOUD")) {
1239                 LOGGER.debug ("Got MulticloudUtils for vduPlugin");
1240                 return multicloudUtils; }
1241         }
1242         // Default - return HEAT plugin, though will fail later
1243         return heatUtils;
1244     }
1245 }