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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation;
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;
47 import java.util.Optional;
49 import java.util.stream.Collectors;
50 import org.yaml.snakeyaml.Yaml;
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;
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.
78 class SOL004MetaDirectoryValidator implements Validator {
80 private static final Logger LOGGER = LoggerFactory.getLogger(SOL004MetaDirectoryValidator.class);
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;
90 public Map<String, List<ErrorMessage>> validateContent(final FileContentHandler contentHandler) {
91 this.contentHandler = contentHandler;
92 this.folderList = contentHandler.getFolderList();
95 return Collections.unmodifiableMap(getAnyValidationErrors());
99 * Parses the {@link org.openecomp.sdc.tosca.csar.CSARConstants#TOSCA_META_PATH_FILE_NAME} file
101 private void parseToscaMetadata() {
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);
112 private void verifyMetadataFile() {
113 if (toscaMetadata.isValid() && hasETSIMetadata()) {
114 verifyManifestNameAndExtension();
115 handleMetadataEntries();
117 errorsByFile.addAll(toscaMetadata.getErrors());
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());
129 if (!mainDefinitionFileName.equals(manifestFileName)) {
130 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_NAME.getErrorMessage(),
131 manifestFileName, mainDefinitionFileName));
135 private String getFileExtension(final String filePath) {
136 return FilenameUtils.getExtension(filePath);
139 private String getFileName(final String filePath) {
140 return FilenameUtils.getBaseName(filePath);
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);
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));
158 private void handleMetadataEntries() {
159 for(final Map.Entry entry: toscaMetadata.getMetaEntries().entrySet()){
164 private void handleEntry(final Map.Entry<String, String> entry) {
165 final String key = entry.getKey();
166 final String value = entry.getValue();
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);
173 case TOSCA_META_ENTRY_DEFINITIONS:
174 validateDefinitionFile(value);
176 case TOSCA_META_ETSI_ENTRY_MANIFEST:
177 validateManifestFile(value);
179 case TOSCA_META_ETSI_ENTRY_CHANGE_LOG:
180 validateChangeLog(value);
182 case TOSCA_META_ETSI_ENTRY_TESTS:
183 case TOSCA_META_ETSI_ENTRY_LICENSES:
184 validateOtherEntries(entry);
186 case TOSCA_META_ETSI_ENTRY_CERTIFICATE:
187 validateOtherEntries(value);
190 reportError(ErrorLevel.ERROR, Messages.METADATA_UNSUPPORTED_ENTRY.formatMessage(entry.toString()));
191 LOGGER.warn(Messages.METADATA_UNSUPPORTED_ENTRY.getErrorMessage(), entry);
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);
206 final String key = (String) entry.getKey();
207 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_PNF_METADATA.getErrorMessage(), key));
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);
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);
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));
229 private void validateDefinitionFile(final String filePath) {
230 final Set<String> existingFiles = contentHandler.getFileList();
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);
240 reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_DEFINITION_FILE.getErrorMessage(), filePath));
244 private boolean verifyFileExists(final Set<String> existingFiles, final String filePath) {
245 return existingFiles.contains(filePath);
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()) {
255 verifyManifestMetadata(onboardingManifest.getMetadata());
256 } catch (final InvalidManifestMetadataException e) {
257 reportError(ErrorLevel.ERROR, e.getMessage());
258 LOGGER.error(e.getMessage(), e);
260 verifyManifestSources(onboardingManifest);
262 final List<String> manifestErrors = onboardingManifest.getErrors();
263 manifestErrors.forEach(error -> reportError(ErrorLevel.ERROR, error));
266 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_NOT_FOUND.getErrorMessage(), filePath));
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));
276 if (isPnfMetadata(metadata)) {
277 handlePnfMetadataEntries(metadata);
279 handleVnfMetadataEntries(metadata);
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());
292 return TOSCA_TYPE_PNF.equals(expectedMetadataType);
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));
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));
312 * Checks if all manifest sources exists within the package and if all package files are being referred.
314 * @param onboardingManifest The manifest
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);
321 final Map<String, List<String>> nonManoArtifacts = onboardingManifest.getNonManoSources();
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);
332 verifyFilesExist(packageFiles, nonManoValidFilePaths, MANIFEST_NON_MANO_SOURCE);
334 final Set<String> allReferredFiles = new HashSet<>();
335 allReferredFiles.addAll(sources);
336 allReferredFiles.addAll(nonManoValidFilePaths);
337 verifyFilesBeingReferred(allReferredFiles, packageFiles);
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.
344 * @param filePath the file path inside the package
346 private void validateYaml(final String filePath) {
347 if (!contentHandler.containsFile(filePath)) {
350 final String fileExtension = getFileExtension(filePath);
351 if (!"yaml".equalsIgnoreCase(fileExtension) && !"yml".equalsIgnoreCase(fileExtension)) {
352 reportError(ErrorLevel.ERROR, Messages.INVALID_YAML_EXTENSION.formatMessage(filePath));
356 final InputStream fileContent = contentHandler.getFileContentAsStream(filePath);
357 if (fileContent == null) {
358 reportError(ErrorLevel.ERROR, Messages.EMPTY_YAML_FILE_1.formatMessage(filePath));
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()));
369 * Checks if all package files are referred in manifest.
370 * Reports missing references.
372 * @param referredFileSet the list of referred files path
373 * @param packageFileSet the list of package file path
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));
383 private List<String> filterSources(final List<String> source){
384 return source.stream()
385 .filter(this::externalFileReferences)
386 .collect(Collectors.toList());
389 private boolean externalFileReferences(final String filePath){
390 return !filePath.contains("://");
393 private void validateOtherEntries(final String folderPath) {
394 if(!verifyFoldersExist(folderList, folderPath))
395 reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_OPTIONAL_FOLDERS.getErrorMessage(),
399 private boolean verifyFoldersExist(final Set<String> folderList, final String folderPath) {
400 return folderList.contains(folderPath + "/");
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));
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));
417 private void reportError(final ErrorLevel errorLevel, final String errorMessage) {
418 errorsByFile.add(new ErrorMessage(errorLevel, errorMessage));
421 private Map<String, List<ErrorMessage>> getAnyValidationErrors() {
422 if (errorsByFile.isEmpty()) {
423 return Collections.emptyMap();
425 final Map<String, List<ErrorMessage>> errors = new HashMap<>();
426 errors.put(SdcCommon.UPLOAD_FILE, errorsByFile);