570eee373648be73a8a2ab4cd0e78ff6347204d6
[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 org.openecomp.core.converter.ServiceTemplateReaderService;
24 import org.openecomp.core.impl.services.ServiceTemplateReaderServiceImpl;
25 import org.openecomp.core.utilities.file.FileContentHandler;
26 import org.openecomp.sdc.common.errors.Messages;
27 import org.openecomp.sdc.common.utils.SdcCommon;
28 import org.openecomp.sdc.datatypes.error.ErrorLevel;
29 import org.openecomp.sdc.datatypes.error.ErrorMessage;
30 import org.openecomp.sdc.logging.api.Logger;
31 import org.openecomp.sdc.logging.api.LoggerFactory;
32 import org.openecomp.sdc.tosca.csar.Manifest;
33 import org.openecomp.sdc.tosca.csar.OnboardingManifest;
34 import org.openecomp.sdc.tosca.csar.OnboardingToscaMetadata;
35 import org.openecomp.sdc.tosca.csar.ToscaMetadata;
36 import org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.exceptions.InvalidManifestMetadataException;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45
46 import static org.openecomp.sdc.tosca.csar.CSARConstants.CSAR_VERSION_1_0;
47 import static org.openecomp.sdc.tosca.csar.CSARConstants.CSAR_VERSION_1_1;
48 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_METADATA_LIMIT;
49 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA;
50 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA;
51 import static org.openecomp.sdc.tosca.csar.CSARConstants.NON_FILE_IMPORT_ATTRIBUTES;
52 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_FILE_VERSION_ENTRY;
53 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_CREATED_BY_ENTRY;
54 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_CSAR_VERSION_ENTRY;
55 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_CHANGE_LOG;
56 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_DEFINITIONS;
57 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_LICENSES;
58 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_MANIFEST;
59 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_TESTS;
60 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_FILE_VERSION;
61 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_PATH_FILE_NAME;
62
63 /**
64  * Validates the contents of the package to ensure it complies with the "CSAR with TOSCA-Metadata directory" structure
65  * as defined in ETSI GS NFV-SOL 004 v2.5.1.
66  *
67  */
68
69 class SOL004MetaDirectoryValidator implements Validator{
70
71     private static final Logger LOGGER = LoggerFactory.getLogger(SOL004MetaDirectoryValidator.class);
72
73     private final List<ErrorMessage> errorsByFile = new ArrayList<>();
74     private final Set<String> verifiedImports = new HashSet<>();
75
76     @Override
77     public Map<String, List<ErrorMessage>> validateContent(FileContentHandler contentHandler, List<String> folderList) {
78             validateMetaFile(contentHandler, folderList);
79             return Collections.unmodifiableMap(getAnyValidationErrors());
80     }
81
82     private void validateMetaFile(FileContentHandler contentHandler, List<String> folderList) {
83         try {
84             ToscaMetadata toscaMetadata = OnboardingToscaMetadata.parseToscaMetadataFile(contentHandler.getFileContent(TOSCA_META_PATH_FILE_NAME));
85             if(toscaMetadata.isValid() && hasETSIMetadata(toscaMetadata)) {
86                 verifyManifestNameAndExtension(toscaMetadata);
87                 handleMetadataEntries(contentHandler, folderList, toscaMetadata);
88             }else {
89                 errorsByFile.addAll(toscaMetadata.getErrors());
90             }
91         }catch (IOException e){
92             reportError(ErrorLevel.ERROR, Messages.METADATA_PARSER_INTERNAL.getErrorMessage());
93             LOGGER.error(Messages.METADATA_PARSER_INTERNAL.getErrorMessage(), e.getMessage(), e);
94         }
95     }
96
97     private void verifyManifestNameAndExtension(ToscaMetadata toscaMetadata) {
98         Map<String, String> entries = toscaMetadata.getMetaEntries();
99         String manifestFileName = getFileName(entries.get(TOSCA_META_ENTRY_MANIFEST));
100         String manifestExtension = getFileExtension(entries.get(TOSCA_META_ENTRY_MANIFEST));
101         String mainDefinitionFileName= getFileName(entries.get(TOSCA_META_ENTRY_DEFINITIONS));
102         if(!("mf").equals(manifestExtension)){
103             reportError(ErrorLevel.ERROR, Messages.MANIFEST_INVALID_EXT.getErrorMessage());
104         }
105         if(!mainDefinitionFileName.equals(manifestFileName)){
106             reportError(ErrorLevel.ERROR, Messages.MANIFEST_INVALID_NAME.getErrorMessage());
107         }
108     }
109
110     public String getFileExtension(String filePath){
111         return filePath.substring(filePath.lastIndexOf(".") + 1);
112     }
113
114     private String getFileName(String filePath){
115         return filePath.substring(filePath.lastIndexOf("/") + 1, filePath.lastIndexOf("."));
116     }
117
118     private boolean hasETSIMetadata(ToscaMetadata toscaMetadata){
119         Map<String, String> entries = toscaMetadata.getMetaEntries();
120         return hasEntry(entries, TOSCA_META_FILE_VERSION_ENTRY)
121                 && hasEntry(entries, TOSCA_META_CSAR_VERSION_ENTRY)
122                 && hasEntry(entries, TOSCA_META_CREATED_BY_ENTRY);
123     }
124
125     private boolean hasEntry(Map<String, String> entries, String mandatoryEntry) {
126         if (!entries.containsKey(mandatoryEntry)) {
127             reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_ENTRY.getErrorMessage(),mandatoryEntry));
128             return false;
129         }
130         return true;
131     }
132
133     private void handleMetadataEntries(FileContentHandler contentHandler, List<String> folderList, ToscaMetadata toscaMetadata) {
134         for(Map.Entry entry: toscaMetadata.getMetaEntries().entrySet()){
135             String key = (String) entry.getKey();
136             String value = (String) entry.getValue();
137             switch (key){
138                 case TOSCA_META_FILE_VERSION_ENTRY:
139                 case TOSCA_META_CSAR_VERSION_ENTRY:
140                 case TOSCA_META_CREATED_BY_ENTRY:
141                     verifyMetadataEntryVersions(key, value);
142                     break;
143                 case TOSCA_META_ENTRY_DEFINITIONS:
144                     validateDefinitionFile(contentHandler, value);
145                     break;
146                 case TOSCA_META_ENTRY_MANIFEST:
147                     validateManifestFile(contentHandler, value);
148                     break;
149                 case TOSCA_META_ENTRY_CHANGE_LOG:
150                     validateChangeLog(contentHandler, value);
151                     break;
152                 case TOSCA_META_ENTRY_TESTS:
153                 case TOSCA_META_ENTRY_LICENSES:
154                     validateOtherEntries(folderList, value);
155                     break;
156                 default:
157                     errorsByFile.add(new ErrorMessage(ErrorLevel.ERROR, String.format(Messages.METADATA_UNSUPPORTED_ENTRY.getErrorMessage(), entry)));
158                     LOGGER.warn(Messages.METADATA_UNSUPPORTED_ENTRY.getErrorMessage(), entry);
159                     break;
160             }
161
162         }
163     }
164
165
166     private void verifyMetadataEntryVersions(String key, String version) {
167         if(!(isValidTOSCAVersion(key,version) || isValidCSARVersion(key, version) || TOSCA_META_CREATED_BY_ENTRY.equals(key))) {
168             errorsByFile.add(new ErrorMessage(ErrorLevel.ERROR, Messages.ENTITY_NOT_FOUND.getErrorMessage()));
169             LOGGER.error("{}: key {} - value {} ", Messages.ENTITY_NOT_FOUND.getErrorMessage(), key, version);
170         }
171     }
172
173     private boolean isValidTOSCAVersion(String key, String version){
174         return TOSCA_META_FILE_VERSION_ENTRY.equals(key) && TOSCA_META_FILE_VERSION.equals(version);
175     }
176
177     private boolean isValidCSARVersion(String value, String version){
178         return "CSAR-Version".equals(value) && (CSAR_VERSION_1_1.equals(version)
179                 || CSAR_VERSION_1_0.equals(version));
180     }
181
182     private void validateDefinitionFile(FileContentHandler contentHandler, String filePath) {
183         Set<String> existingFiles = contentHandler.getFileList();
184
185         if (verifyFileExists(existingFiles, filePath)) {
186             byte[] definitionFile = getFileContent(filePath, contentHandler);
187             handleImports(contentHandler, filePath, existingFiles, definitionFile);
188         }else{
189             reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_DEFINITION_FILE.getErrorMessage(), filePath));
190         }
191     }
192
193     private void handleImports(FileContentHandler contentHandler, String filePath, Set<String> existingFiles,
194                                byte[] definitionFile) {
195         try {
196             ServiceTemplateReaderService readerService = new ServiceTemplateReaderServiceImpl(definitionFile);
197             List<Object> imports = (readerService).getImports();
198             for (Object o : imports) {
199                 String rootDir = "/";
200                 if (filePath.contains("/")) {
201                     rootDir = filePath.substring(0, filePath.lastIndexOf("/"));
202                 }
203                 String verifiedFile = verifyImport(existingFiles, o, rootDir);
204                 if (verifiedFile != null && !verifiedImports.contains(verifiedFile)) {
205                     verifiedImports.add(verifiedFile);
206                     handleImports(contentHandler, verifiedFile, existingFiles, getFileContent(verifiedFile,
207                             contentHandler));
208                 }
209             }
210         }
211         catch (Exception  e){
212             reportError(ErrorLevel.ERROR, String.format(Messages.INVALID_YAML_FORMAT.getErrorMessage(), e.getMessage()));
213             LOGGER.error("{}", Messages.INVALID_YAML_FORMAT_REASON, e.getMessage(), e);
214         }
215     }
216
217     private String verifyImport(Set<String> existingFiles, Object o, String parentDir) {
218         if(o instanceof String){
219             String filePath = ((String) o);
220             if(!filePath.contains("/")){
221                 filePath = parentDir + "/" + filePath;
222             }
223             if(!verifyFileExists(existingFiles, filePath)){
224                 reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_IMPORT_FILE.getErrorMessage(), (String) o));
225                 return null;
226             }
227             return filePath;
228         } else if(o instanceof Map){
229             Map<String, Object> o1 = (Map)o;
230             for(Map.Entry<String, Object> entry: o1.entrySet()){
231                 if(NON_FILE_IMPORT_ATTRIBUTES.stream().noneMatch(attr -> entry.getKey().equals(attr))){
232                     verifyImport(existingFiles, entry.getValue(), parentDir);
233                 }
234             }
235         }else {
236             reportError(ErrorLevel.ERROR, Messages.INVALID_IMPORT_STATEMENT.getErrorMessage());
237         }
238         return null;
239     }
240
241     private boolean verifyFileExists(Set<String> existingFiles, String filePath){
242         return existingFiles.contains(filePath);
243     }
244
245     private byte[] getFileContent(String filename, FileContentHandler contentHandler){
246         Map<String, byte[]> files = contentHandler.getFiles();
247         return files.get(filename);
248     }
249
250     private void validateManifestFile(FileContentHandler contentHandler, String filePath){
251         final Set<String> exitingFiles = contentHandler.getFileList();
252         if(verifyFileExists(exitingFiles, filePath)) {
253             Manifest onboardingManifest = OnboardingManifest.parse(contentHandler.getFileContent(filePath));
254             if(onboardingManifest.isValid()){
255                 try {
256                     verifyManifestMetadata(onboardingManifest.getMetadata());
257                 }catch (InvalidManifestMetadataException e){
258                    reportError(ErrorLevel.ERROR, e.getMessage());
259                    LOGGER.error(e.getMessage(), e);
260                 }
261                 verifySourcesExists(exitingFiles, onboardingManifest);
262             }else{
263                 List<String> manifestErrors = onboardingManifest.getErrors();
264                 for(String error: manifestErrors){
265                     reportError(ErrorLevel.ERROR, error);
266                 }
267             }
268         }else {
269             reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_NOT_EXIST.getErrorMessage(), filePath));
270         }
271     }
272
273     private void verifyManifestMetadata(Map<String, String> metadata) {
274         if(metadata.size() != MANIFEST_METADATA_LIMIT){
275             reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT.getErrorMessage(),
276                     MANIFEST_METADATA_LIMIT));
277         }
278         if(isPnfMetadata(metadata)){
279             handlePnfMetadataEntries(metadata);
280         }else {
281             handleVnfMetadataEntries(metadata);
282         }
283     }
284
285     private boolean isPnfMetadata(Map<String, String> metadata) {
286         String metadataType = null;
287         for(String key: metadata.keySet()) {
288             if(metadataType == null){
289                  metadataType = key.contains("pnf") ? "pnf" : "vnf";
290             }else if(!key.contains(metadataType)){
291                 throw new InvalidManifestMetadataException(Messages.MANIFEST_METADATA_INVALID_ENTRY.getErrorMessage());
292             }
293         }
294         return "pnf".equals(metadataType);
295     }
296
297     private void handleVnfMetadataEntries(Map<String, String> metadata) {
298         for (String requiredPnfEntry : MANIFEST_VNF_METADATA) {
299             if (!metadata.containsKey(requiredPnfEntry)) {
300                 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_METADATA_MISSING_ENTRY.getErrorMessage(), requiredPnfEntry));
301             }
302         }
303     }
304
305     private void handlePnfMetadataEntries(Map<String, String> metadata) {
306         for (String requiredPnfEntry : MANIFEST_PNF_METADATA) {
307             if (!metadata.containsKey(requiredPnfEntry)) {
308                 reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_METADATA_MISSING_ENTRY.getErrorMessage(), requiredPnfEntry));
309             }
310         }
311     }
312
313     private void verifySourcesExists(Set<String> exitingFiles, Manifest onboardingManifest) {
314         List<String> sources = onboardingManifest.getSources();
315         Map<String, List<String>> nonManoArtifacts = onboardingManifest.getNonManoSources();
316         verifyFilesExist(exitingFiles, sources);
317         for (Map.Entry entry : nonManoArtifacts.entrySet()) {
318             verifyFilesExist(exitingFiles, (List) entry.getValue());
319         }
320     }
321
322     private void validateOtherEntries(List<String> folderList, String folderPath){
323         if(!verifyFoldersExist(folderList, folderPath))
324             reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_OPTIONAL_FOLDERS.getErrorMessage(),
325                     folderPath));
326     }
327
328     private boolean verifyFoldersExist(List<String> folderList, String folderPath){
329         return folderList.contains(folderPath + "/");
330     }
331
332     private void verifyFilesExist(Set<String> existingFiles, List<String> sources){
333         for(String file: sources){
334             if(!verifyFileExists(existingFiles, file)){
335                 reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_ARTIFACT.getErrorMessage(), file));
336             }
337
338         }
339     }
340
341     private void validateChangeLog(FileContentHandler contentHandler, String filePath){
342         if(!verifyFileExists(contentHandler.getFileList(), filePath)){
343             reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_ARTIFACT.getErrorMessage(), filePath));
344         }
345     }
346
347     private void reportError(ErrorLevel errorLevel, String errorMessage){
348         errorsByFile.add(new ErrorMessage(errorLevel, errorMessage));
349     }
350
351     private Map<String, List<ErrorMessage>> getAnyValidationErrors(){
352
353         if(errorsByFile.isEmpty()){
354             return Collections.emptyMap();
355         }
356         Map<String, List<ErrorMessage>> errors = new HashMap<>();
357         errors.put(SdcCommon.UPLOAD_FILE, errorsByFile);
358         return errors;
359     }
360 }