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