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.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer.SIGNATURE_EXTENSION;
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;
27 import java.io.IOException;
28 import java.nio.file.Files;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
35 import java.util.Map.Entry;
36 import java.util.Optional;
38 import java.util.zip.ZipEntry;
39 import java.util.zip.ZipOutputStream;
40 import org.apache.commons.collections.CollectionUtils;
41 import org.apache.commons.collections.MapUtils;
42 import org.apache.commons.io.FilenameUtils;
43 import org.apache.commons.io.IOUtils;
44 import org.apache.commons.io.output.ByteArrayOutputStream;
45 import org.apache.commons.lang3.StringUtils;
46 import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo;
47 import org.openecomp.sdc.be.dao.cassandra.ArtifactCassandraDao;
48 import org.openecomp.sdc.be.dao.cassandra.CassandraOperationStatus;
49 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
50 import org.openecomp.sdc.be.model.ArtifactDefinition;
51 import org.openecomp.sdc.be.model.Component;
52 import org.openecomp.sdc.be.model.ComponentInstance;
53 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder;
54 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdToscaMetadataBuilder;
55 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
56 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.factory.NsDescriptorGeneratorFactory;
57 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion;
58 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorConfig;
59 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorVersionComparator;
60 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
61 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar;
62 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
63 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer;
64 import org.openecomp.sdc.be.resources.data.DAOArtifactData;
65 import org.openecomp.sdc.be.tosca.utils.OperationArtifactUtil;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68 import org.springframework.beans.factory.config.BeanDefinition;
69 import org.springframework.context.annotation.Scope;
70 import org.springframework.core.io.Resource;
71 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
74 * Implementation of a ETSI NFV NSD CSAR generator
76 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
77 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
78 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
80 private static final Logger LOGGER = LoggerFactory.getLogger(EtsiNfvNsdCsarGeneratorImpl.class);
81 private static final String MANIFEST_EXT = "mf";
82 private static final String SLASH = "/";
83 private static final String DOT = ".";
84 private static final String CSAR_SIGNATURE_EXTENSION = ".cms";
85 private static final String CSAR_EXTENSION = ".csar";
86 private static final String DOT_YAML = DOT + "yaml";
87 private static final String DEFINITION = "Definitions";
88 private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta";
89 private final VnfDescriptorGenerator vnfDescriptorGenerator;
90 private final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory;
91 private final ArtifactCassandraDao artifactCassandraDao;
92 private final NsDescriptorConfig nsDescriptorConfig;
93 private final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer;
95 public EtsiNfvNsdCsarGeneratorImpl(final NsDescriptorConfig nsDescriptorConfig, final VnfDescriptorGenerator vnfDescriptorGenerator,
96 final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory,
97 final ArtifactCassandraDao artifactCassandraDao,
98 final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer) {
99 this.nsDescriptorConfig = nsDescriptorConfig;
100 this.vnfDescriptorGenerator = vnfDescriptorGenerator;
101 this.nsDescriptorGeneratorFactory = nsDescriptorGeneratorFactory;
102 this.artifactCassandraDao = artifactCassandraDao;
103 this.nsdCsarEtsiOption2Signer = nsdCsarEtsiOption2Signer;
107 public NsdCsar generateNsdCsar(final Component component) throws NsdException {
108 if (component == null) {
109 throw new NsdException("Could not generate the NSD CSAR, invalid component argument");
111 loadComponentArtifacts(component);
112 loadComponentInstancesArtifacts(component);
113 final String componentName = component.getName();
115 LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
116 final String nsdFileName = getNsdFileName(component);
117 final NsdCsar nsdCsar = new NsdCsar(nsdFileName);
119 final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
120 vnfDescriptorList.forEach(vnfPackage -> nsdCsar.addAllFiles(vnfPackage.getDefinitionFiles()));
122 final EtsiVersion etsiVersion = nsDescriptorConfig.getNsVersion();
123 final Nsd nsd = generateNsd(component, vnfDescriptorList);
125 nsdCsar.addFile(getNsdPath(nsdFileName), nsd.getContents());
126 nsdCsar.addFile(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes());
128 nsdCsar.addAllFiles(createEtsiSolNsdTypeEntries(etsiVersion));
129 for (final String referencedFile : nsd.getArtifactReferences()) {
130 getReferencedArtifact(component, referencedFile)
131 .ifPresent(artifactDefinition -> nsdCsar.addFile(referencedFile, artifactDefinition.getPayloadData())
134 final boolean isCertificateConfigured = nsdCsarEtsiOption2Signer.isCertificateConfigured();
135 final String manifestPath = getManifestPath(nsdFileName);
136 final NsdCsarManifestBuilder manifestBuilder =
137 createManifestBuilder(nsd, etsiVersion, manifestPath, nsdCsar.getFileMap().keySet(), isCertificateConfigured);
139 nsdCsar.addManifest(manifestBuilder);
141 if (isCertificateConfigured) {
142 nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar);
145 byte[] package1 = buildCsarPackage(nsdCsar.getFileMap());
146 if (isCertificateConfigured) {
147 package1 = buildZipWithCsarAndSignature(nsdCsar.getFileName(), package1);
148 nsdCsar.setSigned(true);
150 nsdCsar.setCsarPackage(package1);
151 LOGGER.debug("Successfully generated NSD CSAR package");
153 } catch (final Exception exception) {
154 throw new NsdException("Could not generate the NSD CSAR file", exception);
158 private void loadComponentArtifacts(final Component component) {
159 final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
160 if (allArtifactsMap == null) {
163 allArtifactsMap.keySet().forEach(key -> {
164 final ArtifactDefinition artifactDefinition = allArtifactsMap.get(key);
165 if (StringUtils.isNotEmpty(artifactDefinition.getEsId())) {
166 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
167 if (artifactPayload.isPresent()) {
168 artifactDefinition.setPayload(artifactPayload.get());
170 LOGGER.warn("Could not load component '{}' artifact '{}'", component.getName(), artifactDefinition.getArtifactName());
176 private void loadComponentInstancesArtifacts(final Component component) {
177 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
178 if (CollectionUtils.isEmpty(componentInstanceList)) {
181 for (final ComponentInstance componentInstance : componentInstanceList) {
182 final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
183 if (MapUtils.isEmpty(deploymentArtifacts)) {
186 deploymentArtifacts.values().stream().filter(artifactDefinition -> StringUtils.isNotEmpty(artifactDefinition.getEsId()))
187 .forEach(artifactDefinition -> {
188 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
189 if (artifactPayload.isPresent()) {
190 artifactDefinition.setPayload(artifactPayload.get());
192 LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'", component.getName(), componentInstance.getName(),
193 artifactDefinition.getArtifactName());
199 private List<VnfDescriptor> generateVnfPackages(final Component component) throws NsdException {
200 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
201 if (CollectionUtils.isEmpty(componentInstanceList)) {
202 LOGGER.warn("Could not find any instance in service '{}'", component.getName());
203 return Collections.emptyList();
205 final List<VnfDescriptor> vnfDescriptorList = new ArrayList<>();
206 for (final ComponentInstance componentInstance : componentInstanceList) {
207 final String componentInstanceName = componentInstance.getName();
208 final ArtifactDefinition onboardedCsarArtifact = findOnboardedCsar(componentInstance).orElse(null);
209 if (onboardedCsarArtifact == null) {
210 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
213 final Optional<VnfDescriptor> vnfPackage;
215 vnfPackage = vnfDescriptorGenerator.generate(componentInstanceName, onboardedCsarArtifact);
216 } catch (final Exception e) {
217 final String errorMsg = String.format("Could not generate VNF package for component instance %s", componentInstanceName);
218 throw new NsdException(errorMsg, e);
220 if (vnfPackage.isPresent()) {
221 vnfDescriptorList.add(vnfPackage.get());
223 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
226 return vnfDescriptorList;
229 private Optional<ArtifactDefinition> findOnboardedCsar(final ComponentInstance componentInstance) {
230 final Map<String, ArtifactDefinition> artifactDefinitionMap = componentInstance.getDeploymentArtifacts();
231 if (artifactDefinitionMap == null || artifactDefinitionMap.isEmpty()) {
232 return Optional.empty();
234 return artifactDefinitionMap.values().stream().filter(artifactDefinition -> {
235 final String artifactType = (String) artifactDefinition.getToscaPresentationValue(JsonPresentationFields.ARTIFACT_TYPE);
236 return ONBOARDED_PACKAGE.getType().equals(artifactType) || ETSI_PACKAGE.getType().equals(artifactType);
240 private Map<String, byte[]> createEtsiSolNsdTypeEntries(final EtsiVersion etsiVersion) {
241 final EtsiVersion currentVersion = etsiVersion == null ? EtsiVersion.getDefaultVersion() : etsiVersion;
243 final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
245 final Resource[] resources =
246 resolver.getResources(String.format("classpath:etsi-nfv-types/%s/*.*", currentVersion.getVersion()));
247 if (resources.length > 0) {
248 final Map<String, byte[]> entryMap = new HashMap<>();
249 for (final Resource resource : resources) {
250 readResource(resource).ifPresent(resourceBytes -> {
251 final String entryName = createDefinitionEntryName(resource.getFilename());
252 entryMap.put(entryName, resourceBytes);
257 } catch (final IOException e) {
258 LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e);
261 return Collections.emptyMap();
264 private String createDefinitionEntryName(final String fileName) {
265 return DEFINITION + "/" + fileName;
268 private Optional<byte[]> readResource(final Resource resource) {
270 return Optional.ofNullable(IOUtils.toByteArray(resource.getInputStream()));
271 } catch (final IOException exception) {
272 LOGGER.error("Error adding '{}' to NSD CSAR", resource.getFilename(), exception);
274 return Optional.empty();
277 private Nsd generateNsd(final Component component,
278 final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
279 final NsDescriptorGenerator nsDescriptorGenerator =
280 nsDescriptorGeneratorFactory.create();
281 return nsDescriptorGenerator.generate(component, vnfDescriptorList)
283 new NsdException(String
284 .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
288 private Optional<ArtifactDefinition> getReferencedArtifact(final Component component,
289 final String filePath) throws NsdException {
290 final Map<String, ArtifactDefinition> interfaceOperationArtifactsByName =
291 OperationArtifactUtil.getDistinctInterfaceOperationArtifactsByName(component);
292 final String[] pathComponents = filePath.split(SLASH);
293 final String artifactName = pathComponents[pathComponents.length - 1];
294 final ArtifactDefinition artifactDefinition = interfaceOperationArtifactsByName.get(artifactName);
295 if (artifactDefinition == null) {
296 throw new NsdException(String.format("Could not find artifact '%s'", filePath));
298 LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
299 artifactDefinition.getUniqueId());
300 if (artifactDefinition.getPayloadData() == null) {
301 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
303 if (artifactPayload.isEmpty()) {
304 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
306 artifactDefinition.setPayload(artifactPayload.get());
309 return Optional.of(artifactDefinition);
312 private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
313 final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
314 .getArtifact(artifactCassandraId);
316 if (artifactResponse.isRight()) {
317 LOGGER.debug("Failed to fetch artifact from Cassandra by id {} error {} ", artifactCassandraId,
318 artifactResponse.right().value());
319 return Optional.empty();
321 final DAOArtifactData artifactData = artifactResponse.left().value();
322 return Optional.of(artifactData.getDataAsArray());
325 private String buildToscaMetaContent(final String nsdFileName) {
326 LOGGER.debug("Creating TOSCA.meta content");
327 final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
329 builder.withCsarVersion("1.1")
330 .withCreatedBy("ONAP")
331 .withToscaMetaVersion("1.0")
332 .withEntryDefinitions(getNsdPath(nsdFileName))
333 .withEntryManifest(getManifestPath(nsdFileName))
334 .withEntryChangeLog("ChangeLog.txt");
336 final String toscaMetadata = builder.build();
337 LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
338 return toscaMetadata;
341 private NsdCsarManifestBuilder createManifestBuilder(final Nsd nsd, final EtsiVersion nsdVersion,
342 final String manifestFilePath, final Set<String> filePath,
343 final boolean addSignatureFiles) {
344 LOGGER.debug("Creating NS manifest file content");
346 final Set<String> filesToAdd = new HashSet<>(filePath);
347 if (addSignatureFiles) {
348 filePath.forEach(file -> filesToAdd.add(file + SIGNATURE_EXTENSION));
350 filesToAdd.add(manifestFilePath);
352 final NsdCsarManifestBuilder nsdCsarManifestBuilder = new NsdCsarManifestBuilder();
353 nsdCsarManifestBuilder.withDesigner(nsd.getDesigner())
354 .withInvariantId(nsd.getInvariantId())
355 .withName(nsd.getName())
356 .withNowReleaseDateTime()
357 .withFileStructureVersion(nsd.getVersion())
358 .withSources(filesToAdd);
360 final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator();
362 if (nsdVersion != null && nsdVersionComparator.compare(nsdVersion, EtsiVersion.VERSION_3_3_1) >= 0) {
363 nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion());
366 if (LOGGER.isDebugEnabled()) {
367 LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", nsdCsarManifestBuilder.build());
369 return nsdCsarManifestBuilder;
372 private String getManifestPath(final String nsdFileName) {
373 return nsdFileName + DOT + MANIFEST_EXT;
376 private String getNsdPath(final String nsdFileName) {
377 return DEFINITION + SLASH + nsdFileName + DOT_YAML;
380 private String getNsdFileName(final Component component) {
381 return component.getNormalizedName();
384 private byte[] buildCsarPackage(final Map<String, byte[]> nsdCsarFileMap) throws NsdException {
385 if (nsdCsarFileMap.isEmpty()) {
386 throw new NsdException("No files were provided to build the NSD CSAR package");
388 try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
389 final ZipOutputStream zip = new ZipOutputStream(out)) {
390 for (final Entry<String, byte[]> entry : nsdCsarFileMap.entrySet()) {
391 final String filePath = entry.getKey();
392 final byte[] fileContent = entry.getValue();
393 if (fileContent == null) {
394 LOGGER.error("Could not add '{}' to NSD CSAR. File content is null", filePath);
397 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
398 zip.putNextEntry(new ZipEntry(filePath));
399 zip.write(fileContent);
403 LOGGER.debug("NSD CSAR zip file was successfully built");
405 return out.toByteArray();
406 } catch (final IOException e) {
407 throw new NsdException("Could not build the NSD CSAR zip file", e);
411 private byte[] buildZipWithCsarAndSignature(final String csarFileName, final byte[] csarPackageBytes) throws NsdException {
412 final byte[] signature;
414 signature = nsdCsarEtsiOption2Signer.sign(csarPackageBytes);
415 } catch (final Exception e) {
416 throw new NsdException(String.format("Could not sign the CSAR '%s'", csarFileName), e);
418 final Optional<CertificateInfo> certificateInfoOpt = nsdCsarEtsiOption2Signer.getSigningCertificate();
419 if (certificateInfoOpt.isEmpty()) {
420 throw new NsdException(String.format("Could not sign the CSAR '%s'. No certificate configured.", csarFileName));
422 final CertificateInfo certificateInfo = certificateInfoOpt.get();
423 try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
424 final ZipOutputStream zip = new ZipOutputStream(out)) {
425 zip.putNextEntry(new ZipEntry(csarFileName + CSAR_EXTENSION));
426 zip.write(csarPackageBytes);
427 zip.putNextEntry(new ZipEntry(csarFileName + CSAR_SIGNATURE_EXTENSION));
428 zip.write(signature);
429 final File certificateFile = certificateInfo.getCertificateFile();
430 zip.putNextEntry(new ZipEntry(csarFileName + "." + FilenameUtils.getExtension(certificateFile.getName())));
431 zip.write(Files.readAllBytes(certificateFile.toPath()));
434 LOGGER.debug("NSD signed CSAR zip file was successfully built");
436 return out.toByteArray();
437 } catch (final IOException e) {
438 throw new NsdException("Could not build the NSD signed CSAR zip file", e);