Additional null checks and comments
[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         Metadata metadata = groupDefinition.getMetadata();
355
356         Map<String, String> mergedProperties =
357                 mergeProperties(metadata == null ? Collections.emptyMap() : metadata.getAllProperties(),
358                         groupDefinition.getProperties());
359
360         groupModel.populateModelIdentificationInformation(mergedProperties);
361         SubstitutionMappings substitutionMappings = serviceNode.getSubMappingToscaTemplate();
362         if (substitutionMappings != null) {
363             processVfModuleGroup(groupModel, getVfModuleMembers(substitutionMappings,
364                     groupDefinition.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)));
365         }
366
367         vfModel.addResource(groupModel); // Add group (VfModule) to the (VF) model
368         // Check if we have already encountered the same VfModule across all the artifacts
369         if (!resources.contains(groupModel)) {
370             resources.add(groupModel);
371         }
372     }
373
374     /**
375      * @param substitutionMappings
376      * @param vfModuleInvariantUuid
377      * @return all serviceNode child Node Templates which are members of the first VF Module Group
378      */
379     private List<NodeTemplate> getVfModuleMembers(SubstitutionMappings substitutionMappings,
380             String vfModuleInvariantUuid) {
381         return Optional.ofNullable(substitutionMappings.getGroups()) //
382                 .map(groups -> groups.stream() //
383                         .filter(filterByVfModuleInvariantUuid(vfModuleInvariantUuid)) //
384                         .findFirst().map(module -> Optional.ofNullable(module.getMembers()).orElse(new ArrayList<>()))
385                         .orElse(new ArrayList<>()))
386                 .map(members -> substitutionMappings.getNodeTemplates().stream()
387                         .filter(nt -> members.contains(nt.getName())) //
388                         .collect(Collectors.toList()))
389                 .orElse(Collections.emptyList());
390     }
391
392     private Predicate<? super Group> filterByVfModuleInvariantUuid(String vfModuleInvariantUuid) {
393         return nt -> (nt.getMetadata() != null && vfModuleInvariantUuid
394                 .equals(nt.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)));
395     }
396
397     /**
398      * @param groupModel
399      * @param members
400      * @throws XmlArtifactGenerationException
401      *             if the configured widget mappings do not support the widget type of a member
402      */
403     private void processVfModuleGroup(Resource groupModel, List<NodeTemplate> members)
404             throws XmlArtifactGenerationException {
405         if (members != null && !members.isEmpty()) {
406             // Get names of the members of the service group
407             List<String> memberNames = members.stream().map(NodeTemplate::getName).collect(Collectors.toList());
408             groupModel.setMembers(memberNames);
409             for (NodeTemplate member : members) {
410                 processGroupMembers(groupModel, member);
411             }
412         }
413     }
414
415     /**
416      * Process the Widget members of a VF Module Group
417      *
418      * @param group
419      *            the group resource model
420      * @param member
421      *            the group member to process
422      * @throws XmlArtifactGenerationException
423      *             if the configured widget mappings do not support the widget type of the member
424      */
425     private void processGroupMembers(Resource group, NodeTemplate member) throws XmlArtifactGenerationException {
426         Resource resource = Model.getModelFor(member.getType());
427
428         log.debug(member.getType() + " mapped to " + resource);
429
430         if (resource.hasWidgetType("L3_NET")) {
431             // An l3-network inside a vf-module is treated as a Widget
432             resource.setModelType(ModelType.WIDGET);
433         }
434
435         if (resource.getModelType() == ModelType.WIDGET) {
436             Widget widget = Widget.createWidget(resource.getWidgetType());
437             widget.addKey(member.getName());
438             // Add the widget element encountered to the Group model
439             group.addWidget(widget);
440         }
441     }
442
443     /**
444      * Create a Map of property name against String property value from the input Map
445      *
446      * @param inputMap
447      *            The input Map
448      * @return Map of property name against String property value
449      */
450     private Map<String, String> populateStringProperties(Map<String, Property> inputMap) {
451         return inputMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
452                 e -> e.getValue().getValue() == null ? "" : e.getValue().getValue().toString()));
453     }
454
455     /**
456      * If the specified resourceNode is a type of Resource, add it to the specified resourceModel. If the Resource type
457      * is ProvidingService then return true, otherwise return false.
458      *
459      * @param resourceModel
460      *            parent Resource
461      * @param metaData
462      *            for populating the Resource IDs
463      * @param childResource
464      *            a child Resource (will be ignored if this is a Widget type)
465      * @param nodeProperties
466      *            the node properties
467      * @return whether or not a ProvidingService was processed
468      */
469     private boolean processModel(Model resourceModel, Metadata metaData, Resource childResource,
470             Map<String, Property> nodeProperties) {
471         boolean isProvidingService = childResource != null
472                 && (boolean) Optional.ofNullable(childResource.getProperties().get("providingService")).orElse(false);
473
474         if (isProvidingService) {
475             processProvidingService(resourceModel, childResource, nodeProperties);
476         } else if (childResource != null && childResource.getModelType() == ModelType.RESOURCE
477                 && !childResource.hasWidgetType("L3_NET")) {
478             if (metaData != null) {
479                 childResource.populateModelIdentificationInformation(metaData.getAllProperties());
480             }
481             resourceModel.addResource(childResource);
482         }
483         return isProvidingService;
484     }
485
486     /**
487      * @param resourceModel
488      * @param resourceNode
489      * @param nodeProperties
490      */
491     private void processProvidingService(Model resourceModel, Resource resourceNode,
492             Map<String, Property> nodeProperties) {
493         if (nodeProperties == null || nodeProperties.get("providing_service_uuid") == null
494                 || nodeProperties.get("providing_service_invariant_uuid") == null) {
495             throw new IllegalArgumentException(
496                     String.format(GENERATOR_AAI_PROVIDING_SERVICE_METADATA_MISSING, resourceModel.getModelId()));
497         }
498         Map<String, String> properties = populateStringProperties(nodeProperties);
499         properties.put(VERSION, "1.0");
500         resourceNode.populateModelIdentificationInformation(properties);
501         resourceModel.addResource(resourceNode);
502     }
503 }