Plugin to Generate Service ETSI NSD CSAR
[sdc.git] / catalog-be-plugins / etsi-nfv-nsd-csar-plugin / src / main / java / org / openecomp / sdc / be / plugins / etsi / nfv / nsd / generator / EtsiNfvNsdCsarGeneratorImpl.java
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.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;
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.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;
61
62 /**
63  * Implementation of a ETSI NFV NSD CSAR generator
64  */
65 @org.springframework.stereotype.Component("etsiNfvNsdCsarGenerator")
66 public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator {
67
68     private static final Logger LOGGER = LoggerFactory.getLogger(EtsiNfvNsdCsarGeneratorImpl.class);
69
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";
74
75     private static final String DEFINITION = "Definitions";
76     private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta";
77
78     private final VnfDescriptorGenerator vnfDescriptorGenerator;
79     private final NsDescriptorGenerator nsDescriptorGeneratorImpl;
80     private final ArtifactCassandraDao artifactCassandraDao;
81
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;
88     }
89
90     @Override
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");
94         }
95
96         loadComponentArtifacts(component);
97         loadComponentInstancesArtifacts(component);
98
99         final String componentName = component.getName();
100
101         try {
102             LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName);
103             final Map<String, byte[]> nsdCsarFiles = new HashMap<>();
104
105             final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component);
106             vnfDescriptorList.forEach(vnfPackage -> nsdCsarFiles.putAll(vnfPackage.getDefinitionFiles()));
107
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())
116                 );
117             }
118             nsdCsarFiles
119                 .put(getManifestPath(nsdFileName), getManifestFileContent(nsd, nsdCsarFiles.keySet()).getBytes());
120
121             final byte[] csar = buildCsarPackage(nsdCsarFiles);
122             LOGGER.debug("Successfully generated NSD CSAR package");
123             return csar;
124         } catch (final Exception exception) {
125             throw new NsdException("Could not generate the NSD CSAR file", exception);
126         }
127     }
128
129     private void loadComponentArtifacts(final Component component) {
130         final Map<String, ArtifactDefinition> allArtifactsMap = component.getAllArtifacts();
131         if (allArtifactsMap == null) {
132             return;
133         }
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());
140                 } else {
141                     LOGGER.warn("Could not load component '{}' artifact '{}'",
142                         component.getName(), artifactDefinition.getArtifactName());
143                 }
144             }
145         });
146     }
147
148     private void loadComponentInstancesArtifacts(final Component component) {
149         final List<ComponentInstance> componentInstanceList = component.getComponentInstances();
150         if (CollectionUtils.isEmpty(componentInstanceList)) {
151             return;
152         }
153         for (final ComponentInstance componentInstance : componentInstanceList) {
154             final Map<String, ArtifactDefinition> deploymentArtifacts = componentInstance.getDeploymentArtifacts();
155             if (MapUtils.isEmpty(deploymentArtifacts)) {
156                 continue;
157             }
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());
164                     } else {
165                         LOGGER.warn("Could not load component '{}' instance '{}' artifact '{}'",
166                             component.getName(), componentInstance.getName(), artifactDefinition.getArtifactName());
167                     }
168                 });
169         }
170     }
171
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();
177         }
178
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) {
184                 LOGGER.warn(
185                     "Unable to generate VNF Package for component instance '{}', no onboarded package present",
186                     componentInstanceName);
187                 continue;
188             }
189             final Optional<VnfDescriptor> vnfPackage;
190             try {
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);
196             }
197             if (vnfPackage.isPresent()) {
198                 vnfDescriptorList.add(vnfPackage.get());
199             } else {
200                 LOGGER.warn(
201                     "Unable to generate VNF Package for component instance '{}', no onboarded package present",
202                     componentInstanceName);
203             }
204         }
205
206         return vnfDescriptorList;
207     }
208
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();
213         }
214         return artifactDefinitionMap.values()
215             .stream()
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);
220             })
221             .findFirst();
222     }
223
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";
227
228         try {
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);
235         }
236
237         String commonTypesFilename = "etsi_nfv_sol001_common_types.yaml";
238         try {
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);
245         }
246     }
247
248     private Nsd generateNsd(final Component component,
249                             final List<VnfDescriptor> vnfDescriptorList) throws NsdException {
250         return nsDescriptorGeneratorImpl.generate(component, vnfDescriptorList)
251             .orElseThrow(() ->
252                 new NsdException(String
253                     .format("Could not generate the Network Service Descriptor for component %s", component.getName()))
254             );
255     }
256
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));
266         }
267         LOGGER.debug("ArtifactName {}, unique ID {}", artifactDefinition.getArtifactName(),
268             artifactDefinition.getUniqueId());
269         if (artifactDefinition.getPayloadData() == null) {
270             final Optional<byte[]> artifactPayload = loadArtifactPayload(artifactDefinition.getEsId());
271
272             if (!artifactPayload.isPresent()) {
273                 throw new NsdException(String.format("Could not load artifact '%s' payload", filePath));
274             }
275             artifactDefinition.setPayload(artifactPayload.get());
276         }
277
278         return Optional.of(artifactDefinition);
279     }
280
281     private Optional<byte[]> loadArtifactPayload(final String artifactCassandraId) {
282         final Either<DAOArtifactData, CassandraOperationStatus> artifactResponse = artifactCassandraDao
283             .getArtifact(artifactCassandraId);
284
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();
289         }
290         final DAOArtifactData artifactData = artifactResponse.left().value();
291         return Optional.of(artifactData.getDataAsArray());
292     }
293
294     private String buildToscaMetaContent(final String nsdFileName) {
295         LOGGER.debug("Creating TOSCA.meta content");
296         final NsdToscaMetadataBuilder builder = new NsdToscaMetadataBuilder();
297
298         builder.withCsarVersion("1.1")
299             .withCreatedBy("ONAP")
300             .withToscaMetaVersion("1.0")
301             .withEntryDefinitions(getNsdPath(nsdFileName))
302             .withEntryManifest(getManifestPath(nsdFileName))
303             .withEntryChangeLog("ChangeLog.txt");
304
305         final String toscaMetadata = builder.build();
306         LOGGER.debug("Successfully created NS CSAR TOSCA.meta content:\n {}", toscaMetadata);
307         return toscaMetadata;
308     }
309
310     private String getManifestFileContent(final Nsd nsd, final Set<String> files) {
311         LOGGER.debug("Creating NS manifest file content");
312
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())
319             .withSources(files);
320
321         final String manifest = nsdCsarManifestBuilder.build();
322         LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", manifest);
323         return manifest;
324
325     }
326
327     private String getManifestPath(final String nsdFileName) {
328         return nsdFileName + DOT + MANIFEST_EXT;
329     }
330
331     private String getNsdPath(final String nsdFileName) {
332         return DEFINITION + SLASH + nsdFileName + DOT_YAML;
333     }
334
335     private String getNsdFileName(final Component component) {
336         return component.getNormalizedName();
337     }
338
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");
342         }
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);
350                     continue;
351                 }
352                 LOGGER.debug("Adding '{}' to NSD CSAR with content size: '{}'", filePath, fileContent.length);
353                 zip.putNextEntry(new ZipEntry(filePath));
354                 zip.write(fileContent);
355             }
356             zip.flush();
357             zip.finish();
358             LOGGER.debug("NSD CSAR zip file was successfully built");
359
360             return out.toByteArray();
361         } catch (final IOException e) {
362             throw new NsdException("Could not build the NSD CSAR zip file", e);
363         }
364     }
365
366 }