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
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.
16 * SPDX-License-Identifier: Apache-2.0
17 * ============LICENSE_END=========================================================
19 package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator;
21 import fj.data.Either;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Map.Entry;
30 import java.util.Optional;
31 import java.util.stream.Collectors;
32 import org.apache.commons.collections4.CollectionUtils;
33 import org.apache.commons.collections4.MapUtils;
34 import org.openecomp.sdc.be.config.ConfigurationManager;
35 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
36 import org.openecomp.sdc.be.model.Component;
37 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintType;
38 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
39 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
40 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
41 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.tosca.yaml.ToscaTemplateYamlGenerator;
42 import org.openecomp.sdc.be.tosca.ToscaError;
43 import org.openecomp.sdc.be.tosca.ToscaExportHandler;
44 import org.openecomp.sdc.be.tosca.model.SubstitutionMapping;
45 import org.openecomp.sdc.be.tosca.model.ToscaNodeTemplate;
46 import org.openecomp.sdc.be.tosca.model.ToscaNodeType;
47 import org.openecomp.sdc.be.tosca.model.ToscaProperty;
48 import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraint;
49 import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraintValidValues;
50 import org.openecomp.sdc.be.tosca.model.ToscaRequirement;
51 import org.openecomp.sdc.be.tosca.model.ToscaTemplate;
52 import org.openecomp.sdc.be.tosca.model.ToscaTemplateRequirement;
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 import org.springframework.beans.factory.config.BeanDefinition;
58 import org.springframework.context.annotation.Scope;
60 @org.springframework.stereotype.Component("nsDescriptorGenerator")
61 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
62 public class NsDescriptorGeneratorImpl implements NsDescriptorGenerator {
64 private static final Logger LOGGER = LoggerFactory.getLogger(NsDescriptorGeneratorImpl.class);
65 private static final String TOSCA_VERSION = "tosca_simple_yaml_1_1";
66 private static final String NS_TOSCA_TYPE = "tosca.nodes.nfv.NS";
67 private static final List<Map<String, Map<String, String>>> DEFAULT_IMPORTS = ConfigurationManager.getConfigurationManager().getConfiguration()
69 private static final List<String> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE = Arrays
70 .asList("cds_model_name", "cds_model_version", "skip_post_instantiation_configuration", "controller_actor");
71 private static final List<String> ETSI_SOL_NSD_NS_NODE_TYPE_PROPERTIES = Arrays
72 .asList("descriptor_id", "designer", "version", "name", "invariant_id", "flavour_id", "ns_profile", "service_availability_level");
73 private static final List<String> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE = Arrays
74 .asList("nf_function", "nf_role", "nf_naming_code", "nf_type", "nf_naming", "availability_zone_max_count", "min_instances", "max_instances",
75 "multi_stage_design", "sdnc_model_name", "sdnc_model_version", "sdnc_artifact_name", "skip_post_instantiation_configuration",
77 private final ToscaExportHandler toscaExportHandler;
78 private final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider;
80 public NsDescriptorGeneratorImpl(final ToscaExportHandler toscaExportHandler,
81 final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider) {
82 this.toscaExportHandler = toscaExportHandler;
83 this.toscaTemplateYamlGeneratorProvider = toscaTemplateYamlGeneratorProvider;
86 public Optional<Nsd> generate(final Component component, final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
87 if (!ComponentTypeEnum.SERVICE.equals(component.getComponentType())) {
88 return Optional.empty();
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();
96 return Optional.of(buildNsd(toscaTemplate, nsNodeType));
99 private Nsd buildNsd(final ToscaTemplate toscaTemplate, final ToscaNodeType nsNodeType) {
100 final Nsd nsd = new Nsd();
101 nsd.setDesigner(getProperty(nsNodeType, Nsd.DESIGNER_PROPERTY));
102 nsd.setVersion(getProperty(nsNodeType, Nsd.VERSION_PROPERTY));
103 nsd.setName(getProperty(nsNodeType, Nsd.NAME_PROPERTY));
104 nsd.setInvariantId(getProperty(nsNodeType, Nsd.INVARIANT_ID_PROPERTY));
105 final ToscaTemplateYamlGenerator yamlParserProvider = toscaTemplateYamlGeneratorProvider.getObject(toscaTemplate);
106 final byte[] contents = yamlParserProvider.parseToYamlString().getBytes();
107 nsd.setContents(contents);
108 final List<String> interfaceImplementations = getInterfaceImplementations(toscaTemplate);
109 nsd.setArtifactReferences(interfaceImplementations);
113 private List<String> getInterfaceImplementations(final ToscaTemplate template) {
114 if (template.getTopology_template().getNode_templates() == null) {
115 return Collections.emptyList();
117 final List<String> interfaceImplementations = new ArrayList<>();
118 final Collection<ToscaNodeTemplate> nodeTemplates = template.getTopology_template().getNode_templates().values();
119 nodeTemplates.stream().filter(toscaNodeTemplate -> toscaNodeTemplate.getInterfaces() != null).forEach(
120 toscaNodeTemplate -> toscaNodeTemplate.getInterfaces().values()
121 .forEach(interfaceInstance -> interfaceImplementations.addAll(getInterfaceImplementations(interfaceInstance))));
122 return interfaceImplementations;
125 private Collection<String> getInterfaceImplementations(final Object interfaceInstance) {
126 final Collection<String> interfaceImplementations = new ArrayList<>();
127 if (interfaceInstance instanceof Map) {
128 for (final Object value : ((Map<?, ?>) interfaceInstance).values()) {
129 if (value instanceof Map && ((Map<?, ?>) value).get("implementation") != null) {
130 interfaceImplementations.add(((Map<?, ?>) value).get("implementation").toString());
134 return interfaceImplementations;
137 private String getProperty(final ToscaNodeType nodeType, final String propertyName) {
138 final ToscaProperty toscaProperty = nodeType.getProperties().get(propertyName);
139 final String errorMsg = String.format("Property '%s' must be defined and must have a valid values constraint", propertyName);
140 final String returnValueOnError = "unknown";
141 if (toscaProperty == null || CollectionUtils.isEmpty(toscaProperty.getConstraints())) {
142 LOGGER.error(errorMsg);
143 return returnValueOnError;
145 final ToscaPropertyConstraint toscaPropertyConstraint = toscaProperty.getConstraints().get(0);
146 if (ConstraintType.VALID_VALUES != toscaPropertyConstraint.getConstraintType()) {
147 LOGGER.error(errorMsg);
148 return returnValueOnError;
150 final ToscaPropertyConstraintValidValues validValuesConstraint = (ToscaPropertyConstraintValidValues) toscaPropertyConstraint;
151 final List<String> validValues = validValuesConstraint.getValidValues();
152 if (CollectionUtils.isEmpty(validValues)) {
153 LOGGER.error(errorMsg);
154 return returnValueOnError;
156 return validValues.get(0);
159 private ToscaTemplate createNetworkServiceDescriptor(final Component component, final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
160 final ToscaTemplate componentToscaTemplate = parseToToscaTemplate(component);
161 final ToscaTemplate componentToscaTemplateInterface = exportComponentInterfaceAsToscaTemplate(component);
162 final Entry<String, ToscaNodeType> firstNodeTypeEntry = componentToscaTemplateInterface.getNode_types().entrySet().stream().findFirst()
164 if (firstNodeTypeEntry == null) {
165 throw new NsdException("Could not find abstract Service type");
167 final String nsNodeTypeName = firstNodeTypeEntry.getKey();
168 final ToscaNodeType nsNodeType = firstNodeTypeEntry.getValue();
169 final Map<String, ToscaNodeType> nodeTypeMap = new HashMap<>();
170 nodeTypeMap.put(nsNodeTypeName, createEtsiSolNsNodeType(nsNodeType, componentToscaTemplate));
171 if (componentToscaTemplate.getNode_types() == null) {
172 componentToscaTemplate.setNode_types(nodeTypeMap);
174 componentToscaTemplate.getNode_types().putAll(nodeTypeMap);
176 handleNodeTemplates(componentToscaTemplate);
177 removeOnapAndEtsiNsdPropertiesFromInputs(componentToscaTemplate);
178 handleSubstitutionMappings(componentToscaTemplate, nsNodeTypeName);
179 final Map<String, ToscaNodeTemplate> nodeTemplates = new HashMap<>();
180 nodeTemplates.put(nsNodeTypeName,
181 createNodeTemplateForNsNodeType(nsNodeTypeName, componentToscaTemplateInterface.getNode_types().get(nsNodeTypeName)));
182 if (componentToscaTemplate.getTopology_template().getNode_templates() == null) {
183 componentToscaTemplate.getTopology_template().setNode_templates(nodeTemplates);
185 setNodeTemplateTypesForVnfs(componentToscaTemplate, vnfDescriptorList);
186 componentToscaTemplate.getTopology_template().getNode_templates().putAll(nodeTemplates);
188 removeOnapMetaData(componentToscaTemplate);
189 setDefaultImportsForEtsiSolNsNsd(componentToscaTemplate, vnfDescriptorList);
190 return componentToscaTemplate;
193 private void handleSubstitutionMappings(final ToscaTemplate componentToscaTemplate, final String nsNodeTypeName) {
194 final SubstitutionMapping substitutionMapping = new SubstitutionMapping();
195 substitutionMapping.setNode_type(nsNodeTypeName);
196 final SubstitutionMapping onapSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings();
197 if (onapSubstitutionMapping != null && onapSubstitutionMapping.getRequirements() != null) {
198 substitutionMapping.setRequirements(adjustRequirementNamesToMatchVnfd(onapSubstitutionMapping.getRequirements()));
200 componentToscaTemplate.getTopology_template().setSubstitution_mappings(substitutionMapping);
203 private Map<String, String[]> adjustRequirementNamesToMatchVnfd(final Map<String, String[]> requirements) {
204 for (final Map.Entry<String, String[]> entry : requirements.entrySet()) {
206 final String[] adjustedValue = {entry.getValue()[0], entry.getValue()[1].substring(entry.getValue()[1].lastIndexOf('.') + 1)};
207 entry.setValue(adjustedValue);
208 } catch (final ArrayIndexOutOfBoundsException exception) {
209 LOGGER.error("Malformed requirement: {}", entry);
215 private void setNodeTemplateTypesForVnfs(final ToscaTemplate template, final List<VnfDescriptor> vnfDescriptorList) {
216 if (CollectionUtils.isEmpty(vnfDescriptorList)) {
219 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
220 if (MapUtils.isEmpty(nodeTemplateMap)) {
223 nodeTemplateMap.forEach(
224 (key, toscaNodeTemplate) -> vnfDescriptorList.stream().filter(vnfDescriptor -> key.equals(vnfDescriptor.getName())).findFirst()
225 .ifPresent(vnfDescriptor -> toscaNodeTemplate.setType(vnfDescriptor.getNodeType())));
228 private void handleNodeTemplates(final ToscaTemplate template) {
229 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
230 if (MapUtils.isEmpty(nodeTemplateMap)) {
233 for (final Entry<String, ToscaNodeTemplate> nodeTemplate : nodeTemplateMap.entrySet()) {
234 setPropertiesForNodeTemplate(nodeTemplate);
235 setRequirementsForNodeTemplate(nodeTemplate);
236 removeCapabilitiesFromNodeTemplate(nodeTemplate);
240 private void setPropertiesForNodeTemplate(final Entry<String, ToscaNodeTemplate> nodeTemplate) {
241 final Map<String, Object> propertyMap = nodeTemplate.getValue().getProperties();
242 if (MapUtils.isEmpty(propertyMap)) {
243 nodeTemplate.getValue().setProperties(null);
246 final Map<String, Object> editedPropertyMap = new HashMap<>();
247 for (final Entry<String, Object> property : propertyMap.entrySet()) {
248 if (!PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE.contains(property.getKey()) && propertyIsDefinedInNodeType(
249 property.getKey())) {
250 editedPropertyMap.put(property.getKey(), property.getValue());
253 if (editedPropertyMap.isEmpty()) {
254 nodeTemplate.getValue().setProperties(null);
256 nodeTemplate.getValue().setProperties(editedPropertyMap);
260 private void setRequirementsForNodeTemplate(final Entry<String, ToscaNodeTemplate> nodeTemplateMap) {
261 final List<Map<String, ToscaTemplateRequirement>> requirementAssignments = nodeTemplateMap.getValue().getRequirements();
262 if (requirementAssignments != null) {
263 final List<Map<String, ToscaTemplateRequirement>> requirementAssignmentsMatchingVnfdRequirements = new ArrayList<>();
264 for (final Map<String, ToscaTemplateRequirement> requirementAssignment : requirementAssignments) {
265 final Map<String, ToscaTemplateRequirement> requirementAssignmentMatchingVnfd =
266 requirementAssignment.entrySet().stream()
267 .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 removeOnapAndEtsiNsdPropertiesFromInputs(final ToscaTemplate template) {
280 final ToscaTopolgyTemplate topologyTemplate = template.getTopology_template();
281 final Map<String, ToscaProperty> inputMap = topologyTemplate.getInputs();
283 if (MapUtils.isNotEmpty(inputMap)) {
284 inputMap.entrySet().removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey())
285 || ETSI_SOL_NSD_NS_NODE_TYPE_PROPERTIES.contains(entry.getKey()));
287 if (MapUtils.isEmpty(inputMap)) {
288 topologyTemplate.setInputs(null);
292 private void removeOnapMetaData(final ToscaTemplate template) {
293 template.setMetadata(null);
294 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
295 if (MapUtils.isEmpty(nodeTemplateMap)) {
298 nodeTemplateMap.values().forEach(toscaNodeTemplate -> toscaNodeTemplate.setMetadata(null));
301 private void setDefaultImportsForEtsiSolNsNsd(final ToscaTemplate template, final List<VnfDescriptor> vnfDescriptorList) {
302 final List<Map<String, Map<String, String>>> importEntryMap = new ArrayList<>();
303 final Map<String, Map<String, String>> defaultImportEntryMap = generateDefaultImportEntry();
304 if (MapUtils.isNotEmpty(defaultImportEntryMap)) {
305 importEntryMap.add(defaultImportEntryMap);
307 if (CollectionUtils.isNotEmpty(vnfDescriptorList)) {
308 for (final VnfDescriptor vnfDescriptor : vnfDescriptorList) {
309 final Map<String, String> vnfImportChildEntry = new HashMap<>();
310 vnfImportChildEntry.put("file", vnfDescriptor.getVnfdFileName());
311 final Map<String, Map<String, String>> vnfdImportVnfdEntry = new HashMap<>();
312 vnfdImportVnfdEntry.put(vnfDescriptor.getName(), vnfImportChildEntry);
313 importEntryMap.add(vnfdImportVnfdEntry);
316 template.setImports(importEntryMap);
319 private Map<String, Map<String, String>> generateDefaultImportEntry() {
320 return Map.of("etsi_nfv_sol001_nsd_types", Map.of("file", "etsi_nfv_sol001_nsd_types.yaml"));
323 private ToscaNodeType createEtsiSolNsNodeType(final ToscaNodeType nsNodeType, final ToscaTemplate componentToscaTemplate) {
324 final ToscaNodeType toscaNodeType = new ToscaNodeType();
325 toscaNodeType.setDerived_from(NS_TOSCA_TYPE);
326 final Map<String, ToscaProperty> propertiesInNsNodeType = nsNodeType.getProperties();
327 for (final Entry<String, ToscaProperty> property : propertiesInNsNodeType.entrySet()) {
328 final ToscaProperty toscaProperty = property.getValue();
329 if (toscaProperty.getDefaultp() != null && ETSI_SOL_NSD_NS_NODE_TYPE_PROPERTIES.contains(property.getKey())) {
330 final ToscaPropertyConstraintValidValues constraint = new ToscaPropertyConstraintValidValues(
331 Collections.singletonList(toscaProperty.getDefaultp().toString()));
332 toscaProperty.setConstraints(Collections.singletonList(constraint));
335 propertiesInNsNodeType.entrySet().removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
336 toscaNodeType.setProperties(propertiesInNsNodeType);
338 final List<Map<String, ToscaRequirement>> requirementsInNsNodeType = getRequirementsForNsNodeType(nsNodeType.getRequirements(),
339 componentToscaTemplate);
340 if (!requirementsInNsNodeType.isEmpty()) {
341 toscaNodeType.setRequirements(requirementsInNsNodeType);
344 return toscaNodeType;
347 private List<Map<String, ToscaRequirement>> getRequirementsForNsNodeType(final List<Map<String, ToscaRequirement>> requirements,
348 final ToscaTemplate componentToscaTemplate) {
349 final Map<String, String[]> requirementsInSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings()
351 if (requirements == null || MapUtils.isEmpty(requirementsInSubstitutionMapping)) {
352 return Collections.emptyList();
354 final List<Map<String, ToscaRequirement>> requirementsToAdd = new ArrayList<>();
355 for (final Map<String, ToscaRequirement> requirementMap : requirements) {
356 final Map<String, ToscaRequirement> neededRequirements = requirementMap.entrySet().stream()
357 .filter(entry -> requirementsInSubstitutionMapping.containsKey(entry.getKey()))
358 .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
359 if (!neededRequirements.isEmpty()) {
360 requirementsToAdd.add(neededRequirements);
363 return requirementsToAdd;
367 private boolean propertyIsDefinedInNodeType(final String propertyName) {
368 // This will achieve what we want for now, but will look into a more generic solution which would involve
370 // checking the node_type definition in the VNFD
371 return !propertyName.equals("additional_parameters");
374 private ToscaNodeTemplate createNodeTemplateForNsNodeType(final String nodeType, final ToscaNodeType toscaNodeType) {
375 final ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
376 nodeTemplate.setType(nodeType);
377 final Map<String, ToscaProperty> properties = toscaNodeType.getProperties();
378 final Map<String, Object> nodeTemplateProperties = new HashMap<>();
379 for (final Entry<String, ToscaProperty> property : properties.entrySet()) {
380 if (property.getValue().getDefaultp() != null) {
381 nodeTemplateProperties.put(property.getKey(), property.getValue().getDefaultp());
384 if (!nodeTemplateProperties.isEmpty()) {
385 nodeTemplate.setProperties(nodeTemplateProperties);
387 final Map<String, Object> interfaces = toscaNodeType.getInterfaces();
388 if (interfaces != null) {
389 for (final Entry<String, Object> nodeInterface : interfaces.entrySet()) {
390 if ("Nslcm".equals(nodeInterface.getKey()) && nodeInterface.getValue() instanceof Map) {
391 ((Map<?, ?>) nodeInterface.getValue()).remove("type");
394 nodeTemplate.setInterfaces(interfaces);
399 private ToscaTemplate parseToToscaTemplate(final Component component) throws NsdException {
400 final Either<ToscaTemplate, ToscaError> toscaTemplateRes = toscaExportHandler.convertToToscaTemplate(component);
401 if (toscaTemplateRes.isRight()) {
402 String errorMsg = String
403 .format("Could not parse component '%s' to tosca template. Error '%s'", component.getName(), toscaTemplateRes.right().value().name());
404 throw new NsdException(errorMsg);
406 return toscaTemplateRes.left().value();
409 private ToscaTemplate exportComponentInterfaceAsToscaTemplate(final Component component) throws NsdException {
410 if (null == DEFAULT_IMPORTS) {
411 throw new NsdException("Could not load default CSAR imports from configuration");
413 final ToscaTemplate toscaTemplate = new ToscaTemplate(TOSCA_VERSION);
414 toscaTemplate.setImports(new ArrayList<>(DEFAULT_IMPORTS));
415 final Either<ToscaTemplate, ToscaError> toscaTemplateRes = toscaExportHandler
416 .convertInterfaceNodeType(new HashMap<>(), component, toscaTemplate, new HashMap<>(), false);
417 if (toscaTemplateRes.isRight()) {
418 throw new NsdException(String.format("Could not create abstract service from component '%s'", component.getName()));
420 return toscaTemplateRes.left().value();