/* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator; import fj.data.Either; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.openecomp.sdc.be.config.ConfigurationManager; import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum; import org.openecomp.sdc.be.model.Component; import org.openecomp.sdc.be.model.tosca.constraints.ConstraintType; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.tosca.yaml.ToscaTemplateYamlGenerator; import org.openecomp.sdc.be.tosca.ToscaError; import org.openecomp.sdc.be.tosca.ToscaExportHandler; import org.openecomp.sdc.be.tosca.model.SubstitutionMapping; import org.openecomp.sdc.be.tosca.model.ToscaNodeTemplate; import org.openecomp.sdc.be.tosca.model.ToscaNodeType; import org.openecomp.sdc.be.tosca.model.ToscaProperty; import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraint; import org.openecomp.sdc.be.tosca.model.ToscaPropertyConstraintValidValues; import org.openecomp.sdc.be.tosca.model.ToscaRequirement; import org.openecomp.sdc.be.tosca.model.ToscaTemplate; import org.openecomp.sdc.be.tosca.model.ToscaTemplateRequirement; import org.openecomp.sdc.be.tosca.model.ToscaTopolgyTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @org.springframework.stereotype.Component("nsDescriptorGenerator") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class NsDescriptorGeneratorImpl implements NsDescriptorGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(NsDescriptorGeneratorImpl.class); private static final String TOSCA_VERSION = "tosca_simple_yaml_1_1"; private static final String NS_TOSCA_TYPE = "tosca.nodes.nfv.NS"; private static final List>> DEFAULT_IMPORTS = ConfigurationManager.getConfigurationManager().getConfiguration() .getDefaultImports(); private static final List PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE = Arrays .asList("cds_model_name", "cds_model_version", "skip_post_instantiation_configuration", "controller_actor"); private static final List ETSI_SOL_NSD_NS_NODE_TYPE_PROPERTIES = Arrays .asList("descriptor_id", "designer", "version", "name", "invariant_id", "flavour_id", "ns_profile", "service_availability_level"); private static final List PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE = Arrays .asList("nf_function", "nf_role", "nf_naming_code", "nf_type", "nf_naming", "availability_zone_max_count", "min_instances", "max_instances", "multi_stage_design", "sdnc_model_name", "sdnc_model_version", "sdnc_artifact_name", "skip_post_instantiation_configuration", "controller_actor"); private final ToscaExportHandler toscaExportHandler; private final ObjectProvider toscaTemplateYamlGeneratorProvider; public NsDescriptorGeneratorImpl(final ToscaExportHandler toscaExportHandler, final ObjectProvider toscaTemplateYamlGeneratorProvider) { this.toscaExportHandler = toscaExportHandler; this.toscaTemplateYamlGeneratorProvider = toscaTemplateYamlGeneratorProvider; } public Optional generate(final Component component, final List vnfDescriptorList) throws NsdException { if (!ComponentTypeEnum.SERVICE.equals(component.getComponentType())) { return Optional.empty(); } final ToscaTemplate toscaTemplate = createNetworkServiceDescriptor(component, vnfDescriptorList); final ToscaNodeType nsNodeType = toscaTemplate.getNode_types().values().stream() .filter(toscaNodeType -> NS_TOSCA_TYPE.equals(toscaNodeType.getDerived_from())).findFirst().orElse(null); if (nsNodeType == null) { return Optional.empty(); } return Optional.of(buildNsd(toscaTemplate, nsNodeType)); } private Nsd buildNsd(final ToscaTemplate toscaTemplate, final ToscaNodeType nsNodeType) { final Nsd nsd = new Nsd(); nsd.setDesigner(getProperty(nsNodeType, Nsd.DESIGNER_PROPERTY)); nsd.setVersion(getProperty(nsNodeType, Nsd.VERSION_PROPERTY)); nsd.setName(getProperty(nsNodeType, Nsd.NAME_PROPERTY)); nsd.setInvariantId(getProperty(nsNodeType, Nsd.INVARIANT_ID_PROPERTY)); final ToscaTemplateYamlGenerator yamlParserProvider = toscaTemplateYamlGeneratorProvider.getObject(toscaTemplate); final byte[] contents = yamlParserProvider.parseToYamlString().getBytes(); nsd.setContents(contents); final List interfaceImplementations = getInterfaceImplementations(toscaTemplate); nsd.setArtifactReferences(interfaceImplementations); return nsd; } private List getInterfaceImplementations(final ToscaTemplate template) { if (template.getTopology_template().getNode_templates() == null) { return Collections.emptyList(); } final List interfaceImplementations = new ArrayList<>(); final Collection nodeTemplates = template.getTopology_template().getNode_templates().values(); nodeTemplates.stream().filter(toscaNodeTemplate -> toscaNodeTemplate.getInterfaces() != null).forEach( toscaNodeTemplate -> toscaNodeTemplate.getInterfaces().values() .forEach(interfaceInstance -> interfaceImplementations.addAll(getInterfaceImplementations(interfaceInstance)))); return interfaceImplementations; } private Collection getInterfaceImplementations(final Object interfaceInstance) { final Collection interfaceImplementations = new ArrayList<>(); if (interfaceInstance instanceof Map) { for (final Object value : ((Map) interfaceInstance).values()) { if (value instanceof Map && ((Map) value).get("implementation") != null) { interfaceImplementations.add(((Map) value).get("implementation").toString()); } } } return interfaceImplementations; } private String getProperty(final ToscaNodeType nodeType, final String propertyName) { final ToscaProperty toscaProperty = nodeType.getProperties().get(propertyName); final String errorMsg = String.format("Property '%s' must be defined and must have a valid values constraint", propertyName); final String returnValueOnError = "unknown"; if (toscaProperty == null || CollectionUtils.isEmpty(toscaProperty.getConstraints())) { LOGGER.error(errorMsg); return returnValueOnError; } final ToscaPropertyConstraint toscaPropertyConstraint = toscaProperty.getConstraints().get(0); if (ConstraintType.VALID_VALUES != toscaPropertyConstraint.getConstraintType()) { LOGGER.error(errorMsg); return returnValueOnError; } final ToscaPropertyConstraintValidValues validValuesConstraint = (ToscaPropertyConstraintValidValues) toscaPropertyConstraint; final List validValues = validValuesConstraint.getValidValues(); if (CollectionUtils.isEmpty(validValues)) { LOGGER.error(errorMsg); return returnValueOnError; } return validValues.get(0); } private ToscaTemplate createNetworkServiceDescriptor(final Component component, final List vnfDescriptorList) throws NsdException { final ToscaTemplate componentToscaTemplate = parseToToscaTemplate(component); final ToscaTemplate componentToscaTemplateInterface = exportComponentInterfaceAsToscaTemplate(component); final Entry firstNodeTypeEntry = componentToscaTemplateInterface.getNode_types().entrySet().stream().findFirst() .orElse(null); if (firstNodeTypeEntry == null) { throw new NsdException("Could not find abstract Service type"); } final String nsNodeTypeName = firstNodeTypeEntry.getKey(); final ToscaNodeType nsNodeType = firstNodeTypeEntry.getValue(); final Map nodeTypeMap = new HashMap<>(); nodeTypeMap.put(nsNodeTypeName, createEtsiSolNsNodeType(nsNodeType, componentToscaTemplate)); if (componentToscaTemplate.getNode_types() == null) { componentToscaTemplate.setNode_types(nodeTypeMap); } else { componentToscaTemplate.getNode_types().putAll(nodeTypeMap); } handleNodeTemplates(componentToscaTemplate); removeOnapAndEtsiNsdPropertiesFromInputs(componentToscaTemplate); handleSubstitutionMappings(componentToscaTemplate, nsNodeTypeName); final Map nodeTemplates = new HashMap<>(); nodeTemplates.put(nsNodeTypeName, createNodeTemplateForNsNodeType(nsNodeTypeName, componentToscaTemplateInterface.getNode_types().get(nsNodeTypeName))); if (componentToscaTemplate.getTopology_template().getNode_templates() == null) { componentToscaTemplate.getTopology_template().setNode_templates(nodeTemplates); } else { setNodeTemplateTypesForVnfs(componentToscaTemplate, vnfDescriptorList); componentToscaTemplate.getTopology_template().getNode_templates().putAll(nodeTemplates); } removeOnapMetaData(componentToscaTemplate); setDefaultImportsForEtsiSolNsNsd(componentToscaTemplate, vnfDescriptorList); return componentToscaTemplate; } private void handleSubstitutionMappings(final ToscaTemplate componentToscaTemplate, final String nsNodeTypeName) { final SubstitutionMapping substitutionMapping = new SubstitutionMapping(); substitutionMapping.setNode_type(nsNodeTypeName); final SubstitutionMapping onapSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings(); if (onapSubstitutionMapping != null && onapSubstitutionMapping.getRequirements() != null) { substitutionMapping.setRequirements(adjustRequirementNamesToMatchVnfd(onapSubstitutionMapping.getRequirements())); } componentToscaTemplate.getTopology_template().setSubstitution_mappings(substitutionMapping); } private Map adjustRequirementNamesToMatchVnfd(final Map requirements) { for (final Map.Entry entry : requirements.entrySet()) { try { final String[] adjustedValue = {entry.getValue()[0], entry.getValue()[1].substring(entry.getValue()[1].lastIndexOf('.') + 1)}; entry.setValue(adjustedValue); } catch (final ArrayIndexOutOfBoundsException exception) { LOGGER.error("Malformed requirement: {}", entry); } } return requirements; } private void setNodeTemplateTypesForVnfs(final ToscaTemplate template, final List vnfDescriptorList) { if (CollectionUtils.isEmpty(vnfDescriptorList)) { return; } final Map nodeTemplateMap = template.getTopology_template().getNode_templates(); if (MapUtils.isEmpty(nodeTemplateMap)) { return; } nodeTemplateMap.forEach( (key, toscaNodeTemplate) -> vnfDescriptorList.stream().filter(vnfDescriptor -> key.equals(vnfDescriptor.getName())).findFirst() .ifPresent(vnfDescriptor -> toscaNodeTemplate.setType(vnfDescriptor.getNodeType()))); } private void handleNodeTemplates(final ToscaTemplate template) { final Map nodeTemplateMap = template.getTopology_template().getNode_templates(); if (MapUtils.isEmpty(nodeTemplateMap)) { return; } for (final Entry nodeTemplate : nodeTemplateMap.entrySet()) { setPropertiesForNodeTemplate(nodeTemplate); setRequirementsForNodeTemplate(nodeTemplate); removeCapabilitiesFromNodeTemplate(nodeTemplate); } } private void setPropertiesForNodeTemplate(final Entry nodeTemplate) { final Map propertyMap = nodeTemplate.getValue().getProperties(); if (MapUtils.isEmpty(propertyMap)) { nodeTemplate.getValue().setProperties(null); return; } final Map editedPropertyMap = new HashMap<>(); for (final Entry property : propertyMap.entrySet()) { if (!PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TEMPLATE.contains(property.getKey()) && propertyIsDefinedInNodeType( property.getKey())) { editedPropertyMap.put(property.getKey(), property.getValue()); } } if (editedPropertyMap.isEmpty()) { nodeTemplate.getValue().setProperties(null); } else { nodeTemplate.getValue().setProperties(editedPropertyMap); } } private void setRequirementsForNodeTemplate(final Entry nodeTemplateMap) { final List> requirementAssignments = nodeTemplateMap.getValue().getRequirements(); if (requirementAssignments != null) { final List> requirementAssignmentsMatchingVnfdRequirements = new ArrayList<>(); for (final Map requirementAssignment : requirementAssignments) { final Map requirementAssignmentMatchingVnfd = requirementAssignment.entrySet().stream() .collect(Collectors.toMap(entry -> entry.getKey().substring(entry.getKey().lastIndexOf('.') + 1), Map.Entry::getValue)); requirementAssignmentsMatchingVnfdRequirements.add(requirementAssignmentMatchingVnfd); } nodeTemplateMap.getValue().setRequirements(requirementAssignmentsMatchingVnfdRequirements); } } private void removeCapabilitiesFromNodeTemplate(final Entry nodeTemplate) { nodeTemplate.getValue().setCapabilities(null); } private void removeOnapAndEtsiNsdPropertiesFromInputs(final ToscaTemplate template) { final ToscaTopolgyTemplate topologyTemplate = template.getTopology_template(); final Map inputMap = topologyTemplate.getInputs(); if (MapUtils.isNotEmpty(inputMap)) { inputMap.entrySet().removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey()) || ETSI_SOL_NSD_NS_NODE_TYPE_PROPERTIES.contains(entry.getKey())); } if (MapUtils.isEmpty(inputMap)) { topologyTemplate.setInputs(null); } } private void removeOnapMetaData(final ToscaTemplate template) { template.setMetadata(null); final Map nodeTemplateMap = template.getTopology_template().getNode_templates(); if (MapUtils.isEmpty(nodeTemplateMap)) { return; } nodeTemplateMap.values().forEach(toscaNodeTemplate -> toscaNodeTemplate.setMetadata(null)); } private void setDefaultImportsForEtsiSolNsNsd(final ToscaTemplate template, final List vnfDescriptorList) { final List>> importEntryMap = new ArrayList<>(); final Map> defaultImportEntryMap = generateDefaultImportEntry(); if (MapUtils.isNotEmpty(defaultImportEntryMap)) { importEntryMap.add(defaultImportEntryMap); } if (CollectionUtils.isNotEmpty(vnfDescriptorList)) { for (final VnfDescriptor vnfDescriptor : vnfDescriptorList) { final Map vnfImportChildEntry = new HashMap<>(); vnfImportChildEntry.put("file", vnfDescriptor.getVnfdFileName()); final Map> vnfdImportVnfdEntry = new HashMap<>(); vnfdImportVnfdEntry.put(vnfDescriptor.getName(), vnfImportChildEntry); importEntryMap.add(vnfdImportVnfdEntry); } } template.setImports(importEntryMap); } private Map> generateDefaultImportEntry() { return Map.of("etsi_nfv_sol001_nsd_types", Map.of("file", "etsi_nfv_sol001_nsd_types.yaml")); } private ToscaNodeType createEtsiSolNsNodeType(final ToscaNodeType nsNodeType, final ToscaTemplate componentToscaTemplate) { final ToscaNodeType toscaNodeType = new ToscaNodeType(); toscaNodeType.setDerived_from(NS_TOSCA_TYPE); final Map propertiesInNsNodeType = nsNodeType.getProperties(); for (final Entry property : propertiesInNsNodeType.entrySet()) { final ToscaProperty toscaProperty = property.getValue(); if (toscaProperty.getDefaultp() != null && ETSI_SOL_NSD_NS_NODE_TYPE_PROPERTIES.contains(property.getKey())) { final ToscaPropertyConstraintValidValues constraint = new ToscaPropertyConstraintValidValues( Collections.singletonList(toscaProperty.getDefaultp().toString())); toscaProperty.setConstraints(Collections.singletonList(constraint)); } } propertiesInNsNodeType.entrySet().removeIf(entry -> PROPERTIES_TO_EXCLUDE_FROM_ETSI_SOL_NSD_NS_NODE_TYPE.contains(entry.getKey())); toscaNodeType.setProperties(propertiesInNsNodeType); final List> requirementsInNsNodeType = getRequirementsForNsNodeType(nsNodeType.getRequirements(), componentToscaTemplate); if (!requirementsInNsNodeType.isEmpty()) { toscaNodeType.setRequirements(requirementsInNsNodeType); } return toscaNodeType; } private List> getRequirementsForNsNodeType(final List> requirements, final ToscaTemplate componentToscaTemplate) { final Map requirementsInSubstitutionMapping = componentToscaTemplate.getTopology_template().getSubstitution_mappings() .getRequirements(); if (requirements == null || MapUtils.isEmpty(requirementsInSubstitutionMapping)) { return Collections.emptyList(); } final List> requirementsToAdd = new ArrayList<>(); for (final Map requirementMap : requirements) { final Map neededRequirements = requirementMap.entrySet().stream() .filter(entry -> requirementsInSubstitutionMapping.containsKey(entry.getKey())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); if (!neededRequirements.isEmpty()) { requirementsToAdd.add(neededRequirements); } } return requirementsToAdd; } private boolean propertyIsDefinedInNodeType(final String propertyName) { // This will achieve what we want for now, but will look into a more generic solution which would involve // checking the node_type definition in the VNFD return !propertyName.equals("additional_parameters"); } private ToscaNodeTemplate createNodeTemplateForNsNodeType(final String nodeType, final ToscaNodeType toscaNodeType) { final ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate(); nodeTemplate.setType(nodeType); final Map properties = toscaNodeType.getProperties(); final Map nodeTemplateProperties = new HashMap<>(); for (final Entry property : properties.entrySet()) { if (property.getValue().getDefaultp() != null) { nodeTemplateProperties.put(property.getKey(), property.getValue().getDefaultp()); } } if (!nodeTemplateProperties.isEmpty()) { nodeTemplate.setProperties(nodeTemplateProperties); } final Map interfaces = toscaNodeType.getInterfaces(); if (interfaces != null) { for (final Entry nodeInterface : interfaces.entrySet()) { if ("Nslcm".equals(nodeInterface.getKey()) && nodeInterface.getValue() instanceof Map) { ((Map) nodeInterface.getValue()).remove("type"); } } nodeTemplate.setInterfaces(interfaces); } return nodeTemplate; } private ToscaTemplate parseToToscaTemplate(final Component component) throws NsdException { final Either toscaTemplateRes = toscaExportHandler.convertToToscaTemplate(component); if (toscaTemplateRes.isRight()) { String errorMsg = String .format("Could not parse component '%s' to tosca template. Error '%s'", component.getName(), toscaTemplateRes.right().value().name()); throw new NsdException(errorMsg); } return toscaTemplateRes.left().value(); } private ToscaTemplate exportComponentInterfaceAsToscaTemplate(final Component component) throws NsdException { if (null == DEFAULT_IMPORTS) { throw new NsdException("Could not load default CSAR imports from configuration"); } final ToscaTemplate toscaTemplate = new ToscaTemplate(TOSCA_VERSION); toscaTemplate.setImports(new ArrayList<>(DEFAULT_IMPORTS)); final Either toscaTemplateRes = toscaExportHandler .convertInterfaceNodeType(new HashMap<>(), component, toscaTemplate, new HashMap<>(), false); if (toscaTemplateRes.isRight()) { throw new NsdException(String.format("Could not create abstract service from component '%s'", component.getName())); } return toscaTemplateRes.left().value(); } }