3 * ============LICENSE_START=======================================================
4 * Copyright (C) 2020 Nordix Foundation
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
20 package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator;
22 import com.google.common.collect.ImmutableMap;
23 import fj.data.Either;
24 import groovy.util.MapEntry;
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;
32 import java.util.Map.Entry;
33 import java.util.Optional;
34 import java.util.stream.Collectors;
35 import org.apache.commons.collections4.CollectionUtils;
36 import org.apache.commons.collections4.MapUtils;
37 import org.openecomp.sdc.be.config.ConfigurationManager;
38 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
39 import org.openecomp.sdc.be.model.Component;
40 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintType;
41 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
42 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
43 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
44 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.tosca.yaml.ToscaTemplateYamlGenerator;
45 import org.openecomp.sdc.be.tosca.ToscaError;
46 import org.openecomp.sdc.be.tosca.ToscaExportHandler;
47 import org.openecomp.sdc.be.tosca.model.SubstitutionMapping;
48 import org.openecomp.sdc.be.tosca.model.ToscaNodeTemplate;
49 import org.openecomp.sdc.be.tosca.model.ToscaNodeType;
50 import org.openecomp.sdc.be.tosca.model.ToscaProperty;
51 import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraint;
52 import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraintValidValues;
53 import org.openecomp.sdc.be.tosca.model.ToscaRequirement;
54 import org.openecomp.sdc.be.tosca.model.ToscaTemplate;
55 import org.openecomp.sdc.be.tosca.model.ToscaTemplateRequirement;
56 import org.openecomp.sdc.be.tosca.model.ToscaTopolgyTemplate;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.beans.factory.ObjectProvider;
60 import org.springframework.beans.factory.config.BeanDefinition;
61 import org.springframework.context.annotation.Scope;
63 @org.springframework.stereotype.Component("nsDescriptorGenerator")
64 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
65 public class NsDescriptorGeneratorImpl implements NsDescriptorGenerator {
67 private static final Logger LOGGER = LoggerFactory.getLogger(NsDescriptorGeneratorImpl.class);
68 private static final String TOSCA_VERSION = "tosca_simple_yaml_1_1";
69 private static final String NS_TOSCA_TYPE = "tosca.nodes.nfv.NS";
70 private static final List<Map<String, Map<String, String>>> DEFAULT_IMPORTS = ConfigurationManager.getConfigurationManager().getConfiguration()
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", "min_instances", "max_instances",
76 "multi_stage_design", "sdnc_model_name", "sdnc_model_version", "sdnc_artifact_name", "skip_post_instantiation_configuration",
78 private final ToscaExportHandler toscaExportHandler;
79 private final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider;
81 public NsDescriptorGeneratorImpl(final ToscaExportHandler toscaExportHandler,
82 final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider) {
83 this.toscaExportHandler = toscaExportHandler;
84 this.toscaTemplateYamlGeneratorProvider = toscaTemplateYamlGeneratorProvider;
87 public Optional<Nsd> generate(final Component component, final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
88 if (!ComponentTypeEnum.SERVICE.equals(component.getComponentType())) {
89 return Optional.empty();
91 final ToscaTemplate toscaTemplate = createNetworkServiceDescriptor(component, vnfDescriptorList);
92 final ToscaNodeType nsNodeType = toscaTemplate.getNode_types().values().stream()
93 .filter(toscaNodeType -> NS_TOSCA_TYPE.equals(toscaNodeType.getDerived_from())).findFirst().orElse(null);
94 if (nsNodeType == null) {
95 return Optional.empty();
97 return Optional.of(buildNsd(toscaTemplate, nsNodeType));
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 = toscaTemplateYamlGeneratorProvider.getObject(toscaTemplate);
107 final byte[] contents = yamlParserProvider.parseToYamlString().getBytes();
108 nsd.setContents(contents);
109 final List<String> interfaceImplementations = getInterfaceImplementations(toscaTemplate);
110 nsd.setArtifactReferences(interfaceImplementations);
114 private List<String> getInterfaceImplementations(final ToscaTemplate template) {
115 if (template.getTopology_template().getNode_templates() == null) {
116 return Collections.emptyList();
118 final List<String> interfaceImplementations = new ArrayList<>();
119 final Collection<ToscaNodeTemplate> nodeTemplates = template.getTopology_template().getNode_templates().values();
120 nodeTemplates.stream().filter(toscaNodeTemplate -> toscaNodeTemplate.getInterfaces() != null).forEach(
121 toscaNodeTemplate -> toscaNodeTemplate.getInterfaces().values()
122 .forEach(interfaceInstance -> interfaceImplementations.addAll(getInterfaceImplementations(interfaceInstance))));
123 return interfaceImplementations;
126 private Collection<String> getInterfaceImplementations(final Object interfaceInstance) {
127 final Collection<String> interfaceImplementations = new ArrayList<>();
128 if (interfaceInstance instanceof Map) {
129 for (final Object value : ((Map<?, ?>) interfaceInstance).values()) {
130 if (value instanceof Map && ((Map<?, ?>) value).get("implementation") != null) {
131 interfaceImplementations.add(((Map<?, ?>) value).get("implementation").toString());
135 return interfaceImplementations;
138 private String getProperty(final ToscaNodeType nodeType, final String propertyName) {
139 final ToscaProperty toscaProperty = nodeType.getProperties().get(propertyName);
140 final String errorMsg = String.format("Property '%s' must be defined and must have a valid values constraint", propertyName);
141 final String returnValueOnError = "unknown";
142 if (toscaProperty == null || CollectionUtils.isEmpty(toscaProperty.getConstraints())) {
143 LOGGER.error(errorMsg);
144 return returnValueOnError;
146 final ToscaPropertyConstraint toscaPropertyConstraint = toscaProperty.getConstraints().get(0);
147 if (ConstraintType.VALID_VALUES != toscaPropertyConstraint.getConstraintType()) {
148 LOGGER.error(errorMsg);
149 return returnValueOnError;
151 final ToscaPropertyConstraintValidValues validValuesConstraint = (ToscaPropertyConstraintValidValues) toscaPropertyConstraint;
152 final List<String> validValues = validValuesConstraint.getValidValues();
153 if (CollectionUtils.isEmpty(validValues)) {
154 LOGGER.error(errorMsg);
155 return returnValueOnError;
157 return validValues.get(0);
160 private ToscaTemplate createNetworkServiceDescriptor(final Component component, final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
161 final ToscaTemplate componentToscaTemplate = parseToToscaTemplate(component);
162 final ToscaTemplate componentToscaTemplateInterface = exportComponentInterfaceAsToscaTemplate(component);
163 final Entry<String, ToscaNodeType> firstNodeTypeEntry = componentToscaTemplateInterface.getNode_types().entrySet().stream().findFirst()
165 if (firstNodeTypeEntry == null) {
166 throw new NsdException("Could not find abstract Service type");
168 final String nsNodeTypeName = firstNodeTypeEntry.getKey();
169 final ToscaNodeType nsNodeType = firstNodeTypeEntry.getValue();
170 final Map<String, ToscaNodeType> nodeTypeMap = new HashMap<>();
171 nodeTypeMap.put(nsNodeTypeName, createEtsiSolNsNodeType(nsNodeType, componentToscaTemplate));
172 if (componentToscaTemplate.getNode_types() == null) {
173 componentToscaTemplate.setNode_types(nodeTypeMap);
175 componentToscaTemplate.getNode_types().putAll(nodeTypeMap);
177 handleNodeTemplates(componentToscaTemplate);
178 removeOnapPropertiesFromInputs(componentToscaTemplate);
179 handleSubstitutionMappings(componentToscaTemplate, nsNodeTypeName);
180 final Map<String, ToscaNodeTemplate> nodeTemplates = new HashMap<>();
181 nodeTemplates.put(nsNodeTypeName,
182 createNodeTemplateForNsNodeType(nsNodeTypeName, componentToscaTemplateInterface.getNode_types().get(nsNodeTypeName)));
183 if (componentToscaTemplate.getTopology_template().getNode_templates() == null) {
184 componentToscaTemplate.getTopology_template().setNode_templates(nodeTemplates);
186 setNodeTemplateTypesForVnfs(componentToscaTemplate, vnfDescriptorList);
187 componentToscaTemplate.getTopology_template().getNode_templates().putAll(nodeTemplates);
189 removeOnapMetaData(componentToscaTemplate);
190 setDefaultImportsForEtsiSolNsNsd(componentToscaTemplate, vnfDescriptorList);
191 return componentToscaTemplate;
194 private void handleSubstitutionMappings(final ToscaTemplate componentToscaTemplate, final String nsNodeTypeName) {
195 final SubstitutionMapping substitutionMapping = new SubstitutionMapping();
196 substitutionMapping.setNode_type(nsNodeTypeName);
197 final SubstitutionMapping onapSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings();
198 if (onapSubstitutionMapping != null && onapSubstitutionMapping.getRequirements() != null) {
199 substitutionMapping.setRequirements(adjustRequirementNamesToMatchVnfd(onapSubstitutionMapping.getRequirements()));
201 componentToscaTemplate.getTopology_template().setSubstitution_mappings(substitutionMapping);
204 private Map<String, String[]> adjustRequirementNamesToMatchVnfd(final Map<String, String[]> requirements) {
205 for (final Map.Entry<String, String[]> entry : requirements.entrySet()) {
207 final String[] adjustedValue = {entry.getValue()[0], entry.getValue()[1].substring(entry.getValue()[1].lastIndexOf('.') + 1)};
208 entry.setValue(adjustedValue);
209 } catch (final ArrayIndexOutOfBoundsException exception) {
210 LOGGER.error("Malformed requirement: {}", entry);
216 private void setNodeTemplateTypesForVnfs(final ToscaTemplate template, final List<VnfDescriptor> vnfDescriptorList) {
217 if (CollectionUtils.isEmpty(vnfDescriptorList)) {
220 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
221 if (MapUtils.isEmpty(nodeTemplateMap)) {
224 nodeTemplateMap.forEach(
225 (key, toscaNodeTemplate) -> vnfDescriptorList.stream().filter(vnfDescriptor -> key.equals(vnfDescriptor.getName())).findFirst()
226 .ifPresent(vnfDescriptor -> toscaNodeTemplate.setType(vnfDescriptor.getNodeType())));
229 private void handleNodeTemplates(final ToscaTemplate template) {
230 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
231 if (MapUtils.isEmpty(nodeTemplateMap)) {
234 for (final Entry<String, ToscaNodeTemplate> nodeTemplate : nodeTemplateMap.entrySet()) {
235 setPropertiesForNodeTemplate(nodeTemplate);
236 setRequirementsForNodeTemplate(nodeTemplate);
237 removeCapabilitiesFromNodeTemplate(nodeTemplate);
241 private void setPropertiesForNodeTemplate(final Entry<String, ToscaNodeTemplate> nodeTemplate) {
242 final Map<String, Object> propertyMap = nodeTemplate.getValue().getProperties();
243 if (MapUtils.isEmpty(propertyMap)) {
244 nodeTemplate.getValue().setProperties(null);
247 final Map<String, Object> editedPropertyMap = new HashMap<>();
248 for (final Entry<String, Object> property : propertyMap.entrySet()) {
249 if (!PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE.contains(property.getKey()) && propertyIsDefinedInNodeType(
250 property.getKey())) {
251 editedPropertyMap.put(property.getKey().substring(property.getKey().indexOf('_') + 1), property.getValue());
254 if (editedPropertyMap.isEmpty()) {
255 nodeTemplate.getValue().setProperties(null);
257 nodeTemplate.getValue().setProperties(editedPropertyMap);
261 private void setRequirementsForNodeTemplate(final Entry<String, ToscaNodeTemplate> nodeTemplateMap) {
262 final List<Map<String,ToscaTemplateRequirement>> requirementAssignments = nodeTemplateMap.getValue().getRequirements();
263 if (requirementAssignments != null) {
264 final List<Map<String,ToscaTemplateRequirement>> requirementAssignmentsMatchingVnfdRequirements = new ArrayList<>();
265 for (final Map<String, ToscaTemplateRequirement> requirementAssignment: requirementAssignments) {
266 final Map<String, ToscaTemplateRequirement> requirementAssignmentMatchingVnfd =
267 requirementAssignment.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey().substring(entry.getKey().lastIndexOf('.') + 1), Map.Entry::getValue));
268 requirementAssignmentsMatchingVnfdRequirements.add(requirementAssignmentMatchingVnfd);
270 nodeTemplateMap.getValue().setRequirements(requirementAssignmentsMatchingVnfdRequirements);
275 private void removeCapabilitiesFromNodeTemplate(final Entry<String, ToscaNodeTemplate> nodeTemplate) {
276 nodeTemplate.getValue().setCapabilities(null);
279 private void removeOnapPropertiesFromInputs(final ToscaTemplate template) {
280 final ToscaTopolgyTemplate topologyTemplate = template.getTopology_template();
281 final Map<String, ToscaProperty> inputMap = topologyTemplate.getInputs();
282 if (MapUtils.isNotEmpty(inputMap)) {
283 inputMap.entrySet().removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
285 if (MapUtils.isEmpty(inputMap)) {
286 topologyTemplate.setInputs(null);
290 private void removeOnapMetaData(final ToscaTemplate template) {
291 template.setMetadata(null);
292 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
293 if (MapUtils.isEmpty(nodeTemplateMap)) {
296 nodeTemplateMap.values().forEach(toscaNodeTemplate -> toscaNodeTemplate.setMetadata(null));
299 private void setDefaultImportsForEtsiSolNsNsd(final ToscaTemplate template, final List<VnfDescriptor> vnfDescriptorList) {
300 final List<Map<String, Map<String, String>>> importEntryMap = new ArrayList<>();
301 final Map<String, Map<String, String>> defaultImportEntryMap = generateDefaultImportEntry();
302 if (MapUtils.isNotEmpty(defaultImportEntryMap)) {
303 importEntryMap.add(defaultImportEntryMap);
305 if (CollectionUtils.isNotEmpty(vnfDescriptorList)) {
306 for (final VnfDescriptor vnfDescriptor : vnfDescriptorList) {
307 final Map<String, String> vnfImportChildEntry = new HashMap<>();
308 vnfImportChildEntry.put("file", vnfDescriptor.getVnfdFileName());
309 final Map<String, Map<String, String>> vnfdImportVnfdEntry = new HashMap<>();
310 vnfdImportVnfdEntry.put(vnfDescriptor.getName(), vnfImportChildEntry);
311 importEntryMap.add(vnfdImportVnfdEntry);
314 template.setImports(importEntryMap);
317 private Map<String, Map<String, String>> generateDefaultImportEntry() {
318 return ImmutableMap.of("etsi_nfv_sol001_nsd_types", ImmutableMap.of("file", "etsi_nfv_sol001_nsd_types.yaml"));
321 private ToscaNodeType createEtsiSolNsNodeType(final ToscaNodeType nsNodeType, final ToscaTemplate componentToscaTemplate) {
322 final ToscaNodeType toscaNodeType = new ToscaNodeType();
323 toscaNodeType.setDerived_from(NS_TOSCA_TYPE);
324 final Map<String, ToscaProperty> propertiesInNsNodeType = nsNodeType.getProperties();
325 for (final Entry<String, ToscaProperty> property : propertiesInNsNodeType.entrySet()) {
326 final ToscaProperty toscaProperty = property.getValue();
327 if (toscaProperty.getDefaultp() != null) {
328 final ToscaPropertyConstraintValidValues constraint = new ToscaPropertyConstraintValidValues(
329 Collections.singletonList(toscaProperty.getDefaultp().toString()));
330 toscaProperty.setConstraints(Collections.singletonList(constraint));
333 propertiesInNsNodeType.entrySet().removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
334 toscaNodeType.setProperties(propertiesInNsNodeType);
336 final List<Map<String, ToscaRequirement>> requirementsInNsNodeType = getRequirementsForNsNodeType(nsNodeType.getRequirements(), componentToscaTemplate);
337 if (!requirementsInNsNodeType.isEmpty()) {
338 toscaNodeType.setRequirements(requirementsInNsNodeType);
341 return toscaNodeType;
344 private List<Map<String,ToscaRequirement>> getRequirementsForNsNodeType(final List<Map<String,ToscaRequirement>> requirements, final ToscaTemplate componentToscaTemplate) {
345 final Map<String, String[]> requirementsInSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings().getRequirements();
346 if (requirements == null || MapUtils.isEmpty(requirementsInSubstitutionMapping)) {
347 return Collections.emptyList();
349 final List<Map<String,ToscaRequirement>> requirementsToAdd = new ArrayList<>();
350 for (final Map<String,ToscaRequirement> requirementMap : requirements) {
351 final Map<String,ToscaRequirement> neededRequirements = requirementMap.entrySet().stream().filter(entry -> requirementsInSubstitutionMapping.containsKey(entry.getKey())).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
352 if (!neededRequirements.isEmpty()) {
353 requirementsToAdd.add(neededRequirements);
356 return requirementsToAdd;
360 private boolean propertyIsDefinedInNodeType(final String propertyName) {
361 // This will achieve what we want for now, but will look into a more generic solution which would involve
363 // checking the node_type definition in the VNFD
364 return !propertyName.equals("additional_parameters");
367 private ToscaNodeTemplate createNodeTemplateForNsNodeType(final String nodeType, final ToscaNodeType toscaNodeType) {
368 final ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
369 nodeTemplate.setType(nodeType);
370 final Map<String, ToscaProperty> properties = toscaNodeType.getProperties();
371 final Map<String, Object> nodeTemplateProperties = new HashMap<>();
372 for (final Entry<String, ToscaProperty> property : properties.entrySet()) {
373 nodeTemplateProperties.put(property.getKey(), property.getValue().getDefaultp());
375 if (!nodeTemplateProperties.isEmpty()) {
376 nodeTemplate.setProperties(nodeTemplateProperties);
378 final Map<String, Object> interfaces = toscaNodeType.getInterfaces();
379 if (interfaces != null) {
380 for (final Entry<String, Object> nodeInterface : interfaces.entrySet()) {
381 if ("Nslcm".equals(nodeInterface.getKey()) && nodeInterface.getValue() instanceof Map) {
382 ((Map<?, ?>) nodeInterface.getValue()).remove("type");
385 nodeTemplate.setInterfaces(interfaces);
390 private ToscaTemplate parseToToscaTemplate(final Component component) throws NsdException {
391 final Either<ToscaTemplate, ToscaError> toscaTemplateRes = toscaExportHandler.convertToToscaTemplate(component);
392 if (toscaTemplateRes.isRight()) {
393 String errorMsg = String
394 .format("Could not parse component '%s' to tosca template. Error '%s'", component.getName(), toscaTemplateRes.right().value().name());
395 throw new NsdException(errorMsg);
397 return toscaTemplateRes.left().value();
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");
404 final ToscaTemplate toscaTemplate = new ToscaTemplate(TOSCA_VERSION);
405 toscaTemplate.setImports(new ArrayList<>(DEFAULT_IMPORTS));
406 final Either<ToscaTemplate, ToscaError> toscaTemplateRes = toscaExportHandler
407 .convertInterfaceNodeType(new HashMap<>(), component, toscaTemplate, new HashMap<>(), false);
408 if (toscaTemplateRes.isRight()) {
409 throw new NsdException(String.format("Could not create abstract service from component '%s'", component.getName()));
411 return toscaTemplateRes.left().value();