Add substitution_mapping attributes mapping
[sdc.git] / catalog-be-plugins / etsi-nfv-nsd-csar-plugin / src / main / java / org / openecomp / sdc / be / plugins / etsi / nfv / nsd / generator / NsDescriptorGeneratorImpl.java
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2020 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  *
16  *  SPDX-License-Identifier: Apache-2.0
17  *  ============LICENSE_END=========================================================
18  */
19
20 package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator;
21
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableMap;
24 import fj.data.Either;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Optional;
34 import org.apache.commons.collections4.CollectionUtils;
35 import org.apache.commons.collections4.MapUtils;
36 import org.openecomp.sdc.be.config.ConfigurationManager;
37 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
38 import org.openecomp.sdc.be.model.Component;
39 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintType;
40 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
41 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
42 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
43 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.tosca.yaml.ToscaTemplateYamlGenerator;
44 import org.openecomp.sdc.be.tosca.ToscaError;
45 import org.openecomp.sdc.be.tosca.ToscaExportHandler;
46 import org.openecomp.sdc.be.tosca.model.SubstitutionMapping;
47 import org.openecomp.sdc.be.tosca.model.ToscaNodeTemplate;
48 import org.openecomp.sdc.be.tosca.model.ToscaNodeType;
49 import org.openecomp.sdc.be.tosca.model.ToscaProperty;
50 import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraint;
51 import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraintValidValues;
52 import org.openecomp.sdc.be.tosca.model.ToscaTemplate;
53 import org.openecomp.sdc.be.tosca.model.ToscaTopolgyTemplate;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56 import org.springframework.beans.factory.ObjectProvider;
57
58 @org.springframework.stereotype.Component("nsDescriptorGenerator")
59 public class NsDescriptorGeneratorImpl implements NsDescriptorGenerator {
60
61     private static final Logger LOGGER = LoggerFactory.getLogger(NsDescriptorGeneratorImpl.class);
62     private static final String TOSCA_VERSION = "tosca_simple_yaml_1_1";
63     private static final String NS_TOSCA_TYPE = "tosca.nodes.nfv.NS";
64     private static final List<Map<String, Map<String, String>>> DEFAULT_IMPORTS_ETSI_SOL_NSD =
65         ImmutableList.of(
66             ImmutableMap.of("etsi_nfv_sol001_nsd_2_7_1_types",
67                 ImmutableMap.of("file", "etsi_nfv_sol001_nsd_2_7_1_types.yaml")
68             )
69         );
70     private static final List<Map<String, Map<String, String>>> DEFAULT_IMPORTS = ConfigurationManager
71         .getConfigurationManager().getConfiguration().getDefaultImports();
72     private static final List<String> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE = Arrays
73         .asList("cds_model_name", "cds_model_version", "skip_post_instantiation_configuration", "controller_actor");
74     private static final List<String> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE = Arrays
75         .asList("nf_function", "nf_role", "nf_naming_code", "nf_type", "nf_naming", "availability_zone_max_count",
76             "min_instances", "max_instances", "multi_stage_design", "sdnc_model_name", "sdnc_model_version",
77             "sdnc_artifact_name", "skip_post_instantiation_configuration", "controller_actor");
78
79     private final ToscaExportHandler toscaExportHandler;
80     private final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider;
81
82     public NsDescriptorGeneratorImpl(final ToscaExportHandler toscaExportHandler,
83                                      final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider) {
84         this.toscaExportHandler = toscaExportHandler;
85         this.toscaTemplateYamlGeneratorProvider = toscaTemplateYamlGeneratorProvider;
86     }
87
88     public Optional<Nsd> generate(final Component component,
89                                   final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
90         if (!ComponentTypeEnum.SERVICE.equals(component.getComponentType())) {
91             return Optional.empty();
92         }
93
94         final ToscaTemplate toscaTemplate = createNetworkServiceDescriptor(component, vnfDescriptorList);
95         final ToscaNodeType nsNodeType = toscaTemplate.getNode_types().values().stream()
96             .filter(toscaNodeType -> NS_TOSCA_TYPE.equals(toscaNodeType.getDerived_from())).findFirst().orElse(null);
97         if (nsNodeType == null) {
98             return Optional.empty();
99         }
100
101         return Optional.of(buildNsd(toscaTemplate, nsNodeType));
102     }
103
104     private Nsd buildNsd(final ToscaTemplate toscaTemplate, final ToscaNodeType nsNodeType) {
105         final Nsd nsd = new Nsd();
106         nsd.setDesigner(getProperty(nsNodeType, Nsd.DESIGNER_PROPERTY));
107         nsd.setVersion(getProperty(nsNodeType, Nsd.VERSION_PROPERTY));
108         nsd.setName(getProperty(nsNodeType, Nsd.NAME_PROPERTY));
109         nsd.setInvariantId(getProperty(nsNodeType, Nsd.INVARIANT_ID_PROPERTY));
110         final ToscaTemplateYamlGenerator yamlParserProvider =
111             toscaTemplateYamlGeneratorProvider.getObject(toscaTemplate);
112         final byte[] contents = yamlParserProvider.parseToYamlString().getBytes();
113         nsd.setContents(contents);
114         final List<String> interfaceImplementations = getInterfaceImplementations(toscaTemplate);
115         nsd.setArtifactReferences(interfaceImplementations);
116         return nsd;
117     }
118
119     private List<String> getInterfaceImplementations(final ToscaTemplate template) {
120         if (template.getTopology_template().getNode_templates() == null) {
121             return Collections.emptyList();
122         }
123         final List<String> interfaceImplementations = new ArrayList<>();
124         final Collection<ToscaNodeTemplate> nodeTemplates =
125             template.getTopology_template().getNode_templates().values();
126         nodeTemplates.stream()
127             .filter(toscaNodeTemplate -> toscaNodeTemplate.getInterfaces() != null)
128             .forEach(toscaNodeTemplate ->
129                 toscaNodeTemplate.getInterfaces().values().forEach(interfaceInstance ->
130                     interfaceImplementations.addAll(getInterfaceImplementations(interfaceInstance))
131             ));
132         return interfaceImplementations;
133     }
134
135     private Collection<String> getInterfaceImplementations(final Object interfaceInstance) {
136         final Collection<String> interfaceImplementations = new ArrayList<>();
137         if (interfaceInstance instanceof Map) {
138             for (final Object value : ((Map<?, ?>) interfaceInstance).values()) {
139                 if (value instanceof Map && ((Map<?, ?>) value).get("implementation") != null) {
140                     interfaceImplementations.add(((Map<?, ?>) value).get("implementation").toString());
141                 }
142             }
143         }
144         return interfaceImplementations;
145     }
146
147     private String getProperty(final ToscaNodeType nodeType, final String propertyName) {
148         final ToscaProperty toscaProperty = nodeType.getProperties().get(propertyName);
149
150         final String errorMsg =
151             String.format("Property '%s' must be defined and must have a valid values constraint", propertyName);
152         final String returnValueOnError = "unknown";
153         if (toscaProperty == null || CollectionUtils.isEmpty(toscaProperty.getConstraints())) {
154             LOGGER.error(errorMsg);
155             return returnValueOnError;
156         }
157
158         final ToscaPropertyConstraint toscaPropertyConstraint = toscaProperty.getConstraints().get(0);
159         if (ConstraintType.VALID_VALUES != toscaPropertyConstraint.getConstraintType()) {
160             LOGGER.error(errorMsg);
161             return returnValueOnError;
162         }
163
164         final ToscaPropertyConstraintValidValues validValuesConstraint =
165             (ToscaPropertyConstraintValidValues) toscaPropertyConstraint;
166         final List<String> validValues = validValuesConstraint.getValidValues();
167         if(CollectionUtils.isEmpty(validValues)) {
168             LOGGER.error(errorMsg);
169             return returnValueOnError;
170         }
171
172         return validValues.get(0);
173     }
174
175     private ToscaTemplate createNetworkServiceDescriptor(final Component component,
176                                                         final List<VnfDescriptor> vnfDescriptorList)
177         throws NsdException {
178
179         final ToscaTemplate componentToscaTemplate = parseToToscaTemplate(component);
180         final ToscaTemplate componentToscaTemplateInterface = exportComponentInterfaceAsToscaTemplate(component);
181
182         final Entry<String, ToscaNodeType> firstNodeTypeEntry =
183             componentToscaTemplateInterface.getNode_types()
184                 .entrySet().stream().findFirst().orElse(null);
185         if (firstNodeTypeEntry == null) {
186             throw new NsdException("Could not find abstract Service type");
187         }
188
189         final String nsNodeTypeName = firstNodeTypeEntry.getKey();
190         final ToscaNodeType nsNodeType = firstNodeTypeEntry.getValue();
191
192         final Map<String, ToscaNodeType> nodeTypeMap = new HashMap<>();
193         nodeTypeMap.put(nsNodeTypeName, createEtsiSolNsNodeType(nsNodeType));
194
195         if (componentToscaTemplate.getNode_types() == null) {
196             componentToscaTemplate.setNode_types(nodeTypeMap);
197         } else {
198             componentToscaTemplate.getNode_types().putAll(nodeTypeMap);
199         }
200
201         setPropertiesForNodeTemplates(componentToscaTemplate);
202         removeCapabilitiesFromNodeTemplates(componentToscaTemplate);
203         removeOnapPropertiesFromInputs(componentToscaTemplate);
204         handleSubstitutionMappings(componentToscaTemplate, nsNodeTypeName);
205
206         final Map<String, ToscaNodeTemplate> nodeTemplates = new HashMap<>();
207         nodeTemplates.put(nsNodeTypeName, createNodeTemplateForNsNodeType(nsNodeTypeName,
208             componentToscaTemplateInterface.getNode_types().get(nsNodeTypeName)));
209
210         if (componentToscaTemplate.getTopology_template().getNode_templates() == null) {
211             componentToscaTemplate.getTopology_template().setNode_templates(nodeTemplates);
212         } else {
213             setNodeTemplateTypesForVnfs(componentToscaTemplate, vnfDescriptorList);
214             componentToscaTemplate.getTopology_template().getNode_templates().putAll(nodeTemplates);
215         }
216
217         removeOnapMetaData(componentToscaTemplate);
218
219         setDefaultImportsForEtsiSolNsNsd(componentToscaTemplate, vnfDescriptorList);
220
221         return componentToscaTemplate;
222     }
223
224     private void handleSubstitutionMappings(final ToscaTemplate componentToscaTemplate, final String nsNodeTypeName) {
225         final SubstitutionMapping substitutionMapping = new SubstitutionMapping();
226         substitutionMapping.setNode_type(nsNodeTypeName);
227         final SubstitutionMapping onapSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings();
228         if (onapSubstitutionMapping != null) {
229             substitutionMapping.setRequirements(onapSubstitutionMapping.getRequirements());
230             substitutionMapping.setCapabilities(onapSubstitutionMapping.getCapabilities());
231             substitutionMapping.setProperties(onapSubstitutionMapping.getProperties());
232             substitutionMapping.setAttributes(onapSubstitutionMapping.getAttributes());
233         }
234         componentToscaTemplate.getTopology_template().setSubstitution_mappings(substitutionMapping);
235     }
236
237     private void setNodeTemplateTypesForVnfs(final ToscaTemplate template,
238                                              final List<VnfDescriptor> vnfDescriptorList) {
239         if (CollectionUtils.isEmpty(vnfDescriptorList)) {
240             return;
241         }
242         final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
243         if (MapUtils.isEmpty(nodeTemplateMap)) {
244             return;
245         }
246         nodeTemplateMap.forEach((key, toscaNodeTemplate) ->
247             vnfDescriptorList.stream()
248                 .filter(vnfDescriptor -> key.equals(vnfDescriptor.getName())).findFirst()
249                 .ifPresent(vnfDescriptor -> toscaNodeTemplate.setType(vnfDescriptor.getNodeType()))
250         );
251     }
252
253     private void setPropertiesForNodeTemplates(final ToscaTemplate template) {
254         final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
255         if (MapUtils.isEmpty(nodeTemplateMap)) {
256             return;
257         }
258         for (final Entry<String, ToscaNodeTemplate> nodeTemplate : nodeTemplateMap.entrySet()) {
259             final Map<String, Object> propertyMap = nodeTemplate.getValue().getProperties();
260             if (MapUtils.isEmpty(propertyMap)) {
261                 nodeTemplate.getValue().setProperties(null);
262                 continue;
263             }
264             final Map<String, Object> editedPropertyMap = new HashMap<>();
265             for (final Entry<String, Object> property : propertyMap.entrySet()) {
266                 if (!PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE.contains(property.getKey())
267                         && propertyIsDefinedInNodeType(property.getKey())) {
268                     editedPropertyMap
269                         .put(property.getKey().substring(property.getKey().indexOf('_') + 1), property.getValue());
270                 }
271             }
272             if (editedPropertyMap.isEmpty()) {
273                 nodeTemplate.getValue().setProperties(null);
274             } else {
275                 nodeTemplate.getValue().setProperties(editedPropertyMap);
276             }
277         }
278     }
279     
280     private void removeCapabilitiesFromNodeTemplates(final ToscaTemplate template) {
281         final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
282         if (MapUtils.isEmpty(nodeTemplateMap)) {
283             return;
284         }
285         for (final Entry<String, ToscaNodeTemplate> nodeTemplate : nodeTemplateMap.entrySet()) {
286                 nodeTemplate.getValue().setCapabilities(null);
287         }
288     }
289
290     private void removeOnapPropertiesFromInputs(final ToscaTemplate template) {
291         final ToscaTopolgyTemplate topologyTemplate = template.getTopology_template();
292         final Map<String, ToscaProperty> inputMap = topologyTemplate.getInputs();
293         if (MapUtils.isNotEmpty(inputMap)) {
294             inputMap.entrySet()
295                 .removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
296         }
297         if (MapUtils.isEmpty(inputMap)) {
298             topologyTemplate.setInputs(null);
299         }
300     }
301
302     private void removeOnapMetaData(final ToscaTemplate template) {
303         template.setMetadata(null);
304         final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
305         if (MapUtils.isEmpty(nodeTemplateMap)) {
306             return;
307         }
308         nodeTemplateMap.values().forEach(toscaNodeTemplate -> toscaNodeTemplate.setMetadata(null));
309     }
310
311     private void setDefaultImportsForEtsiSolNsNsd(final ToscaTemplate template,
312                                                   final List<VnfDescriptor> vnfDescriptorList) {
313         final List<Map<String, Map<String, String>>> importEntryMap = new ArrayList<>(DEFAULT_IMPORTS_ETSI_SOL_NSD);
314         if (CollectionUtils.isNotEmpty(vnfDescriptorList)) {
315             for (final VnfDescriptor vnfDescriptor : vnfDescriptorList) {
316                 final Map<String, String> vnfImportChildEntry = new HashMap<>();
317                 vnfImportChildEntry.put("file", vnfDescriptor.getVnfdFileName());
318                 final Map<String, Map<String, String>> vnfdImportVnfdEntry = new HashMap<>();
319                 vnfdImportVnfdEntry.put(vnfDescriptor.getName(), vnfImportChildEntry);
320                 importEntryMap.add(vnfdImportVnfdEntry);
321             }
322         }
323
324         template.setImports(importEntryMap);
325     }
326
327     private ToscaNodeType createEtsiSolNsNodeType(final ToscaNodeType nsNodeType) {
328         final ToscaNodeType toscaNodeType = new ToscaNodeType();
329         toscaNodeType.setDerived_from(NS_TOSCA_TYPE);
330
331         final Map<String, ToscaProperty> propertiesInNsNodeType = nsNodeType.getProperties();
332
333         for (final Entry<String, ToscaProperty> property : propertiesInNsNodeType.entrySet()) {
334             final ToscaProperty toscaProperty = property.getValue();
335             if (toscaProperty.getDefaultp() != null) {
336                 final ToscaPropertyConstraintValidValues constraint = new ToscaPropertyConstraintValidValues(
337                     Collections.singletonList(toscaProperty.getDefaultp().toString()));
338                 toscaProperty.setConstraints(Collections.singletonList(constraint));
339             }
340         }
341
342         propertiesInNsNodeType.entrySet()
343             .removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
344         toscaNodeType.setProperties(propertiesInNsNodeType);
345
346         return toscaNodeType;
347     }
348
349     private boolean propertyIsDefinedInNodeType(final String propertyName) {
350         // This will achieve what we want for now, but will look into a more generic solution which would involve
351         // checking the node_type definition in the VNFD
352         return !propertyName.equals("additional_parameters");
353     }
354
355
356     private ToscaNodeTemplate createNodeTemplateForNsNodeType(final String nodeType,
357                                                               final ToscaNodeType toscaNodeType) {
358         final ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
359         nodeTemplate.setType(nodeType);
360
361         final Map<String, ToscaProperty> properties = toscaNodeType.getProperties();
362         final Map<String, Object> nodeTemplateProperties = new HashMap<>();
363         for (final Entry<String, ToscaProperty> property : properties.entrySet()) {
364             nodeTemplateProperties.put(property.getKey(), property.getValue().getDefaultp());
365         }
366
367         if (!nodeTemplateProperties.isEmpty()) {
368             nodeTemplate.setProperties(nodeTemplateProperties);
369         }
370
371         final Map<String, Object> interfaces = toscaNodeType.getInterfaces();
372         if (interfaces != null) {
373             for (final Entry<String, Object> nodeInterface : interfaces.entrySet()) {
374                 if ("Nslcm".equals(nodeInterface.getKey()) && nodeInterface.getValue() instanceof Map) {
375                     ((Map<?, ?>) nodeInterface.getValue()).remove("type");
376                 }
377             }
378             nodeTemplate.setInterfaces(interfaces);
379         }
380
381         return nodeTemplate;
382     }
383
384     private ToscaTemplate parseToToscaTemplate(final Component component) throws NsdException {
385         final Either<ToscaTemplate, ToscaError> toscaTemplateRes = toscaExportHandler.convertToToscaTemplate(component);
386         if (toscaTemplateRes.isRight()) {
387             String errorMsg = String.format("Could not parse component '%s' to tosca template. Error '%s'",
388                 component.getName(), toscaTemplateRes.right().value().name());
389             throw new NsdException(errorMsg);
390         }
391
392         return toscaTemplateRes.left().value();
393     }
394
395
396     private ToscaTemplate exportComponentInterfaceAsToscaTemplate(final Component component) throws NsdException {
397         if (null == DEFAULT_IMPORTS) {
398             throw new NsdException("Could not load default CSAR imports from configuration");
399         }
400
401         final ToscaTemplate toscaTemplate = new ToscaTemplate(TOSCA_VERSION);
402         toscaTemplate.setImports(new ArrayList<>(DEFAULT_IMPORTS));
403         final Either<ToscaTemplate, ToscaError> toscaTemplateRes = toscaExportHandler
404             .convertInterfaceNodeType(new HashMap<>(), component, toscaTemplate, new HashMap<>(), false);
405         if (toscaTemplateRes.isRight()) {
406             throw new NsdException(String.format("Could not create abstract service from component '%s'",
407                 component.getName()));
408         }
409
410         return toscaTemplateRes.left().value();
411     }
412
413 }