Use checked Exception instead of RuntimeException
[aai/babel.git] / src / main / java / org / onap / aai / babel / parser / ArtifactGeneratorToscaParser.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright (c) 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * Copyright (c) 2017-2019 European Software Marketing Ltd.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.aai.babel.parser;
23
24 import com.google.gson.Gson;
25 import com.google.gson.JsonSyntaxException;
26 import java.io.BufferedReader;
27 import java.io.File;
28 import java.io.FileReader;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Optional;
36 import java.util.function.Predicate;
37 import java.util.stream.Collectors;
38 import java.util.stream.Stream;
39 import org.onap.aai.babel.logging.LogHelper;
40 import org.onap.aai.babel.xml.generator.XmlArtifactGenerationException;
41 import org.onap.aai.babel.xml.generator.data.GroupConfiguration;
42 import org.onap.aai.babel.xml.generator.data.WidgetConfigurationUtil;
43 import org.onap.aai.babel.xml.generator.model.Model;
44 import org.onap.aai.babel.xml.generator.model.Resource;
45 import org.onap.aai.babel.xml.generator.model.Widget;
46 import org.onap.aai.babel.xml.generator.model.WidgetType;
47 import org.onap.aai.babel.xml.generator.types.ModelType;
48 import org.onap.aai.cl.api.Logger;
49 import org.onap.sdc.tosca.parser.api.ISdcCsarHelper;
50 import org.onap.sdc.tosca.parser.impl.SdcPropertyNames;
51 import org.onap.sdc.tosca.parser.utils.SdcToscaUtility;
52 import org.onap.sdc.toscaparser.api.Group;
53 import org.onap.sdc.toscaparser.api.NodeTemplate;
54 import org.onap.sdc.toscaparser.api.Property;
55 import org.onap.sdc.toscaparser.api.SubstitutionMappings;
56 import org.onap.sdc.toscaparser.api.elements.Metadata;
57
58 /**
59  * Wrapper for the sdc-tosca parser.
60  *
61  */
62 public class ArtifactGeneratorToscaParser {
63
64     private static Logger log = LogHelper.INSTANCE;
65
66     public static final String PROPERTY_TOSCA_MAPPING_FILE = "tosca.mappings.config";
67
68     public static final String GENERATOR_AAI_CONFIGLOCATION_NOT_FOUND =
69             "Cannot generate artifacts. System property %s not configured";
70
71     private static final String GENERATOR_AAI_CONFIGFILE_NOT_FOUND =
72             "Cannot generate artifacts. Artifact Generator Configuration file not found at %s";
73     private static final String GENERATOR_AAI_PROVIDING_SERVICE_METADATA_MISSING =
74             "Cannot generate artifacts. Providing Service Metadata is missing for allotted resource %s";
75     private static final String GENERATOR_AAI_PROVIDING_SERVICE_MISSING =
76             "Cannot generate artifacts. Providing Service is missing for allotted resource %s";
77
78     // Metadata properties
79     private static final String CATEGORY = "category";
80     private static final String ALLOTTED_RESOURCE = "Allotted Resource";
81     private static final String SUBCATEGORY = "subcategory";
82     private static final String TUNNEL_XCONNECT = "Tunnel XConnect";
83
84     private static final String VERSION = "version";
85
86     private ISdcCsarHelper csarHelper;
87
88     /**
89      * Constructs using csarHelper
90      *
91      * @param csarHelper
92      *            The csar helper
93      */
94     public ArtifactGeneratorToscaParser(ISdcCsarHelper csarHelper) {
95         this.csarHelper = csarHelper;
96     }
97
98     /**
99      * Initializes the group filtering and TOSCA to Widget mapping configuration.
100      *
101      * @param configLocation
102      *            the pathname to the JSON mappings file
103      * @throws IOException
104      *             if the file content could not be read successfully
105      */
106     public static void initToscaMappingsConfiguration(String configLocation) throws IOException {
107         log.debug("Getting TOSCA Mappings Configuration");
108         File file = new File(configLocation);
109         if (!file.exists()) {
110             throw new IllegalArgumentException(String.format(GENERATOR_AAI_CONFIGFILE_NOT_FOUND, configLocation));
111         }
112
113         GroupConfiguration config;
114
115         try (BufferedReader bufferedReader = new BufferedReader(new FileReader(configLocation))) {
116             config = new Gson().fromJson(bufferedReader, GroupConfiguration.class);
117         } catch (JsonSyntaxException e) {
118             throw new IOException("Invalid Mappings Configuration " + configLocation, e);
119         }
120
121         if (config == null) {
122             throw new IOException("There is no content for the Mappings Configuration " + configLocation);
123         }
124
125         WidgetConfigurationUtil.setSupportedInstanceGroups(config.getInstanceGroupTypes());
126         WidgetConfigurationUtil.setWidgetTypes(config.getWidgetTypes());
127         WidgetConfigurationUtil.setWidgetMappings(config.getWidgetMappings());
128     }
129
130     /**
131      * Process groups for this service node, according to the defined filter.
132      *
133      * @param resourceModel
134      * @param serviceNodeTemplate
135      * @return resources for which XML Models should be generated
136      * @throws XmlArtifactGenerationException
137      *             if there is no configuration defined for a member Widget of an instance group
138      */
139     public List<Resource> processInstanceGroups(Model resourceModel, NodeTemplate serviceNodeTemplate)
140             throws XmlArtifactGenerationException {
141         List<Resource> resources = new ArrayList<>();
142         if (serviceNodeTemplate.getSubMappingToscaTemplate() != null) {
143             List<Group> serviceGroups = serviceNodeTemplate.getSubMappingToscaTemplate().getGroups();
144             for (Group group : serviceGroups) {
145                 if (WidgetConfigurationUtil.isSupportedInstanceGroup(group.getType())) {
146                     resources.addAll(processInstanceGroup(resourceModel, group.getMemberNodes(),
147                             group.getMetadata().getAllProperties(), group.getProperties()));
148                 }
149             }
150         }
151         return resources;
152     }
153
154     /**
155      * Merge a Map of String values with a Map of TOSCA Property Objects to create a combined Map. If there are
156      * duplicate keys then the TOSCA Property value takes precedence.
157      *
158      * @param stringProps
159      *            initial Map of String property values (e.g. from the TOSCA YAML metadata section)
160      * @param toscaProps
161      *            Map of TOSCA Property Type Object values to merge in (or overwrite)
162      * @return a Map of the property values converted to String
163      */
164     public Map<String, String> mergeProperties(Map<String, String> stringProps, Map<String, Property> toscaProps) {
165         Map<String, String> props = new HashMap<>(stringProps);
166         toscaProps.forEach((key, toscaProp) -> props.put(key,
167                 toscaProp.getValue() == null ? "" : toscaProp.getValue().toString()));
168         return props;
169     }
170
171     public Resource createInstanceGroupModel(Map<String, String> properties) {
172         Resource groupModel = new Resource(WidgetType.valueOf("INSTANCE_GROUP"), true);
173         groupModel.populateModelIdentificationInformation(properties);
174         return groupModel;
175     }
176
177     /**
178      * Add the resource/widget to the specified model.
179      *
180      * @param model
181      * @param relation
182      *            resource or widget model to add
183      * @throws XmlArtifactGenerationException
184      *             if the relation is a widget and there is no configuration defined for the relation's widget type
185      */
186     public void addRelatedModel(final Model model, final Resource relation) throws XmlArtifactGenerationException {
187         if (relation.getModelType() == ModelType.RESOURCE) {
188             model.addResource(relation);
189         } else {
190             model.addWidget(Widget.createWidget(relation.getWidgetType()));
191         }
192     }
193
194     public boolean hasAllottedResource(Map<String, String> metadata) {
195         return ALLOTTED_RESOURCE.equals(metadata.get(CATEGORY));
196     }
197
198     public boolean hasSubCategoryTunnelXConnect(Map<String, String> metadata) {
199         return TUNNEL_XCONNECT.equals(metadata.get(SUBCATEGORY));
200     }
201
202     /**
203      * Process TOSCA Group information for VF Modules.
204      *
205      * @param resources
206      * @param model
207      * @param serviceVfNode
208      *            a VF resource Node Template
209      * @throws XmlArtifactGenerationException
210      *             if the configured widget mappings do not support the widget type of a VF Module
211      */
212     public void processVfModules(List<Resource> resources, Model resourceModel, NodeTemplate serviceVfNode)
213             throws XmlArtifactGenerationException {
214         // Process each VF Group
215         for (Group serviceGroup : getVfModuleGroups(serviceVfNode)) {
216             Model groupModel = Model.getModelFor(serviceGroup.getType());
217             if (groupModel.hasWidgetType("VFMODULE")) {
218                 processVfModule(resources, resourceModel, serviceGroup, serviceVfNode, (Resource) groupModel);
219             }
220         }
221     }
222
223     /**
224      * Implementation taken from the sdc-tosca parser (deprecated method).
225      *
226      * @param serviceVfNode
227      *            a VF resource Node Template
228      * @return all service level VfModule groups with a name matching that of the supplied VF node template
229      */
230     private List<Group> getVfModuleGroups(NodeTemplate serviceVfNode) {
231         String instanceName = SdcToscaUtility.normaliseComponentInstanceName(serviceVfNode.getName());
232
233         return ToscaParser.getServiceLevelGroups(csarHelper).stream()
234                 .filter(group -> "org.openecomp.groups.VfModule".equals(group.getTypeDefinition().getType())
235                         && group.getName().startsWith(instanceName))
236                 .collect(Collectors.toList());
237     }
238
239     /**
240      * Add each of the resources to the specified resourceModel. If the resourceModel type is Allotted Resource then
241      * validate that one of the resources is a Providing Service.
242      *
243      * @param resourceModel
244      *            parent Resource model
245      * @param resourceNodeTemplates
246      *            the child node templates of the resourceModel
247      * @throws XmlArtifactGenerationException
248      *             if the resourceModel is an ALLOTTED_RESOURCE with no Providing Service
249      */
250     public void processResourceModels(Resource resourceModel, List<NodeTemplate> resourceNodeTemplates)
251             throws XmlArtifactGenerationException {
252         boolean foundProvidingService = false;
253
254         for (NodeTemplate resourceNodeTemplate : resourceNodeTemplates) {
255             String nodeTypeName = resourceNodeTemplate.getType();
256             Metadata metadata = resourceNodeTemplate.getMetaData();
257             String metaDataType = Optional.ofNullable(metadata).map(m -> m.getValue("type")).orElse(nodeTypeName);
258             Resource model = Model.getModelFor(nodeTypeName, metaDataType);
259
260             if (metadata != null && hasAllottedResource(metadata.getAllProperties())
261                     && model.hasWidgetType("VSERVER")) {
262                 model = new Resource(WidgetType.valueOf("ALLOTTED_RESOURCE"), false);
263                 Map<String, Object> props = new HashMap<>();
264                 props.put("providingService", true);
265                 model.setProperties(props);
266             }
267
268             foundProvidingService |= processModel(resourceModel, metadata, model, resourceNodeTemplate.getProperties());
269         }
270
271         if (resourceModel.hasWidgetType("ALLOTTED_RESOURCE") && !foundProvidingService) {
272             throw new XmlArtifactGenerationException(String.format(GENERATOR_AAI_PROVIDING_SERVICE_MISSING,
273                     Optional.ofNullable(resourceModel.getModelId()).orElse("<null ID>")));
274         }
275     }
276
277     /**
278      * Create an Instance Group Model and populate it with the supplied data.
279      *
280      * @param resourceModel
281      *            the Resource node template Model
282      * @param memberNodes
283      *            the Resources and Widgets belonging to the Group
284      * @param metaProperties
285      *            the metadata of the Group
286      * @param properties
287      *            the properties of the Group
288      * @return the Instance Group and Member resource models
289      * @throws XmlArtifactGenerationException
290      *             if there is no configuration defined for one of the member Widgets
291      */
292     private List<Resource> processInstanceGroup(Model resourceModel, ArrayList<NodeTemplate> memberNodes,
293             Map<String, String> metaProperties, Map<String, Property> properties)
294             throws XmlArtifactGenerationException {
295         Resource groupModel = createInstanceGroupModel(mergeProperties(metaProperties, properties));
296         resourceModel.addResource(groupModel);
297         List<Resource> resources = Stream.of(groupModel).collect(Collectors.toList());
298
299         if (memberNodes != null && !memberNodes.isEmpty()) {
300             resources.addAll(generateResourcesAndWidgets(memberNodes, groupModel));
301         }
302
303         return resources;
304     }
305
306     /**
307      * @param memberNodes
308      * @param groupModel
309      * @return a list of Resources
310      * @throws XmlArtifactGenerationException
311      *             if a member node template is a widget and there is no configuration defined for that relation's
312      *             widget type
313      */
314     private List<Resource> generateResourcesAndWidgets(final ArrayList<NodeTemplate> memberNodes,
315             final Resource groupModel) throws XmlArtifactGenerationException {
316         log.debug(String.format("Processing member nodes for Group %s (invariant UUID %s)", //
317                 groupModel.getModelName(), groupModel.getModelId()));
318
319         List<Resource> resources = new ArrayList<>();
320
321         for (NodeTemplate nodeTemplate : memberNodes) {
322             String nodeTypeName = nodeTemplate.getType();
323             final String metadataType = nodeTemplate.getMetaData().getValue("type");
324
325             log.debug(String.format("Get model for %s (metadata type %s)", nodeTypeName, metadataType));
326             Resource memberModel = Model.getModelFor(nodeTypeName, metadataType);
327
328             if (memberModel != null) {
329                 memberModel.populateModelIdentificationInformation(nodeTemplate.getMetaData().getAllProperties());
330
331                 log.debug(String.format("Generating grouped %s (%s) from TOSCA type %s",
332                         memberModel.getClass().getSuperclass().getSimpleName(), memberModel.getClass(), nodeTypeName));
333
334                 addRelatedModel(groupModel, memberModel);
335                 if (memberModel.getModelType() == ModelType.RESOURCE) {
336                     resources.add(memberModel);
337                 }
338             }
339         }
340         return resources;
341     }
342
343     /**
344      * @param resources
345      * @param vfModel
346      * @param groupDefinition
347      * @param serviceNode
348      * @param groupModel
349      * @throws XmlArtifactGenerationException
350      *             if the configured widget mappings do not support the widget type of a VF Module
351      */
352     private void processVfModule(List<Resource> resources, Model vfModel, Group groupDefinition,
353             NodeTemplate serviceNode, Resource groupModel) throws XmlArtifactGenerationException {
354         groupModel.populateModelIdentificationInformation(
355                 mergeProperties(groupDefinition.getMetadata().getAllProperties(), groupDefinition.getProperties()));
356
357
358         SubstitutionMappings substitutionMappings = serviceNode.getSubMappingToscaTemplate();
359         if (substitutionMappings != null) {
360             processVfModuleGroup(groupModel, getVfModuleMembers(substitutionMappings,
361                     groupDefinition.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)));
362         }
363
364         vfModel.addResource(groupModel); // Add group (VfModule) to the (VF) model
365         // Check if we have already encountered the same VfModule across all the artifacts
366         if (!resources.contains(groupModel)) {
367             resources.add(groupModel);
368         }
369     }
370
371     /**
372      * @param substitutionMappings
373      * @param vfModuleInvariantUuid
374      * @return all serviceNode child Node Templates which are members of the first VF Module Group
375      */
376     private List<NodeTemplate> getVfModuleMembers(SubstitutionMappings substitutionMappings,
377             String vfModuleInvariantUuid) {
378         return Optional.ofNullable(substitutionMappings.getGroups()) //
379                 .map(groups -> groups.stream() //
380                         .filter(filterByVfModuleInvariantUuid(vfModuleInvariantUuid)) //
381                         .findFirst().map(module -> Optional.ofNullable(module.getMembers()).orElse(new ArrayList<>()))
382                         .orElse(new ArrayList<>()))
383                 .map(members -> substitutionMappings.getNodeTemplates().stream()
384                         .filter(nt -> members.contains(nt.getName())) //
385                         .collect(Collectors.toList()))
386                 .orElse(Collections.emptyList());
387     }
388
389     private Predicate<? super Group> filterByVfModuleInvariantUuid(String vfModuleInvariantUuid) {
390         return nt -> (nt.getMetadata() != null && vfModuleInvariantUuid
391                 .equals(nt.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)));
392     }
393
394     /**
395      * @param groupModel
396      * @param members
397      * @throws XmlArtifactGenerationException
398      *             if the configured widget mappings do not support the widget type of a member
399      */
400     private void processVfModuleGroup(Resource groupModel, List<NodeTemplate> members)
401             throws XmlArtifactGenerationException {
402         if (members != null && !members.isEmpty()) {
403             // Get names of the members of the service group
404             List<String> memberNames = members.stream().map(NodeTemplate::getName).collect(Collectors.toList());
405             groupModel.setMembers(memberNames);
406             for (NodeTemplate member : members) {
407                 processGroupMembers(groupModel, member);
408             }
409         }
410     }
411
412     /**
413      * Process the Widget members of a VF Module Group
414      *
415      * @param group
416      *            the group resource model
417      * @param member
418      *            the group member to process
419      * @throws XmlArtifactGenerationException
420      *             if the configured widget mappings do not support the widget type of the member
421      */
422     private void processGroupMembers(Resource group, NodeTemplate member) throws XmlArtifactGenerationException {
423         Resource resource = Model.getModelFor(member.getType());
424
425         log.debug(member.getType() + " mapped to " + resource);
426
427         if (resource.hasWidgetType("L3_NET")) {
428             // An l3-network inside a vf-module is treated as a Widget
429             resource.setModelType(ModelType.WIDGET);
430         }
431
432         if (resource.getModelType() == ModelType.WIDGET) {
433             Widget widget = Widget.createWidget(resource.getWidgetType());
434             widget.addKey(member.getName());
435             // Add the widget element encountered to the Group model
436             group.addWidget(widget);
437         }
438     }
439
440     /**
441      * Create a Map of property name against String property value from the input Map
442      *
443      * @param inputMap
444      *            The input Map
445      * @return Map of property name against String property value
446      */
447     private Map<String, String> populateStringProperties(Map<String, Property> inputMap) {
448         return inputMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
449                 e -> e.getValue().getValue() == null ? "" : e.getValue().getValue().toString()));
450     }
451
452     /**
453      * If the specified resourceNode is a type of Resource, add it to the specified resourceModel. If the Resource type
454      * is ProvidingService then return true, otherwise return false.
455      *
456      * @param resourceModel
457      *            parent Resource
458      * @param metaData
459      *            for populating the Resource IDs
460      * @param resourceNode
461      *            any Model (will be ignored if not a Resource)
462      * @param nodeProperties
463      *            the node properties
464      * @return whether or not a ProvidingService was processed
465      */
466     private boolean processModel(Model resourceModel, Metadata metaData, Resource resourceNode,
467             Map<String, Property> nodeProperties) {
468         boolean foundProvidingService = resourceNode != null
469                 && (boolean) Optional.ofNullable(resourceNode.getProperties().get("providingService")).orElse(false);
470
471         if (foundProvidingService) {
472             processProvidingService(resourceModel, resourceNode, nodeProperties);
473         } else if (resourceNode != null && resourceNode.getModelType() == ModelType.RESOURCE
474                 && !resourceNode.hasWidgetType("L3_NET")) {
475             if (metaData != null) {
476                 resourceNode.populateModelIdentificationInformation(metaData.getAllProperties());
477             }
478             resourceModel.addResource(resourceNode);
479         }
480         return foundProvidingService;
481     }
482
483     private void processProvidingService(Model resourceModel, Resource resourceNode,
484             Map<String, Property> nodeProperties) {
485         if (nodeProperties == null || nodeProperties.get("providing_service_uuid") == null
486                 || nodeProperties.get("providing_service_invariant_uuid") == null) {
487             throw new IllegalArgumentException(
488                     String.format(GENERATOR_AAI_PROVIDING_SERVICE_METADATA_MISSING, resourceModel.getModelId()));
489         }
490         Map<String, String> properties = populateStringProperties(nodeProperties);
491         properties.put(VERSION, "1.0");
492         resourceNode.populateModelIdentificationInformation(properties);
493         resourceModel.addResource(resourceNode);
494     }
495 }