Depend on sdc-tosca version 1.5.0
[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      * @param resourceModel
241      * @param resourceNodeTemplates
242      */
243     public void processResourceModels(Model resourceModel, List<NodeTemplate> resourceNodeTemplates) {
244         boolean foundProvidingService = false;
245
246         for (NodeTemplate resourceNodeTemplate : resourceNodeTemplates) {
247             String nodeTypeName = resourceNodeTemplate.getType();
248             Metadata metadata = resourceNodeTemplate.getMetaData();
249             String metaDataType = Optional.ofNullable(metadata).map(m -> m.getValue("type")).orElse(nodeTypeName);
250             Resource model = Model.getModelFor(nodeTypeName, metaDataType);
251
252             if (metadata != null && hasAllottedResource(metadata.getAllProperties())
253                     && model.hasWidgetType("VSERVER")) {
254                 model = new Resource(WidgetType.valueOf("ALLOTTED_RESOURCE"), false);
255                 Map<String, Object> props = new HashMap<>();
256                 props.put("providingService", true);
257                 model.setProperties(props);
258             }
259
260             foundProvidingService |= processModel(resourceModel, metadata, model, resourceNodeTemplate.getProperties());
261         }
262
263         if (resourceModel.hasWidgetType("ALLOTTED_RESOURCE") && !foundProvidingService) {
264             final String modelInvariantId = resourceModel.getModelId();
265             throw new IllegalArgumentException(String.format(GENERATOR_AAI_PROVIDING_SERVICE_MISSING,
266                     modelInvariantId == null ? "<null ID>" : modelInvariantId));
267         }
268     }
269
270     /**
271      * Create an Instance Group Model and populate it with the supplied data.
272      *
273      * @param resourceModel
274      *            the Resource node template Model
275      * @param memberNodes
276      *            the Resources and Widgets belonging to the Group
277      * @param metaProperties
278      *            the metadata of the Group
279      * @param properties
280      *            the properties of the Group
281      * @return the Instance Group and Member resource models
282      * @throws XmlArtifactGenerationException
283      *             if there is no configuration defined for one of the member Widgets
284      */
285     private List<Resource> processInstanceGroup(Model resourceModel, ArrayList<NodeTemplate> memberNodes,
286             Map<String, String> metaProperties, Map<String, Property> properties)
287             throws XmlArtifactGenerationException {
288         Resource groupModel = createInstanceGroupModel(mergeProperties(metaProperties, properties));
289         resourceModel.addResource(groupModel);
290         List<Resource> resources = Stream.of(groupModel).collect(Collectors.toList());
291
292         if (memberNodes != null && !memberNodes.isEmpty()) {
293             resources.addAll(generateResourcesAndWidgets(memberNodes, groupModel));
294         }
295
296         return resources;
297     }
298
299     /**
300      * @param memberNodes
301      * @param groupModel
302      * @return a list of Resources
303      * @throws XmlArtifactGenerationException
304      *             if a member node template is a widget and there is no configuration defined for that relation's
305      *             widget type
306      */
307     private List<Resource> generateResourcesAndWidgets(final ArrayList<NodeTemplate> memberNodes,
308             final Resource groupModel) throws XmlArtifactGenerationException {
309         log.debug(String.format("Processing member nodes for Group %s (invariant UUID %s)", //
310                 groupModel.getModelName(), groupModel.getModelId()));
311
312         List<Resource> resources = new ArrayList<>();
313
314         for (NodeTemplate nodeTemplate : memberNodes) {
315             String nodeTypeName = nodeTemplate.getType();
316             final String metadataType = nodeTemplate.getMetaData().getValue("type");
317
318             log.debug(String.format("Get model for %s (metadata type %s)", nodeTypeName, metadataType));
319             Resource memberModel = Model.getModelFor(nodeTypeName, metadataType);
320
321             if (memberModel != null) {
322                 memberModel.populateModelIdentificationInformation(nodeTemplate.getMetaData().getAllProperties());
323
324                 log.debug(String.format("Generating grouped %s (%s) from TOSCA type %s",
325                         memberModel.getClass().getSuperclass().getSimpleName(), memberModel.getClass(), nodeTypeName));
326
327                 addRelatedModel(groupModel, memberModel);
328                 if (memberModel.getModelType() == ModelType.RESOURCE) {
329                     resources.add(memberModel);
330                 }
331             }
332         }
333         return resources;
334     }
335
336     /**
337      * @param resources
338      * @param vfModel
339      * @param groupDefinition
340      * @param serviceNode
341      * @param groupModel
342      * @throws XmlArtifactGenerationException
343      *             if the configured widget mappings do not support the widget type of a VF Module
344      */
345     private void processVfModule(List<Resource> resources, Model vfModel, Group groupDefinition,
346             NodeTemplate serviceNode, Resource groupModel) throws XmlArtifactGenerationException {
347         groupModel.populateModelIdentificationInformation(
348                 mergeProperties(groupDefinition.getMetadata().getAllProperties(), groupDefinition.getProperties()));
349
350
351         SubstitutionMappings substitutionMappings = serviceNode.getSubMappingToscaTemplate();
352         if (substitutionMappings != null) {
353             processVfModuleGroup(groupModel, getVfModuleMembers(substitutionMappings,
354                     groupDefinition.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)));
355         }
356
357         vfModel.addResource(groupModel); // Add group (VfModule) to the (VF) model
358         // Check if we have already encountered the same VfModule across all the artifacts
359         if (!resources.contains(groupModel)) {
360             resources.add(groupModel);
361         }
362     }
363
364     /**
365      * @param substitutionMappings
366      * @param vfModuleInvariantUuid
367      * @return all serviceNode child Node Templates which are members of the first VF Module Group
368      */
369     private List<NodeTemplate> getVfModuleMembers(SubstitutionMappings substitutionMappings,
370             String vfModuleInvariantUuid) {
371         return Optional.ofNullable(substitutionMappings.getGroups()) //
372                 .map(groups -> groups.stream() //
373                         .filter(filterByVfModuleInvariantUuid(vfModuleInvariantUuid)) //
374                         .findFirst().map(module -> Optional.ofNullable(module.getMembers()).orElse(new ArrayList<>()))
375                         .orElse(new ArrayList<>()))
376                 .map(members -> substitutionMappings.getNodeTemplates().stream()
377                         .filter(nt -> members.contains(nt.getName())) //
378                         .collect(Collectors.toList()))
379                 .orElse(Collections.emptyList());
380     }
381
382     private Predicate<? super Group> filterByVfModuleInvariantUuid(String vfModuleInvariantUuid) {
383         return nt -> (nt.getMetadata() != null && vfModuleInvariantUuid
384                 .equals(nt.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)));
385     }
386
387     /**
388      * @param groupModel
389      * @param members
390      * @throws XmlArtifactGenerationException
391      *             if the configured widget mappings do not support the widget type of a member
392      */
393     private void processVfModuleGroup(Resource groupModel, List<NodeTemplate> members)
394             throws XmlArtifactGenerationException {
395         if (members != null && !members.isEmpty()) {
396             // Get names of the members of the service group
397             List<String> memberNames = members.stream().map(NodeTemplate::getName).collect(Collectors.toList());
398             groupModel.setMembers(memberNames);
399             for (NodeTemplate member : members) {
400                 processGroupMembers(groupModel, member);
401             }
402         }
403     }
404
405     /**
406      * Process the Widget members of a VF Module Group
407      *
408      * @param group
409      *            the group resource model
410      * @param member
411      *            the group member to process
412      * @throws XmlArtifactGenerationException
413      *             if the configured widget mappings do not support the widget type of the member
414      */
415     private void processGroupMembers(Resource group, NodeTemplate member) throws XmlArtifactGenerationException {
416         Resource resource = Model.getModelFor(member.getType());
417
418         log.debug(member.getType() + " mapped to " + resource);
419
420         if (resource.hasWidgetType("L3_NET")) {
421             // An l3-network inside a vf-module is treated as a Widget
422             resource.setModelType(ModelType.WIDGET);
423         }
424
425         if (resource.getModelType() == ModelType.WIDGET) {
426             Widget widget = Widget.createWidget(resource.getWidgetType());
427             widget.addKey(member.getName());
428             // Add the widget element encountered to the Group model
429             group.addWidget(widget);
430         }
431     }
432
433     /**
434      * Create a Map of property name against String property value from the input Map
435      *
436      * @param inputMap
437      *            The input Map
438      * @return Map of property name against String property value
439      */
440     private Map<String, String> populateStringProperties(Map<String, Property> inputMap) {
441         return inputMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
442                 e -> e.getValue().getValue() == null ? "" : e.getValue().getValue().toString()));
443     }
444
445     /**
446      * If the specified resourceNode is a type of Resource, add it to the specified resourceModel. If the Resource type
447      * is ProvidingService then return true, otherwise return false.
448      *
449      * @param resourceModel
450      *            parent Resource
451      * @param metaData
452      *            for populating the Resource IDs
453      * @param resourceNode
454      *            any Model (will be ignored if not a Resource)
455      * @param nodeProperties
456      *            the node properties
457      * @return whether or not a ProvidingService was processed
458      */
459     private boolean processModel(Model resourceModel, Metadata metaData, Resource resourceNode,
460             Map<String, Property> nodeProperties) {
461         boolean foundProvidingService = resourceNode != null
462                 && (boolean) Optional.ofNullable(resourceNode.getProperties().get("providingService")).orElse(false);
463
464         if (foundProvidingService) {
465             processProvidingService(resourceModel, resourceNode, nodeProperties);
466         } else if (resourceNode != null && resourceNode.getModelType() == ModelType.RESOURCE
467                 && !resourceNode.hasWidgetType("L3_NET")) {
468             if (metaData != null) {
469                 resourceNode.populateModelIdentificationInformation(metaData.getAllProperties());
470             }
471             resourceModel.addResource(resourceNode);
472         }
473         return foundProvidingService;
474     }
475
476     private void processProvidingService(Model resourceModel, Resource resourceNode,
477             Map<String, Property> nodeProperties) {
478         if (nodeProperties == null || nodeProperties.get("providing_service_uuid") == null
479                 || nodeProperties.get("providing_service_invariant_uuid") == null) {
480             throw new IllegalArgumentException(
481                     String.format(GENERATOR_AAI_PROVIDING_SERVICE_METADATA_MISSING, resourceModel.getModelId()));
482         }
483         Map<String, String> properties = populateStringProperties(nodeProperties);
484         properties.put(VERSION, "1.0");
485         resourceNode.populateModelIdentificationInformation(properties);
486         resourceModel.addResource(resourceNode);
487     }
488 }