f9e0970ba9ec586e006387cd4b2f248ae5b625e1
[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 package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator;
20
21 import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ETSI_PACKAGE;
22 import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ONBOARDED_PACKAGE;
23
24 import fj.data.Either;
25 import java.io.File;
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;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Optional;
36 import java.util.Set;
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;
71
72 /**
73  * Implementation of a ETSI NFV NSD CSAR generator
74  */
75 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
76 @Scope(BeanDefinition.SCOPE_PROTOTYPE)
77 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
78
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;
93
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;
103     }
104
105     @Override
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");
109         }
110         loadComponentArtifacts(component);
111         loadComponentInstancesArtifacts(component);
112         final String componentName = component.getName();
113         try {
114             LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
115             final String nsdFileName = getNsdFileName(component);
116             final NsdCsar nsdCsar = new NsdCsar(nsdFileName);
117
118             final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
119             vnfDescriptorList.forEach(vnfPackage -> nsdCsar.addAllFiles(vnfPackage.getDefinitionFiles()));
120
121             final EtsiVersion etsiVersion = nsDescriptorConfig.getNsVersion();
122             final Nsd nsd = generateNsd(component, vnfDescriptorList);
123
124             nsdCsar.addFile(getNsdPath(nsdFileName), nsd.getContents());
125             nsdCsar.addFile(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes());
126
127             nsdCsar.addAllFiles(createEtsiSolNsdTypeEntries(etsiVersion));
128             for (final String referencedFile : nsd.getArtifactReferences()) {
129                 getReferencedArtifact(component, referencedFile)
130                     .ifPresent(artifactDefinition -> nsdCsar.addFile(referencedFile, artifactDefinition.getPayloadData())
131                 );
132             }
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);
137
138             nsdCsar.addManifest(manifestBuilder);
139
140             if (isCertificateConfigured) {
141                 nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar);
142             }
143
144             byte[] package1 = buildCsarPackage(nsdCsar.getFileMap());
145             if (isCertificateConfigured) {
146                 package1 = buildZipWithCsarAndSignature(nsdCsar.getFileName(), package1);
147                 nsdCsar.setSigned(true);
148             }
149             nsdCsar.setCsarPackage(package1);
150             LOGGER.debug("Successfully generated NSD CSAR package");
151             return nsdCsar;
152         } catch (final Exception exception) {
153             throw new NsdException("Could not generate the NSD CSAR file", exception);
154         }
155     }
156
157     private void loadComponentArtifacts(final Component component) {
158         final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
159         if (allArtifactsMap == null) {
160             return;
161         }
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());
168                 } else {
169                     LOGGER.warn("Could not load component '{}' artifact '{}'", component.getName(), artifactDefinition.getArtifactName());
170                 }
171             }
172         });
173     }
174
175     private void loadComponentInstancesArtifacts(final Component component) {
176         final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
177         if (CollectionUtils.isEmpty(componentInstanceList)) {
178             return;
179         }
180         for (final ComponentInstance componentInstance : componentInstanceList) {
181             final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
182             if (MapUtils.isEmpty(deploymentArtifacts)) {
183                 continue;
184             }
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());
190                     } else {
191                         LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'", component.getName(), componentInstance.getName(),
192                             artifactDefinition.getArtifactName());
193                     }
194                 });
195         }
196     }
197
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();
203         }
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);
210                 continue;
211             }
212             final Optional<VnfDescriptor> vnfPackage;
213             try {
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);
218             }
219             if (vnfPackage.isPresent()) {
220                 vnfDescriptorList.add(vnfPackage.get());
221             } else {
222                 LOGGER.warn("Unable to generate VNF Package for component instance '{}', no onboarded package present", componentInstanceName);
223             }
224         }
225         return vnfDescriptorList;
226     }
227
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();
232         }
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);
236         }).findFirst();
237     }
238
239     private Map<String, byte[]> createEtsiSolNsdTypeEntries(final EtsiVersion etsiVersion) {
240         final EtsiVersion currentVersion = etsiVersion == null ? EtsiVersion.getDefaultVersion() : etsiVersion;
241
242         final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
243         try {
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);
252                     });
253                 }
254                 return entryMap;
255             }
256         } catch (final IOException e) {
257             LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e);
258         }
259
260         return Collections.emptyMap();
261     }
262
263     private String createDefinitionEntryName(final String fileName) {
264         return DEFINITION + "/" + fileName;
265     }
266
267     private Optional<byte[]> readResource(final Resource resource) {
268         try {
269             return Optional.ofNullable(IOUtils.toByteArray(resource.getInputStream()));
270         } catch (final IOException exception) {
271             LOGGER.error("Error adding '{}' to NSD CSAR", resource.getFilename(), exception);
272         }
273         return Optional.empty();
274     }
275
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)
281             .orElseThrow(() ->
282                 new NsdException(String
283                     .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
284             );
285     }
286
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));
296         }
297         LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
298             artifactDefinition.getUniqueId());
299         if (artifactDefinition.getPayloadData() == null) {
300             final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
301
302             if (artifactPayload.isEmpty()) {
303                 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
304             }
305             artifactDefinition.setPayload(artifactPayload.get());
306         }
307
308         return Optional.of(artifactDefinition);
309     }
310
311     private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
312         final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
313             .getArtifact(artifactCassandraId);
314
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();
319         }
320         final DAOArtifactData artifactData = artifactResponse.left().value();
321         return Optional.of(artifactData.getDataAsArray());
322     }
323
324     private String buildToscaMetaContent(final String nsdFileName) {
325         LOGGER.debug("Creating TOSCA.meta content");
326         final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
327
328         builder.withCsarVersion("1.1")
329             .withCreatedBy("ONAP")
330             .withToscaMetaVersion("1.0")
331             .withEntryDefinitions(getNsdPath(nsdFileName))
332             .withEntryManifest(getManifestPath(nsdFileName))
333             .withEntryChangeLog("ChangeLog.txt");
334
335         final String toscaMetadata = builder.build();
336         LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
337         return toscaMetadata;
338     }
339
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");
344
345         final Set<String> filesToAdd = new HashSet<>(filePath);
346         if (addSignatureFiles) {
347             filePath.forEach(file -> filesToAdd.add(file + SIGNATURE_EXTENSION));
348         }
349         filesToAdd.add(manifestFilePath);
350
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);
358
359         final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator();
360
361         if (nsdVersion != null && nsdVersionComparator.compare(nsdVersion, EtsiVersion.VERSION_3_3_1) >= 0) {
362             nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion());
363         }
364
365         if (LOGGER.isDebugEnabled()) {
366             LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", nsdCsarManifestBuilder.build());
367         }
368         return nsdCsarManifestBuilder;
369     }
370
371     private String getManifestPath(final String nsdFileName) {
372         return nsdFileName + DOT + MANIFEST_EXT;
373     }
374
375     private String getNsdPath(final String nsdFileName) {
376         return DEFINITION + SLASH + nsdFileName + DOT_YAML;
377     }
378
379     private String getNsdFileName(final Component component) {
380         return component.getNormalizedName();
381     }
382
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");
386         }
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);
394                     continue;
395                 }
396                 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
397                 zip.putNextEntry(new ZipEntry(filePath));
398                 zip.write(fileContent);
399             }
400             zip.flush();
401             zip.finish();
402             LOGGER.debug("NSD CSAR zip file was successfully built");
403
404             return out.toByteArray();
405         } catch (final IOException e) {
406             throw new NsdException("Could not build the NSD CSAR zip file", e);
407         }
408     }
409
410     private byte[] buildZipWithCsarAndSignature(final String csarFileName, final byte[] csarPackageBytes) throws NsdException {
411         final byte[] signature;
412         try {
413             signature = nsdCsarEtsiOption2Signer.sign(csarPackageBytes);
414         } catch (final Exception e) {
415             throw new NsdException(String.format("Could not sign the CSAR '%s'", csarFileName), e);
416         }
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));
420         }
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()));
431             zip.flush();
432             zip.finish();
433             LOGGER.debug("NSD signed CSAR zip file was successfully built");
434
435             return out.toByteArray();
436         } catch (final IOException e) {
437             throw new NsdException("Could not build the NSD signed CSAR zip file", e);
438         }
439     }
440
441 }
442
443