6274a54a58c267ba97e3e1ece8ef1169c6642420
[sdc.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 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  *
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
21 package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation;
22
23 import java.io.InputStream;
24 import org.apache.commons.collections.CollectionUtils;
25 import org.apache.commons.io.FilenameUtils;
26 import org.openecomp.core.impl.ToscaDefinitionImportHandler;
27 import org.openecomp.core.utilities.file.FileContentHandler;
28 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
29 import org.openecomp.sdc.common.errors.Messages;
30 import org.openecomp.sdc.common.utils.SdcCommon;
31 import org.openecomp.sdc.datatypes.error.ErrorLevel;
32 import org.openecomp.sdc.datatypes.error.ErrorMessage;
33 import org.openecomp.sdc.logging.api.Logger;
34 import org.openecomp.sdc.logging.api.LoggerFactory;
35 import org.openecomp.sdc.tosca.csar.Manifest;
36 import org.openecomp.sdc.tosca.csar.OnboardingToscaMetadata;
37 import org.openecomp.sdc.tosca.csar.SOL004ManifestOnboarding;
38 import org.openecomp.sdc.tosca.csar.ToscaMetadata;
39 import org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.exceptions.InvalidManifestMetadataException;
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Optional;
48 import java.util.Set;
49 import java.util.stream.Collectors;
50 import org.yaml.snakeyaml.Yaml;
51
52 import static org.openecomp.sdc.tosca.csar.CSARConstants.CSAR_VERSION_1_0;
53 import static org.openecomp.sdc.tosca.csar.CSARConstants.CSAR_VERSION_1_1;
54 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_METADATA_LIMIT;
55 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA;
56 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA;
57 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_MANIFEST_FILE_EXT;
58 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ETSI_ENTRY_CERTIFICATE;
59 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_FILE_VERSION_ENTRY;
60 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_CREATED_BY_ENTRY;
61 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_CSAR_VERSION_ENTRY;
62 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ETSI_ENTRY_CHANGE_LOG;
63 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_DEFINITIONS;
64 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ETSI_ENTRY_LICENSES;
65 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ETSI_ENTRY_MANIFEST;
66 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ETSI_ENTRY_TESTS;
67 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_FILE_VERSION;
68 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_PATH_FILE_NAME;
69 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_TYPE_PNF;
70 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_TYPE_VNF;
71 import static org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation.NonManoArtifactType.ONAP_PM_DICTIONARY;
72 import static org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation.NonManoArtifactType.ONAP_VES_EVENTS;
73
74 /**
75  * Validates the contents of the package to ensure it complies with the "CSAR with TOSCA-Metadata directory" structure
76  * as defined in ETSI GS NFV-SOL 004 v2.6.1.
77  */
78 class SOL004MetaDirectoryValidator implements Validator {
79
80     private static final Logger LOGGER = LoggerFactory.getLogger(SOL004MetaDirectoryValidator.class);
81
82     private static final String MANIFEST_SOURCE = "Source";
83     private static final String MANIFEST_NON_MANO_SOURCE = "Non-MANO Source";
84     private final List<ErrorMessage> errorsByFile = new ArrayList<>();
85     private FileContentHandler contentHandler;
86     private Set<String> folderList;
87     private ToscaMetadata toscaMetadata;
88
89     @Override
90     public Map<String, List<ErrorMessage>> validateContent(final FileContentHandler contentHandler) {
91         this.contentHandler = contentHandler;
92         this.folderList = contentHandler.getFolderList();
93         parseToscaMetadata();
94         verifyMetadataFile();
95         return Collections.unmodifiableMap(getAnyValidationErrors());
96     }
97
98     /**
99      * Parses the {@link org.openecomp.sdc.tosca.csar.CSARConstants#TOSCA_META_PATH_FILE_NAME} file
100      */
101     private void parseToscaMetadata() {
102         try {
103             toscaMetadata =
104                 OnboardingToscaMetadata
105                     .parseToscaMetadataFile(contentHandler.getFileContentAsStream(TOSCA_META_PATH_FILE_NAME));
106         } catch (final IOException e) {
107             reportError(ErrorLevel.ERROR, Messages.METADATA_PARSER_INTERNAL.getErrorMessage());
108             LOGGER.error(Messages.METADATA_PARSER_INTERNAL.getErrorMessage(), e.getMessage(), e);
109         }
110     }
111
112     private void verifyMetadataFile() {
113         if (toscaMetadata.isValid() && hasETSIMetadata()) {
114             verifyManifestNameAndExtension();
115             handleMetadataEntries();
116         } else {
117             errorsByFile.addAll(toscaMetadata.getErrors());
118         }
119     }
120
121     private void verifyManifestNameAndExtension() {
122         final Map<String, String> entries = toscaMetadata.getMetaEntries();
123         final String manifestFileName = getFileName(entries.get(TOSCA_META_ETSI_ENTRY_MANIFEST));
124         final String manifestExtension = getFileExtension(entries.get(TOSCA_META_ETSI_ENTRY_MANIFEST));
125         final String mainDefinitionFileName = getFileName(entries.get(TOSCA_META_ENTRY_DEFINITIONS));
126         if (!(TOSCA_MANIFEST_FILE_EXT).equals(manifestExtension)) {
127             reportError(ErrorLevel.ERROR, Messages.MANIFEST_INVALID_EXT.getErrorMessage());
128         }
129         if (!mainDefinitionFileName.equals(manifestFileName)) {
130             reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_NAME.getErrorMessage(),
131                     manifestFileName, mainDefinitionFileName));
132         }
133     }
134
135     private String getFileExtension(final String filePath) {
136         return FilenameUtils.getExtension(filePath);
137     }
138
139     private String getFileName(final String filePath) {
140         return FilenameUtils.getBaseName(filePath);
141     }
142
143     private boolean hasETSIMetadata(){
144         final Map<String, String> entries = toscaMetadata.getMetaEntries();
145         return hasEntry(entries, TOSCA_META_FILE_VERSION_ENTRY)
146                 && hasEntry(entries, TOSCA_META_CSAR_VERSION_ENTRY)
147                 && hasEntry(entries, TOSCA_META_CREATED_BY_ENTRY);
148     }
149
150     private boolean hasEntry(final Map<String, String> entries, final String mandatoryEntry) {
151         if (!entries.containsKey(mandatoryEntry)) {
152             reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_ENTRY.getErrorMessage(),mandatoryEntry));
153             return false;
154         }
155         return true;
156     }
157
158     private void handleMetadataEntries() {
159         for(final Map.Entry entry: toscaMetadata.getMetaEntries().entrySet()){
160             handleEntry(entry);
161         }
162     }
163
164     private void handleEntry(final Map.Entry<String, String> entry) {
165         final String key = entry.getKey();
166         final String value = entry.getValue();
167         switch (key){
168             case TOSCA_META_FILE_VERSION_ENTRY:
169             case TOSCA_META_CSAR_VERSION_ENTRY:
170             case TOSCA_META_CREATED_BY_ENTRY:
171                 verifyMetadataEntryVersions(key, value);
172                 break;
173             case TOSCA_META_ENTRY_DEFINITIONS:
174                 validateDefinitionFile(value);
175                 break;
176             case TOSCA_META_ETSI_ENTRY_MANIFEST:
177                 validateManifestFile(value);
178                 break;
179             case TOSCA_META_ETSI_ENTRY_CHANGE_LOG:
180                 validateChangeLog(value);
181                 break;
182             case TOSCA_META_ETSI_ENTRY_TESTS:
183             case TOSCA_META_ETSI_ENTRY_LICENSES:
184                 validateOtherEntries(entry);
185                 break;
186             case TOSCA_META_ETSI_ENTRY_CERTIFICATE:
187                 validateOtherEntries(value);
188                 break;
189             default:
190                 reportError(ErrorLevel.ERROR, Messages.METADATA_UNSUPPORTED_ENTRY.formatMessage(entry.toString()));
191                 LOGGER.warn(Messages.METADATA_UNSUPPORTED_ENTRY.getErrorMessage(), entry);
192                 break;
193         }
194     }
195
196     private void validateOtherEntries(final Map.Entry entry) {
197         final String manifestFile = toscaMetadata.getMetaEntries().get(TOSCA_META_ETSI_ENTRY_MANIFEST);
198         if(verifyFileExists(contentHandler.getFileList(), manifestFile)){
199             final Manifest onboardingManifest = new SOL004ManifestOnboarding();
200             onboardingManifest.parse(contentHandler.getFileContentAsStream(manifestFile));
201             final Optional<ResourceTypeEnum> resourceType = onboardingManifest.getType();
202             if (resourceType.isPresent() && resourceType.get() == ResourceTypeEnum.VF){
203                 final String value = (String) entry.getValue();
204                 validateOtherEntries(value);
205             } else {
206                 final String key = (String) entry.getKey();
207                 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_PNF_METADATA.getErrorMessage(), key));
208             }
209
210         }
211     }
212
213     private void verifyMetadataEntryVersions(final String key, final String version) {
214         if(!(isValidTOSCAVersion(key,version) || isValidCSARVersion(key, version) || TOSCA_META_CREATED_BY_ENTRY.equals(key))) {
215             errorsByFile.add(new ErrorMessage(ErrorLevel.ERROR, String.format(Messages.METADATA_INVALID_VERSION.getErrorMessage(), key, version)));
216             LOGGER.error("{}: key {} - value {} ", Messages.METADATA_INVALID_VERSION.getErrorMessage(), key, version);
217         }
218     }
219
220     private boolean isValidTOSCAVersion(final String key, final String version){
221         return TOSCA_META_FILE_VERSION_ENTRY.equals(key) && TOSCA_META_FILE_VERSION.equals(version);
222     }
223
224     private boolean isValidCSARVersion(final String value, final String version){
225         return TOSCA_META_CSAR_VERSION_ENTRY.equals(value) && (CSAR_VERSION_1_1.equals(version)
226                 || CSAR_VERSION_1_0.equals(version));
227     }
228
229     private void validateDefinitionFile(final String filePath) {
230         final Set<String> existingFiles = contentHandler.getFileList();
231
232         if (verifyFileExists(existingFiles, filePath)) {
233             final ToscaDefinitionImportHandler toscaDefinitionImportHandler =
234                 new ToscaDefinitionImportHandler(contentHandler.getFiles(), filePath);
235             final List<ErrorMessage> validationErrorList = toscaDefinitionImportHandler.getErrors();
236             if (CollectionUtils.isNotEmpty(validationErrorList)) {
237                 errorsByFile.addAll(validationErrorList);
238             }
239         } else {
240             reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_DEFINITION_FILE.getErrorMessage(), filePath));
241         }
242     }
243
244     private boolean verifyFileExists(final Set<String> existingFiles, final String filePath) {
245         return existingFiles.contains(filePath);
246     }
247
248     private void validateManifestFile(final String filePath) {
249         final Set<String> existingFiles = contentHandler.getFileList();
250         if (verifyFileExists(existingFiles, filePath)) {
251             final Manifest onboardingManifest = new SOL004ManifestOnboarding();
252             onboardingManifest.parse(contentHandler.getFileContentAsStream(filePath));
253             if (onboardingManifest.isValid()) {
254                 try {
255                     verifyManifestMetadata(onboardingManifest.getMetadata());
256                 } catch (final InvalidManifestMetadataException e) {
257                     reportError(ErrorLevel.ERROR, e.getMessage());
258                     LOGGER.error(e.getMessage(), e);
259                 }
260                 verifyManifestSources(onboardingManifest);
261             } else {
262                 final List<String> manifestErrors = onboardingManifest.getErrors();
263                 manifestErrors.forEach(error -> reportError(ErrorLevel.ERROR, error));
264             }
265         } else {
266             reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_NOT_FOUND.getErrorMessage(), filePath));
267         }
268     }
269
270     private void verifyManifestMetadata(final Map<String, String> metadata) {
271         if (metadata.size() != MANIFEST_METADATA_LIMIT) {
272             reportError(ErrorLevel.ERROR,
273                 String.format(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT.getErrorMessage(),
274                     MANIFEST_METADATA_LIMIT));
275         }
276         if (isPnfMetadata(metadata)) {
277             handlePnfMetadataEntries(metadata);
278         } else {
279             handleVnfMetadataEntries(metadata);
280         }
281     }
282
283     private boolean isPnfMetadata(final Map<String, String> metadata) {
284         final String firstMetadataDefinition = metadata.keySet().iterator().next();
285         final String expectedMetadataType =
286             firstMetadataDefinition.contains(TOSCA_TYPE_PNF) ? TOSCA_TYPE_PNF : TOSCA_TYPE_VNF;
287         if (metadata.keySet().stream()
288             .anyMatch((final String metadataEntry) -> !metadataEntry.contains(expectedMetadataType))) {
289             throw new InvalidManifestMetadataException(Messages.MANIFEST_METADATA_INVALID_ENTRY.getErrorMessage());
290         }
291
292         return TOSCA_TYPE_PNF.equals(expectedMetadataType);
293     }
294
295     private void handleVnfMetadataEntries(final Map<String, String> metadata) {
296         for (final String requiredVnfEntry : MANIFEST_VNF_METADATA) {
297             if (!metadata.containsKey(requiredVnfEntry)) {
298                 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_METADATA_MISSING_ENTRY.getErrorMessage(), requiredVnfEntry));
299             }
300         }
301     }
302
303     private void handlePnfMetadataEntries(final Map<String, String> metadata) {
304         for (final String requiredPnfEntry : MANIFEST_PNF_METADATA) {
305             if (!metadata.containsKey(requiredPnfEntry)) {
306                 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_METADATA_MISSING_ENTRY.getErrorMessage(), requiredPnfEntry));
307             }
308         }
309     }
310
311     /**
312      * Checks if all manifest sources exists within the package and if all package files are being referred.
313      *
314      * @param onboardingManifest    The manifest
315      */
316     private void verifyManifestSources(final Manifest onboardingManifest) {
317         final Set<String> packageFiles = contentHandler.getFileList();
318         final List<String> sources = filterSources(onboardingManifest.getSources());
319         verifyFilesExist(packageFiles, sources, MANIFEST_SOURCE);
320
321         final Map<String, List<String>> nonManoArtifacts = onboardingManifest.getNonManoSources();
322
323         final List<String> nonManoValidFilePaths = new ArrayList<>();
324         nonManoArtifacts.forEach((nonManoType, files) -> {
325             final List<String> internalNonManoFileList = filterSources(files);
326             nonManoValidFilePaths.addAll(internalNonManoFileList);
327             if (ONAP_PM_DICTIONARY.getType().equals(nonManoType) || ONAP_VES_EVENTS.getType().equals(nonManoType)) {
328                 internalNonManoFileList.forEach(this::validateYaml);
329             }
330         });
331
332         verifyFilesExist(packageFiles, nonManoValidFilePaths, MANIFEST_NON_MANO_SOURCE);
333
334         final Set<String> allReferredFiles = new HashSet<>();
335         allReferredFiles.addAll(sources);
336         allReferredFiles.addAll(nonManoValidFilePaths);
337         verifyFilesBeingReferred(allReferredFiles, packageFiles);
338     }
339
340     /**
341      * Validates if a YAML file has the correct extension, is not empty and the content is a valid YAML.
342      * Reports each error found.
343      *
344      * @param filePath  the file path inside the package
345      */
346     private void validateYaml(final String filePath) {
347         if (!contentHandler.containsFile(filePath)) {
348             return;
349         }
350         final String fileExtension = getFileExtension(filePath);
351         if (!"yaml".equalsIgnoreCase(fileExtension) && !"yml".equalsIgnoreCase(fileExtension)) {
352             reportError(ErrorLevel.ERROR, Messages.INVALID_YAML_EXTENSION.formatMessage(filePath));
353             return;
354         }
355
356         final InputStream fileContent = contentHandler.getFileContentAsStream(filePath);
357         if (fileContent == null) {
358             reportError(ErrorLevel.ERROR, Messages.EMPTY_YAML_FILE_1.formatMessage(filePath));
359             return;
360         }
361         try {
362             new Yaml().loadAll(fileContent).iterator().next();
363         } catch (final Exception e) {
364             reportError(ErrorLevel.ERROR, Messages.INVALID_YAML_FORMAT_1.formatMessage(filePath, e.getMessage()));
365         }
366     }
367
368     /**
369      * Checks if all package files are referred in manifest.
370      * Reports missing references.
371      *
372      * @param referredFileSet   the list of referred files path
373      * @param packageFileSet    the list of package file path
374      */
375     private void verifyFilesBeingReferred(final Set<String> referredFileSet, final Set<String> packageFileSet) {
376         packageFileSet.forEach(filePath -> {
377             if (!referredFileSet.contains(filePath)) {
378                 reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_MANIFEST_REFERENCE.getErrorMessage(), filePath));
379             }
380         });
381     }
382
383     private List<String> filterSources(final List<String> source){
384         return source.stream()
385                 .filter(this::externalFileReferences)
386                 .collect(Collectors.toList());
387     }
388
389     private boolean externalFileReferences(final String filePath){
390         return !filePath.contains("://");
391     }
392
393     private void validateOtherEntries(final String folderPath) {
394         if(!verifyFoldersExist(folderList, folderPath))
395             reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_OPTIONAL_FOLDERS.getErrorMessage(),
396                     folderPath));
397     }
398
399     private boolean verifyFoldersExist(final Set<String> folderList, final String folderPath) {
400         return folderList.contains(folderPath + "/");
401     }
402
403     private void verifyFilesExist(final Set<String> existingFiles, final List<String> sources, final String type) {
404         sources.forEach(file -> {
405             if(!existingFiles.contains(file)){
406                 reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_MANIFEST_SOURCE.getErrorMessage(), type, file));
407             }
408         });
409     }
410
411     private void validateChangeLog(final String filePath) {
412         if(!verifyFileExists(contentHandler.getFileList(), filePath)){
413             reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_METADATA_FILES.getErrorMessage(), filePath));
414         }
415     }
416
417     private void reportError(final ErrorLevel errorLevel, final String errorMessage) {
418         errorsByFile.add(new ErrorMessage(errorLevel, errorMessage));
419     }
420
421     private Map<String, List<ErrorMessage>> getAnyValidationErrors() {
422         if (errorsByFile.isEmpty()) {
423             return Collections.emptyMap();
424         }
425         final Map<String, List<ErrorMessage>> errors = new HashMap<>();
426         errors.put(SdcCommon.UPLOAD_FILE, errorsByFile);
427         return errors;
428     }
429 }