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 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.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
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.IOUtils;
42 import org.apache.commons.io.output.ByteArrayOutputStream;
43 import org.apache.commons.lang.StringUtils;
44 import org.openecomp.sdc.be.dao.cassandra.ArtifactCassandraDao;
45 import org.openecomp.sdc.be.dao.cassandra.CassandraOperationStatus;
46 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
47 import org.openecomp.sdc.be.model.ArtifactDefinition;
48 import org.openecomp.sdc.be.model.Component;
49 import org.openecomp.sdc.be.model.ComponentInstance;
50 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder;
51 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdToscaMetadataBuilder;
52 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException;
53 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd;
54 import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor;
55 import org.openecomp.sdc.be.resources.data.DAOArtifactData;
56 import org.openecomp.sdc.be.tosca.utils.OperationArtifactUtil;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.core.io.ClassPathResource;
60 import org.springframework.core.io.Resource;
63 * Implementation of a ETSI NFV NSD CSAR generator
65 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
66 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
68 private static final Logger LOGGER = LoggerFactory.getLogger(EtsiNfvNsdCsarGeneratorImpl.class);
70 private static final String MANIFEST_EXT = "mf";
71 private static final String SLASH = "/";
72 private static final String DOT = ".";
73 private static final String DOT_YAML = DOT + "yaml";
75 private static final String DEFINITION = "Definitions";
76 private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta";
78 private final VnfDescriptorGenerator vnfDescriptorGenerator;
79 private final NsDescriptorGenerator nsDescriptorGeneratorImpl;
80 private final ArtifactCassandraDao artifactCassandraDao;
82 public EtsiNfvNsdCsarGeneratorImpl(final VnfDescriptorGenerator vnfDescriptorGenerator,
83 final NsDescriptorGenerator nsDescriptorGenerator,
84 final ArtifactCassandraDao artifactCassandraDao) {
85 this.vnfDescriptorGenerator = vnfDescriptorGenerator;
86 this.nsDescriptorGeneratorImpl = nsDescriptorGenerator;
87 this.artifactCassandraDao = artifactCassandraDao;
91 public byte[] generateNsdCsar(final Component component) throws NsdException {
92 if (component == null) {
93 throw new NsdException("Could not generate the NSD CSAR, invalid component argument");
96 loadComponentArtifacts(component);
97 loadComponentInstancesArtifacts(component);
99 final String componentName = component.getName();
102 LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
103 final Map<String, byte[]> nsdCsarFiles = new HashMap<>();
105 final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
106 vnfDescriptorList.forEach(vnfPackage -> nsdCsarFiles.putAll(vnfPackage.getDefinitionFiles()));
108 final String nsdFileName = getNsdFileName(component);
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(nsdCsarFiles);
113 for (final String referencedFile : nsd.getArtifactReferences()) {
114 getReferencedArtifact(component, referencedFile).ifPresent(
115 artifactDefinition -> nsdCsarFiles.put(referencedFile, artifactDefinition.getPayloadData())
119 .put(getManifestPath(nsdFileName), getManifestFileContent(nsd, nsdCsarFiles.keySet()).getBytes());
121 final byte[] csar = buildCsarPackage(nsdCsarFiles);
122 LOGGER.debug("Successfully generated NSD CSAR package");
124 } catch (final Exception exception) {
125 throw new NsdException("Could not generate the NSD CSAR file", exception);
129 private void loadComponentArtifacts(final Component component) {
130 final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
131 if (allArtifactsMap == null) {
134 allArtifactsMap.keySet().forEach(key -> {
135 final ArtifactDefinition artifactDefinition = allArtifactsMap.get(key);
136 if (StringUtils.isNotEmpty(artifactDefinition.getEsId())) {
137 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
138 if (artifactPayload.isPresent()) {
139 artifactDefinition.setPayload(artifactPayload.get());
141 LOGGER.warn("Could not load component '{}' artifact '{}'",
142 component.getName(), artifactDefinition.getArtifactName());
148 private void loadComponentInstancesArtifacts(final Component component) {
149 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
150 if (CollectionUtils.isEmpty(componentInstanceList)) {
153 for (final ComponentInstance componentInstance : componentInstanceList) {
154 final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
155 if (MapUtils.isEmpty(deploymentArtifacts)) {
158 deploymentArtifacts.values().stream()
159 .filter(artifactDefinition -> StringUtils.isNotEmpty(artifactDefinition.getEsId()))
160 .forEach(artifactDefinition -> {
161 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
162 if (artifactPayload.isPresent()) {
163 artifactDefinition.setPayload(artifactPayload.get());
165 LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'",
166 component.getName(), componentInstance.getName(), artifactDefinition.getArtifactName());
172 private List<VnfDescriptor> generateVnfPackages(final Component component) throws NsdException {
173 final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
174 if (CollectionUtils.isEmpty(componentInstanceList)) {
175 LOGGER.warn("Could not find any instance in service '{}'", component.getName());
176 return Collections.emptyList();
179 final List<VnfDescriptor> vnfDescriptorList = new ArrayList<>();
180 for (final ComponentInstance componentInstance : componentInstanceList) {
181 final String componentInstanceName = componentInstance.getName();
182 final ArtifactDefinition onboardedCsarArtifact = findOnboardedCsar(componentInstance).orElse(null);
183 if (onboardedCsarArtifact == null) {
185 "Unable to generate VNF Package for component instance '{}', no onboarded package present",
186 componentInstanceName);
189 final Optional<VnfDescriptor> vnfPackage;
191 vnfPackage = vnfDescriptorGenerator.generate(componentInstanceName, onboardedCsarArtifact);
192 } catch (final Exception e) {
193 final String errorMsg =
194 String.format("Could not generate VNF package for component instance %s", componentInstanceName);
195 throw new NsdException(errorMsg, e);
197 if (vnfPackage.isPresent()) {
198 vnfDescriptorList.add(vnfPackage.get());
201 "Unable to generate VNF Package for component instance '{}', no onboarded package present",
202 componentInstanceName);
206 return vnfDescriptorList;
209 private Optional<ArtifactDefinition> findOnboardedCsar(final ComponentInstance componentInstance) {
210 final Map<String, ArtifactDefinition> artifactDefinitionMap = componentInstance.getDeploymentArtifacts();
211 if (artifactDefinitionMap == null || artifactDefinitionMap.isEmpty()) {
212 return Optional.empty();
214 return artifactDefinitionMap.values()
216 .filter(artifactDefinition -> {
217 final String artifactType = (String) artifactDefinition
218 .getToscaPresentationValue(JsonPresentationFields.ARTIFACT_TYPE);
219 return ONBOARDED_PACKAGE.getType().equals(artifactType) || ETSI_PACKAGE.getType().equals(artifactType);
224 private void addEtsiSolNsdTypes(final Map<String, byte[]> nsdCsarFileMap) {
225 final Path baseFolderPath = Paths.get("etsi-nfv-types");
226 String nsdTypesFilename = "etsi_nfv_sol001_nsd_2_7_1_types.yaml";
229 final Resource resource =
230 new ClassPathResource(Paths.get(baseFolderPath.toString(), nsdTypesFilename).toString());
231 nsdCsarFileMap.put(DEFINITION + "/" + nsdTypesFilename,
232 IOUtils.toByteArray(resource.getInputStream()));
233 } catch (final IOException exception) {
234 LOGGER.error("Error adding {} to NSD CSAR", nsdTypesFilename, exception);
237 String commonTypesFilename = "etsi_nfv_sol001_common_types.yaml";
239 final Resource resource =
240 new ClassPathResource(Paths.get(baseFolderPath.toString(), commonTypesFilename).toString());
241 nsdCsarFileMap.put(DEFINITION + "/" + commonTypesFilename,
242 IOUtils.toByteArray(resource.getInputStream()));
243 } catch (final IOException exception) {
244 LOGGER.error("Error adding {} to NSD CSAR", commonTypesFilename, exception);
248 private Nsd generateNsd(final Component component,
249 final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
250 return nsDescriptorGeneratorImpl.generate(component, vnfDescriptorList)
252 new NsdException(String
253 .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
257 private Optional<ArtifactDefinition> getReferencedArtifact(final Component component,
258 final String filePath) throws NsdException {
259 final Map<String, ArtifactDefinition> interfaceOperationArtifactsByName =
260 OperationArtifactUtil.getDistinctInterfaceOperationArtifactsByName(component);
261 final String[] pathComponents = filePath.split(SLASH);
262 final String artifactName = pathComponents[pathComponents.length - 1];
263 final ArtifactDefinition artifactDefinition = interfaceOperationArtifactsByName.get(artifactName);
264 if (artifactDefinition == null) {
265 throw new NsdException(String.format("Could not find artifact '%s'", filePath));
267 LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
268 artifactDefinition.getUniqueId());
269 if (artifactDefinition.getPayloadData() == null) {
270 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
272 if (!artifactPayload.isPresent()) {
273 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
275 artifactDefinition.setPayload(artifactPayload.get());
278 return Optional.of(artifactDefinition);
281 private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
282 final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
283 .getArtifact(artifactCassandraId);
285 if (artifactResponse.isRight()) {
286 LOGGER.debug("Failed to fetch artifact from Cassandra by id {} error {} ", artifactCassandraId,
287 artifactResponse.right().value());
288 return Optional.empty();
290 final DAOArtifactData artifactData = artifactResponse.left().value();
291 return Optional.of(artifactData.getDataAsArray());
294 private String buildToscaMetaContent(final String nsdFileName) {
295 LOGGER.debug("Creating TOSCA.meta content");
296 final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
298 builder.withCsarVersion("1.1")
299 .withCreatedBy("ONAP")
300 .withToscaMetaVersion("1.0")
301 .withEntryDefinitions(getNsdPath(nsdFileName))
302 .withEntryManifest(getManifestPath(nsdFileName))
303 .withEntryChangeLog("ChangeLog.txt");
305 final String toscaMetadata = builder.build();
306 LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
307 return toscaMetadata;
310 private String getManifestFileContent(final Nsd nsd, final Set<String> files) {
311 LOGGER.debug("Creating NS manifest file content");
313 final NsdCsarManifestBuilder nsdCsarManifestBuilder = new NsdCsarManifestBuilder();
314 nsdCsarManifestBuilder.withDesigner(nsd.getDesigner())
315 .withInvariantId(nsd.getInvariantId())
316 .withName(nsd.getName())
317 .withNowReleaseDateTime()
318 .withFileStructureVersion(nsd.getVersion())
321 final String manifest = nsdCsarManifestBuilder.build();
322 LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", manifest);
327 private String getManifestPath(final String nsdFileName) {
328 return nsdFileName + DOT + MANIFEST_EXT;
331 private String getNsdPath(final String nsdFileName) {
332 return DEFINITION + SLASH + nsdFileName + DOT_YAML;
335 private String getNsdFileName(final Component component) {
336 return component.getNormalizedName();
339 private byte[] buildCsarPackage(final Map<String, byte[]> nsdCsarFileMap) throws NsdException {
340 if (nsdCsarFileMap.isEmpty()) {
341 throw new NsdException("No files were provided to build the NSD CSAR package");
343 try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
344 final ZipOutputStream zip = new ZipOutputStream(out)) {
345 for (final Entry<String, byte[]> entry : nsdCsarFileMap.entrySet()) {
346 final String filePath = entry.getKey();
347 final byte[] fileContent = entry.getValue();
348 if (fileContent == null) {
349 LOGGER.error("Could not add '{}' to NSD CSAR. File content is null", filePath);
352 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
353 zip.putNextEntry(new ZipEntry(filePath));
354 zip.write(fileContent);
358 LOGGER.debug("NSD CSAR zip file was successfully built");
360 return out.toByteArray();
361 } catch (final IOException e) {
362 throw new NsdException("Could not build the NSD CSAR zip file", e);