efc6ade92d5cca5698209464620641aa15617f87
[sdc.git] /
1 /*
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
8  *
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.
15  *
16  *  SPDX-License-Identifier: Apache-2.0
17  *  ============LICENSE_END=========================================================
18  */
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
75     private static final String MANIFEST_EXT = "mf";
76     private static final String SLASH = "/";
77     private static final String DOT = ".";
78     private static final String DOT_YAML = DOT + "yaml";
79
80     private static final String DEFINITION = "Definitions";
81     private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta";
82
83     private final VnfDescriptorGenerator vnfDescriptorGenerator;
84     private final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory;
85     private final ArtifactCassandraDao artifactCassandraDao;
86     private final NsDescriptorConfig nsDescriptorConfig;
87
88     public EtsiNfvNsdCsarGeneratorImpl(final NsDescriptorConfig nsDescriptorConfig,
89                                        final VnfDescriptorGenerator vnfDescriptorGenerator,
90                                        final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory,
91                                        final ArtifactCassandraDao artifactCassandraDao) {
92         this.nsDescriptorConfig = nsDescriptorConfig;
93         this.vnfDescriptorGenerator = vnfDescriptorGenerator;
94         this.nsDescriptorGeneratorFactory = nsDescriptorGeneratorFactory;
95         this.artifactCassandraDao = artifactCassandraDao;
96     }
97
98     @Override
99     public byte[] generateNsdCsar(final Component component) throws NsdException {
100         if (component == null) {
101             throw new NsdException("Could not generate the NSD CSAR, invalid component argument");
102         }
103
104         loadComponentArtifacts(component);
105         loadComponentInstancesArtifacts(component);
106
107         final String componentName = component.getName();
108
109         try {
110             LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
111             final Map<String, byte[]> nsdCsarFiles = new HashMap<>();
112
113             final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
114             vnfDescriptorList.forEach(vnfPackage -> nsdCsarFiles.putAll(vnfPackage.getDefinitionFiles()));
115
116             final String nsdFileName = getNsdFileName(component);
117             final EtsiVersion etsiVersion = nsDescriptorConfig.getNsVersion();
118             final Nsd nsd = generateNsd(component, vnfDescriptorList);
119             nsdCsarFiles.put(getNsdPath(nsdFileName), nsd.getContents());
120             nsdCsarFiles.put(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes());
121             addEtsiSolNsdTypes(etsiVersion, nsdCsarFiles);
122             for (final String referencedFile : nsd.getArtifactReferences()) {
123                 getReferencedArtifact(component, referencedFile).ifPresent(
124                     artifactDefinition -> nsdCsarFiles.put(referencedFile, artifactDefinition.getPayloadData())
125                 );
126             }
127             nsdCsarFiles
128                 .put(getManifestPath(nsdFileName), getManifestFileContent(nsd, etsiVersion, nsdCsarFiles.keySet()).getBytes());
129
130             final byte[] csar = buildCsarPackage(nsdCsarFiles);
131             LOGGER.debug("Successfully generated NSD CSAR package");
132             return csar;
133         } catch (final Exception exception) {
134             throw new NsdException("Could not generate the NSD CSAR file", exception);
135         }
136     }
137
138     private void loadComponentArtifacts(final Component component) {
139         final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
140         if (allArtifactsMap == null) {
141             return;
142         }
143         allArtifactsMap.keySet().forEach(key -> {
144             final ArtifactDefinition artifactDefinition = allArtifactsMap.get(key);
145             if (StringUtils.isNotEmpty(artifactDefinition.getEsId())) {
146                 final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
147                 if (artifactPayload.isPresent()) {
148                     artifactDefinition.setPayload(artifactPayload.get());
149                 } else {
150                     LOGGER.warn("Could not load component '{}' artifact '{}'",
151                         component.getName(), artifactDefinition.getArtifactName());
152                 }
153             }
154         });
155     }
156
157     private void loadComponentInstancesArtifacts(final Component component) {
158         final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
159         if (CollectionUtils.isEmpty(componentInstanceList)) {
160             return;
161         }
162         for (final ComponentInstance componentInstance : componentInstanceList) {
163             final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
164             if (MapUtils.isEmpty(deploymentArtifacts)) {
165                 continue;
166             }
167             deploymentArtifacts.values().stream()
168                 .filter(artifactDefinition -> StringUtils.isNotEmpty(artifactDefinition.getEsId()))
169                 .forEach(artifactDefinition -> {
170                     final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
171                     if (artifactPayload.isPresent()) {
172                         artifactDefinition.setPayload(artifactPayload.get());
173                     } else {
174                         LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'",
175                             component.getName(), componentInstance.getName(), artifactDefinition.getArtifactName());
176                     }
177                 });
178         }
179     }
180
181     private List<VnfDescriptor> generateVnfPackages(final Component component) throws NsdException {
182         final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
183         if (CollectionUtils.isEmpty(componentInstanceList)) {
184             LOGGER.warn("Could not find any instance in service '{}'", component.getName());
185             return Collections.emptyList();
186         }
187
188         final List<VnfDescriptor> vnfDescriptorList = new ArrayList<>();
189         for (final ComponentInstance componentInstance : componentInstanceList) {
190             final String componentInstanceName = componentInstance.getName();
191             final ArtifactDefinition onboardedCsarArtifact = findOnboardedCsar(componentInstance).orElse(null);
192             if (onboardedCsarArtifact == null) {
193                 LOGGER.warn(
194                     "Unable to generate VNF Package for component instance '{}', no onboarded package present",
195                     componentInstanceName);
196                 continue;
197             }
198             final Optional<VnfDescriptor> vnfPackage;
199             try {
200                 vnfPackage = vnfDescriptorGenerator.generate(componentInstanceName, onboardedCsarArtifact);
201             } catch (final Exception e) {
202                 final String errorMsg =
203                     String.format("Could not generate VNF package for component instance %s", componentInstanceName);
204                 throw new NsdException(errorMsg, e);
205             }
206             if (vnfPackage.isPresent()) {
207                 vnfDescriptorList.add(vnfPackage.get());
208             } else {
209                 LOGGER.warn(
210                     "Unable to generate VNF Package for component instance '{}', no onboarded package present",
211                     componentInstanceName);
212             }
213         }
214
215         return vnfDescriptorList;
216     }
217
218     private Optional<ArtifactDefinition> findOnboardedCsar(final ComponentInstance componentInstance) {
219         final Map<String, ArtifactDefinition> artifactDefinitionMap = componentInstance.getDeploymentArtifacts();
220         if (artifactDefinitionMap == null || artifactDefinitionMap.isEmpty()) {
221             return Optional.empty();
222         }
223         return artifactDefinitionMap.values()
224             .stream()
225             .filter(artifactDefinition -> {
226                 final String artifactType = (String) artifactDefinition
227                     .getToscaPresentationValue(JsonPresentationFields.ARTIFACT_TYPE);
228                 return ONBOARDED_PACKAGE.getType().equals(artifactType) || ETSI_PACKAGE.getType().equals(artifactType);
229             })
230             .findFirst();
231     }
232
233     private void addEtsiSolNsdTypes(final EtsiVersion etsiVersion,
234                                     final Map<String, byte[]> nsdCsarFileMap) {
235         final EtsiVersion currentVersion = etsiVersion == null ?
236             EtsiVersion.getDefaultVersion() : etsiVersion;
237
238         final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
239
240         try {
241             final Resource[] resources =
242                 resolver.getResources(String.format("classpath:etsi-nfv-types/%s/*.*", currentVersion.getVersion()));
243             if (resources.length > 0) {
244                 for (final Resource resource : resources) {
245                     addToCsarFileMap(resource, nsdCsarFileMap);
246                 }
247             }
248         } catch (final IOException e) {
249             LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e);
250         }
251     }
252
253     private void addToCsarFileMap(final Resource resource, final Map<String, byte[]> nsdCsarFileMap) {
254         try {
255             nsdCsarFileMap.put(DEFINITION + "/" + resource.getFilename(),
256                 IOUtils.toByteArray(resource.getInputStream()));
257         } catch (final IOException exception) {
258             LOGGER.error("Error adding '{}' to NSD CSAR", resource.getFilename(), exception);
259         }
260     }
261
262     private Nsd generateNsd(final Component component,
263                             final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
264         final NsDescriptorGenerator nsDescriptorGenerator =
265             nsDescriptorGeneratorFactory.create();
266         return nsDescriptorGenerator.generate(component, vnfDescriptorList)
267             .orElseThrow(() ->
268                 new NsdException(String
269                     .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
270             );
271     }
272
273     private Optional<ArtifactDefinition> getReferencedArtifact(final Component component,
274                                                                final String filePath) throws NsdException {
275         final Map<String, ArtifactDefinition> interfaceOperationArtifactsByName =
276             OperationArtifactUtil.getDistinctInterfaceOperationArtifactsByName(component);
277         final String[] pathComponents = filePath.split(SLASH);
278         final String artifactName = pathComponents[pathComponents.length - 1];
279         final ArtifactDefinition artifactDefinition = interfaceOperationArtifactsByName.get(artifactName);
280         if (artifactDefinition == null) {
281             throw new NsdException(String.format("Could not find artifact '%s'", filePath));
282         }
283         LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
284             artifactDefinition.getUniqueId());
285         if (artifactDefinition.getPayloadData() == null) {
286             final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
287
288             if (artifactPayload.isEmpty()) {
289                 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
290             }
291             artifactDefinition.setPayload(artifactPayload.get());
292         }
293
294         return Optional.of(artifactDefinition);
295     }
296
297     private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
298         final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
299             .getArtifact(artifactCassandraId);
300
301         if (artifactResponse.isRight()) {
302             LOGGER.debug("Failed to fetch artifact from Cassandra by id {} error {} ", artifactCassandraId,
303                 artifactResponse.right().value());
304             return Optional.empty();
305         }
306         final DAOArtifactData artifactData = artifactResponse.left().value();
307         return Optional.of(artifactData.getDataAsArray());
308     }
309
310     private String buildToscaMetaContent(final String nsdFileName) {
311         LOGGER.debug("Creating TOSCA.meta content");
312         final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
313
314         builder.withCsarVersion("1.1")
315             .withCreatedBy("ONAP")
316             .withToscaMetaVersion("1.0")
317             .withEntryDefinitions(getNsdPath(nsdFileName))
318             .withEntryManifest(getManifestPath(nsdFileName))
319             .withEntryChangeLog("ChangeLog.txt");
320
321         final String toscaMetadata = builder.build();
322         LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
323         return toscaMetadata;
324     }
325
326     private String getManifestFileContent(final Nsd nsd,
327                                           final EtsiVersion nsdVersion,
328                                           final Set<String> files) {
329         LOGGER.debug("Creating NS manifest file content");
330
331         final NsdCsarManifestBuilder nsdCsarManifestBuilder = new NsdCsarManifestBuilder();
332         nsdCsarManifestBuilder.withDesigner(nsd.getDesigner())
333             .withInvariantId(nsd.getInvariantId())
334             .withName(nsd.getName())
335             .withNowReleaseDateTime()
336             .withFileStructureVersion(nsd.getVersion())
337             .withSources(files);
338
339         final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator();
340
341         if (nsdVersion != null && nsdVersionComparator.compare(nsdVersion, EtsiVersion.VERSION_3_3_1) >= 0) {
342             nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion());
343         }
344
345         final String manifest = nsdCsarManifestBuilder.build();
346         LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", manifest);
347         return manifest;
348
349     }
350
351     private String getManifestPath(final String nsdFileName) {
352         return nsdFileName + DOT + MANIFEST_EXT;
353     }
354
355     private String getNsdPath(final String nsdFileName) {
356         return DEFINITION + SLASH + nsdFileName + DOT_YAML;
357     }
358
359     private String getNsdFileName(final Component component) {
360         return component.getNormalizedName();
361     }
362
363     private byte[] buildCsarPackage(final Map<String, byte[]> nsdCsarFileMap) throws NsdException {
364         if (nsdCsarFileMap.isEmpty()) {
365             throw new NsdException("No files were provided to build the NSD CSAR package");
366         }
367         try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
368             final ZipOutputStream zip = new ZipOutputStream(out)) {
369             for (final Entry<String, byte[]> entry : nsdCsarFileMap.entrySet()) {
370                 final String filePath = entry.getKey();
371                 final byte[] fileContent = entry.getValue();
372                 if (fileContent == null) {
373                     LOGGER.error("Could not add '{}' to NSD CSAR. File content is null", filePath);
374                     continue;
375                 }
376                 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
377                 zip.putNextEntry(new ZipEntry(filePath));
378                 zip.write(fileContent);
379             }
380             zip.flush();
381             zip.finish();
382             LOGGER.debug("NSD CSAR zip file was successfully built");
383
384             return out.toByteArray();
385         } catch (final IOException e) {
386             throw new NsdException("Could not build the NSD CSAR zip file", e);
387         }
388     }
389
390 }