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