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 static org.openecomp.sdc.common.api.ArtifactTypeEnum.ETSI_PACKAGE;
22 import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ONBOARDED_PACKAGE;
24 import fj.data.Either;
26 import java.io.IOException;
27 import java.nio.file.Files;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
34 import java.util.Map.Entry;
35 import java.util.Optional;
37 import java.util.zip.ZipEntry;
38 import java.util.zip.ZipOutputStream;
39 import org.apache.commons.collections.CollectionUtils;
40 import org.apache.commons.collections.MapUtils;
41 import org.apache.commons.io.FilenameUtils;
42 import org.apache.commons.io.IOUtils;
43 import org.apache.commons.io.output.ByteArrayOutputStream;
44 import org.apache.commons.lang.StringUtils;
45 import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo;
46 import org.openecomp.sdc.be.dao.cassandra.ArtifactCassandraDao;
47 import org.openecomp.sdc.be.dao.cassandra.CassandraOperationStatus;
48 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
49 import org.openecomp.sdc.be.model.ArtifactDefinition;
50 import org.openecomp.sdc.be.model.Component;
51 import org.openecomp.sdc.be.model.ComponentInstance;
52 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder;
53 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdToscaMetadataBuilder;
54 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
55 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.factory.NsDescriptorGeneratorFactory;
56 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion;
57 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorConfig;
58 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorVersionComparator;
59 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
60 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar;
61 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
62 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer;
63 import org.openecomp.sdc.be.resources.data.DAOArtifactData;
64 import org.openecomp.sdc.be.tosca.utils.OperationArtifactUtil;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67 import org.springframework.beans.factory.config.BeanDefinition;
68 import org.springframework.context.annotation.Scope;
69 import org.springframework.core.io.Resource;
70 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
73 * Implementation of a ETSI NFV NSD CSAR generator
75 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
76 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
77 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
79 private static final Logger LOGGER = LoggerFactory.getLogger(EtsiNfvNsdCsarGeneratorImpl.class);
80 private static final String MANIFEST_EXT = "mf";
81 private static final String SLASH = "/";
82 private static final String DOT = ".";
83 private static final String SIGNATURE_EXTENSION = ".sig.cms";
84 private static final String CSAR_EXTENSION = ".csar";
85 private static final String DOT_YAML = DOT + "yaml";
86 private static final String DEFINITION = "Definitions";
87 private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta";
88 private final VnfDescriptorGenerator vnfDescriptorGenerator;
89 private final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory;
90 private final ArtifactCassandraDao artifactCassandraDao;
91 private final NsDescriptorConfig nsDescriptorConfig;
92 private final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer;
94 public EtsiNfvNsdCsarGeneratorImpl(final NsDescriptorConfig nsDescriptorConfig, final VnfDescriptorGenerator vnfDescriptorGenerator,
95 final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory,
96 final ArtifactCassandraDao artifactCassandraDao,
97 final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer) {
98 this.nsDescriptorConfig = nsDescriptorConfig;
99 this.vnfDescriptorGenerator = vnfDescriptorGenerator;
100 this.nsDescriptorGeneratorFactory = nsDescriptorGeneratorFactory;
101 this.artifactCassandraDao = artifactCassandraDao;
102 this.nsdCsarEtsiOption2Signer = nsdCsarEtsiOption2Signer;
106 public NsdCsar generateNsdCsar(final Component component) throws NsdException {
107 if (component == null) {
108 throw new NsdException("Could not generate the NSD CSAR, invalid component argument");
110 loadComponentArtifacts(component);
111 loadComponentInstancesArtifacts(component);
112 final String componentName = component.getName();
114 LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
115 final String nsdFileName = getNsdFileName(component);
116 final NsdCsar nsdCsar = new NsdCsar(nsdFileName);
118 final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
119 vnfDescriptorList.forEach(vnfPackage -> nsdCsar.addAllFiles(vnfPackage.getDefinitionFiles()));
121 final EtsiVersion etsiVersion = nsDescriptorConfig.getNsVersion();
122 final Nsd nsd = generateNsd(component, vnfDescriptorList);
124 nsdCsar.addFile(getNsdPath(nsdFileName), nsd.getContents());
125 nsdCsar.addFile(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes());
127 nsdCsar.addAllFiles(createEtsiSolNsdTypeEntries(etsiVersion));
128 for (final String referencedFile : nsd.getArtifactReferences()) {
129 getReferencedArtifact(component, referencedFile)
130 .ifPresent(artifactDefinition -> nsdCsar.addFile(referencedFile, artifactDefinition.getPayloadData())
133 final boolean isCertificateConfigured = nsdCsarEtsiOption2Signer.isCertificateConfigured();
134 final String manifestPath = getManifestPath(nsdFileName);
135 final NsdCsarManifestBuilder manifestBuilder =
136 createManifestBuilder(nsd, etsiVersion, manifestPath, nsdCsar.getFileMap().keySet(), isCertificateConfigured);
138 nsdCsar.addManifest(manifestBuilder);
140 if (isCertificateConfigured) {
141 nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar);
144 byte[] package1 = buildCsarPackage(nsdCsar.getFileMap());
145 if (isCertificateConfigured) {
146 package1 = buildZipWithCsarAndSignature(nsdCsar.getFileName(), package1);
147 nsdCsar.setSigned(true);
149 nsdCsar.setCsarPackage(package1);
150 LOGGER.debug("Successfully generated NSD CSAR package");
152 } catch (final Exception exception) {
153 throw new NsdException("Could not generate the NSD CSAR file", exception);
157 private void loadComponentArtifacts(final Component component) {
158 final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
159 if (allArtifactsMap == null) {
162 allArtifactsMap.keySet().forEach(key -> {
163 final ArtifactDefinition artifactDefinition = allArtifactsMap.get(key);
164 if (StringUtils.isNotEmpty(artifactDefinition.getEsId())) {
165 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
166 if (artifactPayload.isPresent()) {
167 artifactDefinition.setPayload(artifactPayload.get());
169 LOGGER.warn("Could not load component '{}' artifact '{}'", component.getName(), artifactDefinition.getArtifactName());
175 private void loadComponentInstancesArtifacts(final Component component) {
176 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
177 if (CollectionUtils.isEmpty(componentInstanceList)) {
180 for (final ComponentInstance componentInstance : componentInstanceList) {
181 final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
182 if (MapUtils.isEmpty(deploymentArtifacts)) {
185 deploymentArtifacts.values().stream().filter(artifactDefinition -> StringUtils.isNotEmpty(artifactDefinition.getEsId()))
186 .forEach(artifactDefinition -> {
187 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
188 if (artifactPayload.isPresent()) {
189 artifactDefinition.setPayload(artifactPayload.get());
191 LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'", component.getName(), componentInstance.getName(),
192 artifactDefinition.getArtifactName());
198 private List<VnfDescriptor> generateVnfPackages(final Component component) throws NsdException {
199 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
200 if (CollectionUtils.isEmpty(componentInstanceList)) {
201 LOGGER.warn("Could not find any instance in service '{}'", component.getName());
202 return Collections.emptyList();
204 final List<VnfDescriptor> vnfDescriptorList = new ArrayList<>();
205 for (final ComponentInstance componentInstance : componentInstanceList) {
206 final String componentInstanceName = componentInstance.getName();
207 final ArtifactDefinition onboardedCsarArtifact = findOnboardedCsar(componentInstance).orElse(null);
208 if (onboardedCsarArtifact == null) {
209 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
212 final Optional<VnfDescriptor> vnfPackage;
214 vnfPackage = vnfDescriptorGenerator.generate(componentInstanceName, onboardedCsarArtifact);
215 } catch (final Exception e) {
216 final String errorMsg = String.format("Could not generate VNF package for component instance %s", componentInstanceName);
217 throw new NsdException(errorMsg, e);
219 if (vnfPackage.isPresent()) {
220 vnfDescriptorList.add(vnfPackage.get());
222 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
225 return vnfDescriptorList;
228 private Optional<ArtifactDefinition> findOnboardedCsar(final ComponentInstance componentInstance) {
229 final Map<String, ArtifactDefinition> artifactDefinitionMap = componentInstance.getDeploymentArtifacts();
230 if (artifactDefinitionMap == null || artifactDefinitionMap.isEmpty()) {
231 return Optional.empty();
233 return artifactDefinitionMap.values().stream().filter(artifactDefinition -> {
234 final String artifactType = (String) artifactDefinition.getToscaPresentationValue(JsonPresentationFields.ARTIFACT_TYPE);
235 return ONBOARDED_PACKAGE.getType().equals(artifactType) || ETSI_PACKAGE.getType().equals(artifactType);
239 private Map<String, byte[]> createEtsiSolNsdTypeEntries(final EtsiVersion etsiVersion) {
240 final EtsiVersion currentVersion = etsiVersion == null ? EtsiVersion.getDefaultVersion() : etsiVersion;
242 final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
244 final Resource[] resources =
245 resolver.getResources(String.format("classpath:etsi-nfv-types/%s/*.*", currentVersion.getVersion()));
246 if (resources.length > 0) {
247 final Map<String, byte[]> entryMap = new HashMap<>();
248 for (final Resource resource : resources) {
249 readResource(resource).ifPresent(resourceBytes -> {
250 final String entryName = createDefinitionEntryName(resource.getFilename());
251 entryMap.put(entryName, resourceBytes);
256 } catch (final IOException e) {
257 LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e);
260 return Collections.emptyMap();
263 private String createDefinitionEntryName(final String fileName) {
264 return DEFINITION + "/" + fileName;
267 private Optional<byte[]> readResource(final Resource resource) {
269 return Optional.ofNullable(IOUtils.toByteArray(resource.getInputStream()));
270 } catch (final IOException exception) {
271 LOGGER.error("Error adding '{}' to NSD CSAR", resource.getFilename(), exception);
273 return Optional.empty();
276 private Nsd generateNsd(final Component component,
277 final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
278 final NsDescriptorGenerator nsDescriptorGenerator =
279 nsDescriptorGeneratorFactory.create();
280 return nsDescriptorGenerator.generate(component, vnfDescriptorList)
282 new NsdException(String
283 .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
287 private Optional<ArtifactDefinition> getReferencedArtifact(final Component component,
288 final String filePath) throws NsdException {
289 final Map<String, ArtifactDefinition> interfaceOperationArtifactsByName =
290 OperationArtifactUtil.getDistinctInterfaceOperationArtifactsByName(component);
291 final String[] pathComponents = filePath.split(SLASH);
292 final String artifactName = pathComponents[pathComponents.length - 1];
293 final ArtifactDefinition artifactDefinition = interfaceOperationArtifactsByName.get(artifactName);
294 if (artifactDefinition == null) {
295 throw new NsdException(String.format("Could not find artifact '%s'", filePath));
297 LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
298 artifactDefinition.getUniqueId());
299 if (artifactDefinition.getPayloadData() == null) {
300 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
302 if (artifactPayload.isEmpty()) {
303 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
305 artifactDefinition.setPayload(artifactPayload.get());
308 return Optional.of(artifactDefinition);
311 private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
312 final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
313 .getArtifact(artifactCassandraId);
315 if (artifactResponse.isRight()) {
316 LOGGER.debug("Failed to fetch artifact from Cassandra by id {} error {} ", artifactCassandraId,
317 artifactResponse.right().value());
318 return Optional.empty();
320 final DAOArtifactData artifactData = artifactResponse.left().value();
321 return Optional.of(artifactData.getDataAsArray());
324 private String buildToscaMetaContent(final String nsdFileName) {
325 LOGGER.debug("Creating TOSCA.meta content");
326 final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
328 builder.withCsarVersion("1.1")
329 .withCreatedBy("ONAP")
330 .withToscaMetaVersion("1.0")
331 .withEntryDefinitions(getNsdPath(nsdFileName))
332 .withEntryManifest(getManifestPath(nsdFileName))
333 .withEntryChangeLog("ChangeLog.txt");
335 final String toscaMetadata = builder.build();
336 LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
337 return toscaMetadata;
340 private NsdCsarManifestBuilder createManifestBuilder(final Nsd nsd, final EtsiVersion nsdVersion,
341 final String manifestFilePath, final Set<String> filePath,
342 final boolean addSignatureFiles) {
343 LOGGER.debug("Creating NS manifest file content");
345 final Set<String> filesToAdd = new HashSet<>(filePath);
346 if (addSignatureFiles) {
347 filePath.forEach(file -> filesToAdd.add(file + SIGNATURE_EXTENSION));
349 filesToAdd.add(manifestFilePath);
351 final NsdCsarManifestBuilder nsdCsarManifestBuilder = new NsdCsarManifestBuilder();
352 nsdCsarManifestBuilder.withDesigner(nsd.getDesigner())
353 .withInvariantId(nsd.getInvariantId())
354 .withName(nsd.getName())
355 .withNowReleaseDateTime()
356 .withFileStructureVersion(nsd.getVersion())
357 .withSources(filesToAdd);
359 final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator();
361 if (nsdVersion != null && nsdVersionComparator.compare(nsdVersion, EtsiVersion.VERSION_3_3_1) >= 0) {
362 nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion());
365 if (LOGGER.isDebugEnabled()) {
366 LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", nsdCsarManifestBuilder.build());
368 return nsdCsarManifestBuilder;
371 private String getManifestPath(final String nsdFileName) {
372 return nsdFileName + DOT + MANIFEST_EXT;
375 private String getNsdPath(final String nsdFileName) {
376 return DEFINITION + SLASH + nsdFileName + DOT_YAML;
379 private String getNsdFileName(final Component component) {
380 return component.getNormalizedName();
383 private byte[] buildCsarPackage(final Map<String, byte[]> nsdCsarFileMap) throws NsdException {
384 if (nsdCsarFileMap.isEmpty()) {
385 throw new NsdException("No files were provided to build the NSD CSAR package");
387 try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
388 final ZipOutputStream zip = new ZipOutputStream(out)) {
389 for (final Entry<String, byte[]> entry : nsdCsarFileMap.entrySet()) {
390 final String filePath = entry.getKey();
391 final byte[] fileContent = entry.getValue();
392 if (fileContent == null) {
393 LOGGER.error("Could not add '{}' to NSD CSAR. File content is null", filePath);
396 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
397 zip.putNextEntry(new ZipEntry(filePath));
398 zip.write(fileContent);
402 LOGGER.debug("NSD CSAR zip file was successfully built");
404 return out.toByteArray();
405 } catch (final IOException e) {
406 throw new NsdException("Could not build the NSD CSAR zip file", e);
410 private byte[] buildZipWithCsarAndSignature(final String csarFileName, final byte[] csarPackageBytes) throws NsdException {
411 final byte[] signature;
413 signature = nsdCsarEtsiOption2Signer.sign(csarPackageBytes);
414 } catch (final Exception e) {
415 throw new NsdException(String.format("Could not sign the CSAR '%s'", csarFileName), e);
417 final Optional<CertificateInfo> certificateInfoOpt = nsdCsarEtsiOption2Signer.getSigningCertificate();
418 if (certificateInfoOpt.isEmpty()) {
419 throw new NsdException(String.format("Could not sign the CSAR '%s'. No certificate configured.", csarFileName));
421 final CertificateInfo certificateInfo = certificateInfoOpt.get();
422 try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
423 final ZipOutputStream zip = new ZipOutputStream(out)) {
424 zip.putNextEntry(new ZipEntry(csarFileName + CSAR_EXTENSION));
425 zip.write(csarPackageBytes);
426 zip.putNextEntry(new ZipEntry(csarFileName + SIGNATURE_EXTENSION));
427 zip.write(signature);
428 final File certificateFile = certificateInfo.getCertificateFile();
429 zip.putNextEntry(new ZipEntry(csarFileName + "." + FilenameUtils.getExtension(certificateFile.getName())));
430 zip.write(Files.readAllBytes(certificateFile.toPath()));
433 LOGGER.debug("NSD signed CSAR zip file was successfully built");
435 return out.toByteArray();
436 } catch (final IOException e) {
437 throw new NsdException("Could not build the NSD signed CSAR zip file", e);