e7d30197d7b287d5f513d6ac1fc2bcf22d86eb3a
[sdc.git] /
1  
2 /*
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
9  *
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.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator;
21
22 import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ETSI_PACKAGE;
23 import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ONBOARDED_PACKAGE;
24
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;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Optional;
34 import java.util.Set;
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;
65
66 /**
67  * Implementation of a ETSI NFV NSD CSAR generator
68  */
69 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
70 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
71 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
72
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;
84
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;
92     }
93
94     @Override
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");
98         }
99         loadComponentArtifacts(component);
100         loadComponentInstancesArtifacts(component);
101         final String componentName = component.getName();
102         try {
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()));
116             }
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");
120             return csar;
121         } catch (final Exception exception) {
122             throw new NsdException("Could not generate the NSD CSAR file", exception);
123         }
124     }
125
126     private void loadComponentArtifacts(final Component component) {
127         final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
128         if (allArtifactsMap == null) {
129             return;
130         }
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());
137                 } else {
138                     LOGGER.warn("Could not load component '{}' artifact '{}'", component.getName(), artifactDefinition.getArtifactName());
139                 }
140             }
141         });
142     }
143
144     private void loadComponentInstancesArtifacts(final Component component) {
145         final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
146         if (CollectionUtils.isEmpty(componentInstanceList)) {
147             return;
148         }
149         for (final ComponentInstance componentInstance : componentInstanceList) {
150             final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
151             if (MapUtils.isEmpty(deploymentArtifacts)) {
152                 continue;
153             }
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());
159                     } else {
160                         LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'", component.getName(), componentInstance.getName(),
161                             artifactDefinition.getArtifactName());
162                     }
163                 });
164         }
165     }
166
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();
172         }
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);
179                 continue;
180             }
181             final Optional<VnfDescriptor> vnfPackage;
182             try {
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);
187             }
188             if (vnfPackage.isPresent()) {
189                 vnfDescriptorList.add(vnfPackage.get());
190             } else {
191                 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
192             }
193         }
194         return vnfDescriptorList;
195     }
196
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();
201         }
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);
205         }).findFirst();
206     }
207
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();
211         try {
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);
217                 }
218             }
219         } catch (final IOException e) {
220             LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e);
221         }
222     }
223
224     private void addToCsarFileMap(final Resource resource, final Map<String, byte[]> nsdCsarFileMap) {
225         try {
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);
230         }
231     }
232
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)
238             .orElseThrow(() ->
239                 new NsdException(String
240                     .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
241             );
242     }
243
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));
253         }
254         LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
255             artifactDefinition.getUniqueId());
256         if (artifactDefinition.getPayloadData() == null) {
257             final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
258
259             if (artifactPayload.isEmpty()) {
260                 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
261             }
262             artifactDefinition.setPayload(artifactPayload.get());
263         }
264
265         return Optional.of(artifactDefinition);
266     }
267
268     private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
269         final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
270             .getArtifact(artifactCassandraId);
271
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();
276         }
277         final DAOArtifactData artifactData = artifactResponse.left().value();
278         return Optional.of(artifactData.getDataAsArray());
279     }
280
281     private String buildToscaMetaContent(final String nsdFileName) {
282         LOGGER.debug("Creating TOSCA.meta content");
283         final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
284
285         builder.withCsarVersion("1.1")
286             .withCreatedBy("ONAP")
287             .withToscaMetaVersion("1.0")
288             .withEntryDefinitions(getNsdPath(nsdFileName))
289             .withEntryManifest(getManifestPath(nsdFileName))
290             .withEntryChangeLog("ChangeLog.txt");
291
292         final String toscaMetadata = builder.build();
293         LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
294         return toscaMetadata;
295     }
296
297     private String getManifestFileContent(final Nsd nsd,
298                                           final EtsiVersion nsdVersion,
299                                           final Set<String> files) {
300         LOGGER.debug("Creating NS manifest file content");
301
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())
308             .withSources(files);
309
310         final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator();
311
312         if (nsdVersion != null && nsdVersionComparator.compare(nsdVersion, EtsiVersion.VERSION_3_3_1) >= 0) {
313             nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion());
314         }
315
316         final String manifest = nsdCsarManifestBuilder.build();
317         LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", manifest);
318         return manifest;
319
320     }
321
322     private String getManifestPath(final String nsdFileName) {
323         return nsdFileName + DOT + MANIFEST_EXT;
324     }
325
326     private String getNsdPath(final String nsdFileName) {
327         return DEFINITION + SLASH + nsdFileName + DOT_YAML;
328     }
329
330     private String getNsdFileName(final Component component) {
331         return component.getNormalizedName();
332     }
333
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");
337         }
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);
345                     continue;
346                 }
347                 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
348                 zip.putNextEntry(new ZipEntry(filePath));
349                 zip.write(fileContent);
350             }
351             zip.flush();
352             zip.finish();
353             LOGGER.debug("NSD CSAR zip file was successfully built");
354
355             return out.toByteArray();
356         } catch (final IOException e) {
357             throw new NsdException("Could not build the NSD CSAR zip file", e);
358         }
359     }
360
361 }
362
363