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 static org.openecomp.sdc.common.api.ArtifactTypeEnum.ETSI_PACKAGE;
23 import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ONBOARDED_PACKAGE;
25 import fj.data.Either;
26 import java.io.IOException;
27 import java.util.ArrayList;
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;
35 import java.util.zip.ZipEntry;
36 import java.util.zip.ZipOutputStream;
37 import org.apache.commons.collections.CollectionUtils;
38 import org.apache.commons.collections.MapUtils;
39 import org.apache.commons.io.IOUtils;
40 import org.apache.commons.io.output.ByteArrayOutputStream;
41 import org.apache.commons.lang.StringUtils;
42 import org.openecomp.sdc.be.dao.cassandra.ArtifactCassandraDao;
43 import org.openecomp.sdc.be.dao.cassandra.CassandraOperationStatus;
44 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
45 import org.openecomp.sdc.be.model.ArtifactDefinition;
46 import org.openecomp.sdc.be.model.Component;
47 import org.openecomp.sdc.be.model.ComponentInstance;
48 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder;
49 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdToscaMetadataBuilder;
50 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
51 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.factory.NsDescriptorGeneratorFactory;
52 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion;
53 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorConfig;
54 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorVersionComparator;
55 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
56 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
57 import org.openecomp.sdc.be.resources.data.DAOArtifactData;
58 import org.openecomp.sdc.be.tosca.utils.OperationArtifactUtil;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.beans.factory.config.BeanDefinition;
62 import org.springframework.context.annotation.Scope;
63 import org.springframework.core.io.Resource;
64 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
67 * Implementation of a ETSI NFV NSD CSAR generator
69 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
70 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
71 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
73 private static final Logger LOGGER = LoggerFactory.getLogger(EtsiNfvNsdCsarGeneratorImpl.class);
74 private static final String MANIFEST_EXT = "mf";
75 private static final String SLASH = "/";
76 private static final String DOT = ".";
77 private static final String DOT_YAML = DOT + "yaml";
78 private static final String DEFINITION = "Definitions";
79 private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta";
80 private final VnfDescriptorGenerator vnfDescriptorGenerator;
81 private final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory;
82 private final ArtifactCassandraDao artifactCassandraDao;
83 private final NsDescriptorConfig nsDescriptorConfig;
85 public EtsiNfvNsdCsarGeneratorImpl(final NsDescriptorConfig nsDescriptorConfig, final VnfDescriptorGenerator vnfDescriptorGenerator,
86 final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory,
87 final ArtifactCassandraDao artifactCassandraDao) {
88 this.nsDescriptorConfig = nsDescriptorConfig;
89 this.vnfDescriptorGenerator = vnfDescriptorGenerator;
90 this.nsDescriptorGeneratorFactory = nsDescriptorGeneratorFactory;
91 this.artifactCassandraDao = artifactCassandraDao;
95 public byte[] generateNsdCsar(final Component component) throws NsdException {
96 if (component == null) {
97 throw new NsdException("Could not generate the NSD CSAR, invalid component argument");
99 loadComponentArtifacts(component);
100 loadComponentInstancesArtifacts(component);
101 final String componentName = component.getName();
103 LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
104 final Map<String, byte[]> nsdCsarFiles = new HashMap<>();
105 final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
106 vnfDescriptorList.forEach(vnfPackage -> nsdCsarFiles.putAll(vnfPackage.getDefinitionFiles()));
107 final String nsdFileName = getNsdFileName(component);
108 final EtsiVersion etsiVersion = nsDescriptorConfig.getNsVersion();
109 final Nsd nsd = generateNsd(component, vnfDescriptorList);
110 nsdCsarFiles.put(getNsdPath(nsdFileName), nsd.getContents());
111 nsdCsarFiles.put(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes());
112 addEtsiSolNsdTypes(etsiVersion, nsdCsarFiles);
113 for (final String referencedFile : nsd.getArtifactReferences()) {
114 getReferencedArtifact(component, referencedFile)
115 .ifPresent(artifactDefinition -> nsdCsarFiles.put(referencedFile, artifactDefinition.getPayloadData()));
117 nsdCsarFiles.put(getManifestPath(nsdFileName), getManifestFileContent(nsd, etsiVersion, nsdCsarFiles.keySet()).getBytes());
118 final byte[] csar = buildCsarPackage(nsdCsarFiles);
119 LOGGER.debug("Successfully generated NSD CSAR package");
121 } catch (final Exception exception) {
122 throw new NsdException("Could not generate the NSD CSAR file", exception);
126 private void loadComponentArtifacts(final Component component) {
127 final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
128 if (allArtifactsMap == null) {
131 allArtifactsMap.keySet().forEach(key -> {
132 final ArtifactDefinition artifactDefinition = allArtifactsMap.get(key);
133 if (StringUtils.isNotEmpty(artifactDefinition.getEsId())) {
134 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
135 if (artifactPayload.isPresent()) {
136 artifactDefinition.setPayload(artifactPayload.get());
138 LOGGER.warn("Could not load component '{}' artifact '{}'", component.getName(), artifactDefinition.getArtifactName());
144 private void loadComponentInstancesArtifacts(final Component component) {
145 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
146 if (CollectionUtils.isEmpty(componentInstanceList)) {
149 for (final ComponentInstance componentInstance : componentInstanceList) {
150 final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
151 if (MapUtils.isEmpty(deploymentArtifacts)) {
154 deploymentArtifacts.values().stream().filter(artifactDefinition -> StringUtils.isNotEmpty(artifactDefinition.getEsId()))
155 .forEach(artifactDefinition -> {
156 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
157 if (artifactPayload.isPresent()) {
158 artifactDefinition.setPayload(artifactPayload.get());
160 LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'", component.getName(), componentInstance.getName(),
161 artifactDefinition.getArtifactName());
167 private List<VnfDescriptor> generateVnfPackages(final Component component) throws NsdException {
168 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
169 if (CollectionUtils.isEmpty(componentInstanceList)) {
170 LOGGER.warn("Could not find any instance in service '{}'", component.getName());
171 return Collections.emptyList();
173 final List<VnfDescriptor> vnfDescriptorList = new ArrayList<>();
174 for (final ComponentInstance componentInstance : componentInstanceList) {
175 final String componentInstanceName = componentInstance.getName();
176 final ArtifactDefinition onboardedCsarArtifact = findOnboardedCsar(componentInstance).orElse(null);
177 if (onboardedCsarArtifact == null) {
178 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
181 final Optional<VnfDescriptor> vnfPackage;
183 vnfPackage = vnfDescriptorGenerator.generate(componentInstanceName, onboardedCsarArtifact);
184 } catch (final Exception e) {
185 final String errorMsg = String.format("Could not generate VNF package for component instance %s", componentInstanceName);
186 throw new NsdException(errorMsg, e);
188 if (vnfPackage.isPresent()) {
189 vnfDescriptorList.add(vnfPackage.get());
191 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
194 return vnfDescriptorList;
197 private Optional<ArtifactDefinition> findOnboardedCsar(final ComponentInstance componentInstance) {
198 final Map<String, ArtifactDefinition> artifactDefinitionMap = componentInstance.getDeploymentArtifacts();
199 if (artifactDefinitionMap == null || artifactDefinitionMap.isEmpty()) {
200 return Optional.empty();
202 return artifactDefinitionMap.values().stream().filter(artifactDefinition -> {
203 final String artifactType = (String) artifactDefinition.getToscaPresentationValue(JsonPresentationFields.ARTIFACT_TYPE);
204 return ONBOARDED_PACKAGE.getType().equals(artifactType) || ETSI_PACKAGE.getType().equals(artifactType);
208 private void addEtsiSolNsdTypes(final EtsiVersion etsiVersion, final Map<String, byte[]> nsdCsarFileMap) {
209 final EtsiVersion currentVersion = etsiVersion == null ? EtsiVersion.getDefaultVersion() : etsiVersion;
210 final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
212 final Resource[] resources =
213 resolver.getResources(String.format("classpath:etsi-nfv-types/%s/*.*", currentVersion.getVersion()));
214 if (resources.length > 0) {
215 for (final Resource resource : resources) {
216 addToCsarFileMap(resource, nsdCsarFileMap);
219 } catch (final IOException e) {
220 LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e);
224 private void addToCsarFileMap(final Resource resource, final Map<String, byte[]> nsdCsarFileMap) {
226 nsdCsarFileMap.put(DEFINITION + "/" + resource.getFilename(),
227 IOUtils.toByteArray(resource.getInputStream()));
228 } catch (final IOException exception) {
229 LOGGER.error("Error adding '{}' to NSD CSAR", resource.getFilename(), exception);
233 private Nsd generateNsd(final Component component,
234 final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
235 final NsDescriptorGenerator nsDescriptorGenerator =
236 nsDescriptorGeneratorFactory.create();
237 return nsDescriptorGenerator.generate(component, vnfDescriptorList)
239 new NsdException(String
240 .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
244 private Optional<ArtifactDefinition> getReferencedArtifact(final Component component,
245 final String filePath) throws NsdException {
246 final Map<String, ArtifactDefinition> interfaceOperationArtifactsByName =
247 OperationArtifactUtil.getDistinctInterfaceOperationArtifactsByName(component);
248 final String[] pathComponents = filePath.split(SLASH);
249 final String artifactName = pathComponents[pathComponents.length - 1];
250 final ArtifactDefinition artifactDefinition = interfaceOperationArtifactsByName.get(artifactName);
251 if (artifactDefinition == null) {
252 throw new NsdException(String.format("Could not find artifact '%s'", filePath));
254 LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
255 artifactDefinition.getUniqueId());
256 if (artifactDefinition.getPayloadData() == null) {
257 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
259 if (artifactPayload.isEmpty()) {
260 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
262 artifactDefinition.setPayload(artifactPayload.get());
265 return Optional.of(artifactDefinition);
268 private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
269 final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
270 .getArtifact(artifactCassandraId);
272 if (artifactResponse.isRight()) {
273 LOGGER.debug("Failed to fetch artifact from Cassandra by id {} error {} ", artifactCassandraId,
274 artifactResponse.right().value());
275 return Optional.empty();
277 final DAOArtifactData artifactData = artifactResponse.left().value();
278 return Optional.of(artifactData.getDataAsArray());
281 private String buildToscaMetaContent(final String nsdFileName) {
282 LOGGER.debug("Creating TOSCA.meta content");
283 final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
285 builder.withCsarVersion("1.1")
286 .withCreatedBy("ONAP")
287 .withToscaMetaVersion("1.0")
288 .withEntryDefinitions(getNsdPath(nsdFileName))
289 .withEntryManifest(getManifestPath(nsdFileName))
290 .withEntryChangeLog("ChangeLog.txt");
292 final String toscaMetadata = builder.build();
293 LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
294 return toscaMetadata;
297 private String getManifestFileContent(final Nsd nsd,
298 final EtsiVersion nsdVersion,
299 final Set<String> files) {
300 LOGGER.debug("Creating NS manifest file content");
302 final NsdCsarManifestBuilder nsdCsarManifestBuilder = new NsdCsarManifestBuilder();
303 nsdCsarManifestBuilder.withDesigner(nsd.getDesigner())
304 .withInvariantId(nsd.getInvariantId())
305 .withName(nsd.getName())
306 .withNowReleaseDateTime()
307 .withFileStructureVersion(nsd.getVersion())
310 final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator();
312 if (nsdVersion != null && nsdVersionComparator.compare(nsdVersion, EtsiVersion.VERSION_3_3_1) >= 0) {
313 nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion());
316 final String manifest = nsdCsarManifestBuilder.build();
317 LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", manifest);
322 private String getManifestPath(final String nsdFileName) {
323 return nsdFileName + DOT + MANIFEST_EXT;
326 private String getNsdPath(final String nsdFileName) {
327 return DEFINITION + SLASH + nsdFileName + DOT_YAML;
330 private String getNsdFileName(final Component component) {
331 return component.getNormalizedName();
334 private byte[] buildCsarPackage(final Map<String, byte[]> nsdCsarFileMap) throws NsdException {
335 if (nsdCsarFileMap.isEmpty()) {
336 throw new NsdException("No files were provided to build the NSD CSAR package");
338 try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
339 final ZipOutputStream zip = new ZipOutputStream(out)) {
340 for (final Entry<String, byte[]> entry : nsdCsarFileMap.entrySet()) {
341 final String filePath = entry.getKey();
342 final byte[] fileContent = entry.getValue();
343 if (fileContent == null) {
344 LOGGER.error("Could not add '{}' to NSD CSAR. File content is null", filePath);
347 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
348 zip.putNextEntry(new ZipEntry(filePath));
349 zip.write(fileContent);
353 LOGGER.debug("NSD CSAR zip file was successfully built");
355 return out.toByteArray();
356 } catch (final IOException e) {
357 throw new NsdException("Could not build the NSD CSAR zip file", e);