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 List<String> folderList;
87 private ToscaMetadata toscaMetadata;
90 public Map<String, List<ErrorMessage>> validateContent(final FileContentHandler contentHandler
91 , final List<String> folderList) {
92 this.contentHandler = contentHandler;
93 this.folderList = folderList;
96 return Collections.unmodifiableMap(getAnyValidationErrors());
100 * Parses the {@link org.openecomp.sdc.tosca.csar.CSARConstants#TOSCA_META_PATH_FILE_NAME} file
102 private void parseToscaMetadata() {
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);
113 private void verifyMetadataFile() {
114 if (toscaMetadata.isValid() && hasETSIMetadata()) {
115 verifyManifestNameAndExtension();
116 handleMetadataEntries();
118 errorsByFile.addAll(toscaMetadata.getErrors());
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());
130 if (!mainDefinitionFileName.equals(manifestFileName)) {
131 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_NAME.getErrorMessage(),
132 manifestFileName, mainDefinitionFileName));
136 private String getFileExtension(final String filePath) {
137 return FilenameUtils.getExtension(filePath);
140 private String getFileName(final String filePath) {
141 return FilenameUtils.getBaseName(filePath);
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);
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));
159 private void handleMetadataEntries() {
160 for(final Map.Entry entry: toscaMetadata.getMetaEntries().entrySet()){
165 private void handleEntry(final Map.Entry<String, String> entry) {
166 final String key = entry.getKey();
167 final String value = entry.getValue();
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);
174 case TOSCA_META_ENTRY_DEFINITIONS:
175 validateDefinitionFile(value);
177 case TOSCA_META_ETSI_ENTRY_MANIFEST:
178 validateManifestFile(value);
180 case TOSCA_META_ETSI_ENTRY_CHANGE_LOG:
181 validateChangeLog(value);
183 case TOSCA_META_ETSI_ENTRY_TESTS:
184 case TOSCA_META_ETSI_ENTRY_LICENSES:
185 validateOtherEntries(entry);
187 case TOSCA_META_ETSI_ENTRY_CERTIFICATE:
188 validateOtherEntries(value);
191 reportError(ErrorLevel.ERROR, Messages.METADATA_UNSUPPORTED_ENTRY.formatMessage(entry.toString()));
192 LOGGER.warn(Messages.METADATA_UNSUPPORTED_ENTRY.getErrorMessage(), entry);
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);
207 final String key = (String) entry.getKey();
208 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_PNF_METADATA.getErrorMessage(), key));
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);
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);
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));
230 private void validateDefinitionFile(final String filePath) {
231 final Set<String> existingFiles = contentHandler.getFileList();
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);
241 reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_DEFINITION_FILE.getErrorMessage(), filePath));
245 private boolean verifyFileExists(final Set<String> existingFiles, final String filePath) {
246 return existingFiles.contains(filePath);
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()) {
256 verifyManifestMetadata(onboardingManifest.getMetadata());
257 } catch (final InvalidManifestMetadataException e) {
258 reportError(ErrorLevel.ERROR, e.getMessage());
259 LOGGER.error(e.getMessage(), e);
261 verifyManifestSources(onboardingManifest);
263 final List<String> manifestErrors = onboardingManifest.getErrors();
264 manifestErrors.forEach(error -> reportError(ErrorLevel.ERROR, error));
267 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_NOT_FOUND.getErrorMessage(), filePath));
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));
277 if (isPnfMetadata(metadata)) {
278 handlePnfMetadataEntries(metadata);
280 handleVnfMetadataEntries(metadata);
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());
293 return TOSCA_TYPE_PNF.equals(expectedMetadataType);
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));
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));
313 * Checks if all manifest sources exists within the package and if all package files are being referred.
315 * @param onboardingManifest The manifest
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);
322 final Map<String, List<String>> nonManoArtifacts = onboardingManifest.getNonManoSources();
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);
333 verifyFilesExist(packageFiles, nonManoValidFilePaths, MANIFEST_NON_MANO_SOURCE);
335 final Set<String> allReferredFiles = new HashSet<>();
336 allReferredFiles.addAll(sources);
337 allReferredFiles.addAll(nonManoValidFilePaths);
338 verifyFilesBeingReferred(allReferredFiles, packageFiles);
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.
345 * @param filePath the file path inside the package
347 private void validateYaml(final String filePath) {
348 if (!contentHandler.containsFile(filePath)) {
351 final String fileExtension = getFileExtension(filePath);
352 if (!"yaml".equalsIgnoreCase(fileExtension) && !"yml".equalsIgnoreCase(fileExtension)) {
353 reportError(ErrorLevel.ERROR, Messages.INVALID_YAML_EXTENSION.formatMessage(filePath));
357 final InputStream fileContent = contentHandler.getFileContent(filePath);
358 if (fileContent == null) {
359 reportError(ErrorLevel.ERROR, Messages.EMPTY_YAML_FILE_1.formatMessage(filePath));
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()));
370 * Checks if all package files are referred in manifest.
371 * Reports missing references.
373 * @param referredFileSet the list of referred files path
374 * @param packageFileSet the list of package file path
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));
384 private List<String> filterSources(final List<String> source){
385 return source.stream()
386 .filter(this::externalFileReferences)
387 .collect(Collectors.toList());
390 private boolean externalFileReferences(final String filePath){
391 return !filePath.contains("://");
394 private void validateOtherEntries(final String folderPath) {
395 if(!verifyFoldersExist(folderList, folderPath))
396 reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_OPTIONAL_FOLDERS.getErrorMessage(),
400 private boolean verifyFoldersExist(final List<String> folderList, final String folderPath) {
401 return folderList.contains(folderPath + "/");
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));
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));
418 private void reportError(final ErrorLevel errorLevel, final String errorMessage) {
419 errorsByFile.add(new ErrorMessage(errorLevel, errorMessage));
422 private Map<String, List<ErrorMessage>> getAnyValidationErrors() {
423 if (errorsByFile.isEmpty()) {
424 return Collections.emptyMap();
426 final Map<String, List<ErrorMessage>> errors = new HashMap<>();
427 errors.put(SdcCommon.UPLOAD_FILE, errorsByFile);