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=========================================================
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 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;
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;
59 @org.springframework.stereotype.Component("nsDescriptorGenerator")
60 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
61 public class NsDescriptorGeneratorImpl implements NsDescriptorGenerator {
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");
75 private final ToscaExportHandler toscaExportHandler;
76 private final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider;
78 public NsDescriptorGeneratorImpl(final ToscaExportHandler toscaExportHandler,
79 final ObjectProvider<ToscaTemplateYamlGenerator> toscaTemplateYamlGeneratorProvider) {
80 this.toscaExportHandler = toscaExportHandler;
81 this.toscaTemplateYamlGeneratorProvider = toscaTemplateYamlGeneratorProvider;
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();
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();
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 =
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);
115 private List<String> getInterfaceImplementations(final ToscaTemplate template) {
116 if (template.getTopology_template().getNode_templates() == null) {
117 return Collections.emptyList();
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))
128 return interfaceImplementations;
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());
140 return interfaceImplementations;
143 private String getProperty(final ToscaNodeType nodeType, final String propertyName) {
144 final ToscaProperty toscaProperty = nodeType.getProperties().get(propertyName);
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;
154 final ToscaPropertyConstraint toscaPropertyConstraint = toscaProperty.getConstraints().get(0);
155 if (ConstraintType.VALID_VALUES != toscaPropertyConstraint.getConstraintType()) {
156 LOGGER.error(errorMsg);
157 return returnValueOnError;
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;
168 return validValues.get(0);
171 private ToscaTemplate createNetworkServiceDescriptor(final Component component,
172 final List<VnfDescriptor> vnfDescriptorList)
173 throws NsdException {
175 final ToscaTemplate componentToscaTemplate = parseToToscaTemplate(component);
176 final ToscaTemplate componentToscaTemplateInterface = exportComponentInterfaceAsToscaTemplate(component);
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");
185 final String nsNodeTypeName = firstNodeTypeEntry.getKey();
186 final ToscaNodeType nsNodeType = firstNodeTypeEntry.getValue();
188 final Map<String, ToscaNodeType> nodeTypeMap = new HashMap<>();
189 nodeTypeMap.put(nsNodeTypeName, createEtsiSolNsNodeType(nsNodeType));
191 if (componentToscaTemplate.getNode_types() == null) {
192 componentToscaTemplate.setNode_types(nodeTypeMap);
194 componentToscaTemplate.getNode_types().putAll(nodeTypeMap);
197 setPropertiesForNodeTemplates(componentToscaTemplate);
198 removeCapabilitiesFromNodeTemplates(componentToscaTemplate);
199 removeOnapPropertiesFromInputs(componentToscaTemplate);
200 handleSubstitutionMappings(componentToscaTemplate, nsNodeTypeName);
202 final Map<String, ToscaNodeTemplate> nodeTemplates = new HashMap<>();
203 nodeTemplates.put(nsNodeTypeName, createNodeTemplateForNsNodeType(nsNodeTypeName,
204 componentToscaTemplateInterface.getNode_types().get(nsNodeTypeName)));
206 if (componentToscaTemplate.getTopology_template().getNode_templates() == null) {
207 componentToscaTemplate.getTopology_template().setNode_templates(nodeTemplates);
209 setNodeTemplateTypesForVnfs(componentToscaTemplate, vnfDescriptorList);
210 componentToscaTemplate.getTopology_template().getNode_templates().putAll(nodeTemplates);
213 removeOnapMetaData(componentToscaTemplate);
215 setDefaultImportsForEtsiSolNsNsd(componentToscaTemplate, vnfDescriptorList);
217 return componentToscaTemplate;
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());
228 componentToscaTemplate.getTopology_template().setSubstitution_mappings(substitutionMapping);
231 private void setNodeTemplateTypesForVnfs(final ToscaTemplate template,
232 final List<VnfDescriptor> vnfDescriptorList) {
233 if (CollectionUtils.isEmpty(vnfDescriptorList)) {
236 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
237 if (MapUtils.isEmpty(nodeTemplateMap)) {
240 nodeTemplateMap.forEach((key, toscaNodeTemplate) ->
241 vnfDescriptorList.stream()
242 .filter(vnfDescriptor -> key.equals(vnfDescriptor.getName())).findFirst()
243 .ifPresent(vnfDescriptor -> toscaNodeTemplate.setType(vnfDescriptor.getNodeType()))
247 private void setPropertiesForNodeTemplates(final ToscaTemplate template) {
248 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
249 if (MapUtils.isEmpty(nodeTemplateMap)) {
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);
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())) {
263 .put(property.getKey().substring(property.getKey().indexOf('_') + 1), property.getValue());
266 if (editedPropertyMap.isEmpty()) {
267 nodeTemplate.getValue().setProperties(null);
269 nodeTemplate.getValue().setProperties(editedPropertyMap);
274 private void removeCapabilitiesFromNodeTemplates(final ToscaTemplate template) {
275 final Map<String, ToscaNodeTemplate> nodeTemplateMap = template.getTopology_template().getNode_templates();
276 if (MapUtils.isEmpty(nodeTemplateMap)) {
279 for (final Entry<String, ToscaNodeTemplate> nodeTemplate : nodeTemplateMap.entrySet()) {
280 nodeTemplate.getValue().setCapabilities(null);
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)) {
289 .removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
291 if (MapUtils.isEmpty(inputMap)) {
292 topologyTemplate.setInputs(null);
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)) {
302 nodeTemplateMap.values().forEach(toscaNodeTemplate -> toscaNodeTemplate.setMetadata(null));
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);
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);
322 template.setImports(importEntryMap);
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")
331 private ToscaNodeType createEtsiSolNsNodeType(final ToscaNodeType nsNodeType) {
332 final ToscaNodeType toscaNodeType = new ToscaNodeType();
333 toscaNodeType.setDerived_from(NS_TOSCA_TYPE);
335 final Map<String, ToscaProperty> propertiesInNsNodeType = nsNodeType.getProperties();
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));
346 propertiesInNsNodeType.entrySet()
347 .removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()));
348 toscaNodeType.setProperties(propertiesInNsNodeType);
350 return toscaNodeType;
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");
360 private ToscaNodeTemplate createNodeTemplateForNsNodeType(final String nodeType,
361 final ToscaNodeType toscaNodeType) {
362 final ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
363 nodeTemplate.setType(nodeType);
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());
371 if (!nodeTemplateProperties.isEmpty()) {
372 nodeTemplate.setProperties(nodeTemplateProperties);
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");
382 nodeTemplate.setInterfaces(interfaces);
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);
396 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");
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()));
414 return toscaTemplateRes.left().value();