2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.openecomp.sdc.be.components.impl;
23 import java.io.ByteArrayInputStream;
24 import java.io.IOException;
25 import java.io.StringReader;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.List;
30 import java.util.Optional;
31 import java.util.Properties;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 import java.util.stream.Collectors;
36 import org.apache.commons.lang3.tuple.ImmutablePair;
37 import org.openecomp.sdc.be.config.BeEcompErrorManager;
38 import org.openecomp.sdc.be.config.BeEcompErrorManager.ErrorSeverity;
39 import org.openecomp.sdc.be.dao.api.ActionStatus;
40 import org.openecomp.sdc.be.impl.ComponentsUtils;
41 import org.openecomp.sdc.be.tosca.CsarUtils;
42 import org.openecomp.sdc.common.util.GeneralUtility;
43 import org.openecomp.sdc.exception.ResponseFormat;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 import fj.data.Either;
49 public class CsarValidationUtils {
51 private static Logger log = LoggerFactory.getLogger(CsarValidationUtils.class.getName());
53 private static final String TOSCA_META_FILE_VERSION = "TOSCA-Meta-File-Version";
55 private static final String CSAR_VERSION = "CSAR-Version";
57 private static final String CREATED_BY = "Created-By";
59 private static final String NEW_LINE_DELM = "\n";
61 //public final static String TOSCA_METADATA_FILE = "TOSCA-Metadata/TOSCA.meta";
62 public final static String TOSCA_METADATA = "TOSCA-Metadata";
63 public final static String TOSCA_FILE = "TOSCA.meta";
64 public final static String DEL_PATTERN = "([/\\\\]+)";
65 public static final String TOSCA_METADATA_PATH_PATTERN = TOSCA_METADATA +
66 // Artifact Group (i.e Deployment/Informational)
67 DEL_PATTERN + TOSCA_FILE;
69 public static final String TOSCA_META_ENTRY_DEFINITIONS = "Entry-Definitions";
71 private static final String[] TOSCA_METADATA_FIELDS = { TOSCA_META_FILE_VERSION, CSAR_VERSION, CREATED_BY, TOSCA_META_ENTRY_DEFINITIONS };
73 public final static String ARTIFACTS_METADATA_FILE = "HEAT.meta";
75 public static final String TOSCA_CSAR_EXTENSION = ".csar";
80 * @param componentsUtils
83 public static Either<Boolean, ResponseFormat> validateCsar(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
84 Either<Boolean, ResponseFormat> validateStatus = validateIsTOSCAMetadataExist(csar, csarUUID, componentsUtils);
85 if (validateStatus.isRight()) {
86 return Either.right(validateStatus.right().value());
89 removeNonUniqueArtifactsFromCsar(csar);
91 log.trace("TOSCA-Metadata/TOSCA.meta file found, CSAR id {}", csarUUID);
92 validateStatus = validateTOSCAMetadataFile(csar, csarUUID, componentsUtils);
93 if (validateStatus.isRight()) {
94 return Either.right(validateStatus.right().value());
96 return Either.left(true);
99 private static void removeNonUniqueArtifactsFromCsar(Map<String, byte[]> csar) {
101 List<String> nonUniqueArtifactsToRemove = new ArrayList<>();
102 String[] paths = csar.keySet().toArray(new String[csar.keySet().size()]);
103 int numberOfArtifacts = paths.length;
104 for(int i = 0; i < numberOfArtifacts; ++i ){
105 collectNonUniqueArtifact(paths, i, numberOfArtifacts, nonUniqueArtifactsToRemove);
107 nonUniqueArtifactsToRemove.stream().forEach(path->csar.remove(path));
110 private static void collectNonUniqueArtifact( String[] paths, int currInd, int numberOfArtifacts, List<String> nonUniqueArtifactsToRemove) {
112 String[] parsedPath = paths[currInd].split("/");
113 String[] otherParsedPath;
114 int artifactNameInd = parsedPath.length - 1;
115 for(int j = currInd + 1; j < numberOfArtifacts; ++j ){
116 otherParsedPath = paths[j].split("/");
117 if(parsedPath.length == otherParsedPath.length && parsedPath.length > 3 && isEqualArtifactNames(parsedPath, otherParsedPath)){
118 log.error("Can't upload two artifact with the same name {}. The artifact with path {} will be handled, and the artifact with path {} will be ignored. ",
119 parsedPath[artifactNameInd], paths[currInd], paths[j]);
120 nonUniqueArtifactsToRemove.add(paths[j]);
125 private static boolean isEqualArtifactNames(String[] parsedPath, String[] otherParsedPath) {
126 boolean isEqualArtifactNames = false;
127 int artifactNameInd = parsedPath.length - 1;
128 int artifactGroupTypeInd = parsedPath.length - 3;
129 String groupType = parsedPath[artifactGroupTypeInd];
130 String artifactName = parsedPath[artifactNameInd];
131 String otherGroupType = otherParsedPath[artifactGroupTypeInd];
132 String otherArtifactName = otherParsedPath[artifactNameInd];
133 String vfcToscaName = parsedPath.length == 5 ? parsedPath[1] : null;
135 if(artifactName.equalsIgnoreCase(otherArtifactName) && groupType.equalsIgnoreCase(otherGroupType)){
136 isEqualArtifactNames = vfcToscaName == null ? true : vfcToscaName.equalsIgnoreCase(otherParsedPath[1]);
138 return isEqualArtifactNames;
141 public static Either<ImmutablePair<String, String>, ResponseFormat> getToscaYaml(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
142 Either<Boolean, ResponseFormat> validateStatus = validateIsTOSCAMetadataExist(csar, csarUUID, componentsUtils);
143 if (validateStatus.isRight()) {
144 return Either.right(validateStatus.right().value());
146 Pattern pattern = Pattern.compile(TOSCA_METADATA_PATH_PATTERN);
147 Optional<String> keyOp = csar.keySet().stream().filter(k -> pattern.matcher(k).matches()).findAny();
148 if(!keyOp.isPresent()){
149 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
150 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
151 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
153 byte[] toscaMetaBytes = csar.get(keyOp.get());
154 Properties props = new Properties();
156 //props.load(new ByteArrayInputStream(toscaMetaBytes));
157 String propStr = new String(toscaMetaBytes);
158 props.load(new StringReader(propStr.replace("\\","\\\\")));
159 } catch (IOException e) {
160 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID, e);
161 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
162 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
165 String yamlFileName = props.getProperty(TOSCA_META_ENTRY_DEFINITIONS);
166 String[] ops = yamlFileName.split(DEL_PATTERN);
167 List<String> list = Arrays.asList(ops);
168 String result = list.stream().map(x -> x).collect(Collectors.joining(DEL_PATTERN));
169 keyOp = csar.keySet().stream().filter(k -> Pattern.compile(result).matcher(k).matches()).findAny();
170 if(!keyOp.isPresent()){
171 log.debug("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file, csar ID {}", csarUUID);
172 BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
173 return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, yamlFileName));
176 log.trace("Found Entry-Definitions property in TOSCA-Metadata/TOSCA.meta, Entry-Definitions: {}, CSAR id: {}", yamlFileName, csarUUID);
177 byte[] yamlFileBytes = csar.get(yamlFileName);
178 if (yamlFileBytes == null) {
179 log.debug("Entry-Definitions {} file not found in csar, csar ID {}", yamlFileName, csarUUID);
180 BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions " + yamlFileName + " file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
181 return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, yamlFileName));
184 String yamlFileContents = new String(yamlFileBytes);
186 return Either.left(new ImmutablePair<String, String>(yamlFileName, yamlFileContents));
189 public static Either<ImmutablePair<String, String>, ResponseFormat> getArtifactsMeta(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
191 if( !csar.containsKey(CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE) ) {
192 log.debug("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file, csar ID {}", csarUUID);
193 BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
194 return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, ARTIFACTS_METADATA_FILE));
197 log.trace("Found Entry-Definitions property in TOSCA-Metadata/TOSCA.meta, Entry-Definitions: {}, CSAR id: {}", ARTIFACTS_METADATA_FILE, csarUUID);
198 byte[] artifactsMetaBytes = csar.get(CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE);
199 if (artifactsMetaBytes == null) {
200 log.debug("Entry-Definitions {}{} file not found in csar, csar ID {}", CsarUtils.ARTIFACTS_PATH, ARTIFACTS_METADATA_FILE, csarUUID);
201 BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions " + CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE + " file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
202 return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE));
205 String artifactsFileContents = new String(artifactsMetaBytes);
207 return Either.left(new ImmutablePair<String, String>(CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE, artifactsFileContents));
210 public static Either<ImmutablePair<String, byte[]>, ResponseFormat> getArtifactsContent(String csarUUID, Map<String, byte[]> csar, String artifactPath, String artifactName, ComponentsUtils componentsUtils) {
211 if (!csar.containsKey(artifactPath)) {
212 log.debug("Entry-Definitions entry not found in Artifacts/HEAT.meta file, csar ID {}", csarUUID);
213 BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
214 return Either.right(componentsUtils.getResponseFormat(ActionStatus.ARTIFACT_NOT_FOUND_IN_CSAR, CsarUtils.ARTIFACTS_PATH + artifactName, csarUUID));
217 log.trace("Found Entry-Definitions property in Artifacts/HEAT.meta, Entry-Definitions: {}, CSAR id: {}", artifactPath, csarUUID);
218 byte[] artifactFileBytes = csar.get(artifactPath);
219 if (artifactFileBytes == null) {
220 log.debug("Entry-Definitions {}{} file not found in csar, csar ID {}", CsarUtils.ARTIFACTS_PATH, artifactName, csarUUID);
221 BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions " + artifactPath + " file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
222 return Either.right(componentsUtils.getResponseFormat(ActionStatus.ARTIFACT_NOT_FOUND_IN_CSAR, artifactPath, csarUUID));
225 return Either.left(new ImmutablePair<String, byte[]>(artifactName, artifactFileBytes));
228 private static Either<Boolean, ResponseFormat> validateTOSCAMetadataFile(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
230 Pattern pattern = Pattern.compile(TOSCA_METADATA_PATH_PATTERN);
231 Optional<String> keyOp = csar.keySet().stream().filter(k -> pattern.matcher(k).matches()).findAny();
232 if(!keyOp.isPresent()){
233 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
234 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
235 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
238 byte[] toscaMetaBytes = csar.get(keyOp.get());
239 String toscaMetadata = new String(toscaMetaBytes);
240 String[] splited = toscaMetadata.split(NEW_LINE_DELM);
241 if (splited == null || splited.length < TOSCA_METADATA_FIELDS.length) {
242 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
243 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
244 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
247 * if(splited.length == TOSCA_METADATA_FIELDS.length){ if(!toscaMetadata.endsWith(NEW_LINE_DELM)){ log. debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}" , csarUUID);
248 * BeEcompErrorManager.getInstance(). logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " +csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR); return
249 * Either.right(componentsUtils.getResponseFormat(ActionStatus. CSAR_INVALID_FORMAT, csarUUID)); } }
252 Either<Boolean, ResponseFormat> block_0Status = validateBlock_0(csarUUID, splited, componentsUtils);
253 if (block_0Status.isRight()) {
254 return Either.right(block_0Status.right().value());
257 return Either.left(true);
261 private static Either<Boolean, ResponseFormat> validateBlock_0(String csarUUID, String[] splited, ComponentsUtils componentsUtils) {
263 for (String toscaField : TOSCA_METADATA_FIELDS) {
265 Properties props = new Properties();
268 props.load(new ByteArrayInputStream(splited[index].getBytes()));
269 } catch (IOException e) {
270 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID, e);
271 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
272 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
274 if (!props.containsKey(toscaField)) {
275 log.debug("TOSCA.meta file format is invalid: No new line after block_0 as expected in csar, csar ID {}", csarUUID);
276 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
277 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
279 String value = props.getProperty(toscaField);
280 if (value == null || value.isEmpty()) {
281 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
282 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
283 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
286 // TOSCA-Meta-File-Version & CSAR-Version : digit.digit - format
288 if (toscaField.equals(TOSCA_META_FILE_VERSION) || toscaField.equals(CSAR_VERSION)) {
289 if (!validateTOSCAMetaProperty(value)) {
290 log.debug("TOSCA-Metadata/TOSCA.meta file contains {} in wrong format (digit.digit), csar ID {}", toscaField, csarUUID);
291 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
292 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
297 return Either.left(true);
300 private static boolean validateTOSCAMetaProperty(String toscaProperty) {
301 final String FLOAT_STRING = "^\\d{1}[.]\\d{1}$";
302 final Pattern FLOAT_PATTERN = Pattern.compile(FLOAT_STRING);
304 Matcher floatMatcher = FLOAT_PATTERN.matcher(toscaProperty);
305 return floatMatcher.matches();
308 private static Either<Boolean, ResponseFormat> validateIsTOSCAMetadataExist(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
309 if (csar == null || csar.isEmpty()) {
310 log.debug("Error when fetching csar with ID {}", csarUUID);
311 BeEcompErrorManager.getInstance().logBeDaoSystemError("Creating resource from CSAR: fetching CSAR with id " + csarUUID + " failed");
312 ResponseFormat responseFormat = componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID, csarUUID);
313 return Either.right(responseFormat);
316 Pattern pattern = Pattern.compile(TOSCA_METADATA_PATH_PATTERN);
317 Optional<String> keyOp = csar.keySet().stream().filter(k -> pattern.matcher(k).matches()).findAny();
318 if(!keyOp.isPresent()){
320 log.debug("TOSCA-Metadata/TOSCA.meta file not found in csar, csar ID {}", csarUUID);
321 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
322 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID, csarUUID));
324 byte[] toscaMetaBytes = csar.get(keyOp.get());
325 // && exchanged for ||
326 if (toscaMetaBytes == null || toscaMetaBytes.length == 0) {
327 log.debug("TOSCA-Metadata/TOSCA.meta file not found in csar, csar ID {}", csarUUID);
328 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
329 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID, csarUUID));
332 return Either.left(Boolean.TRUE);
335 public static Either<String, ResponseFormat> getToscaYamlChecksum(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
337 Either<ImmutablePair<String, String>, ResponseFormat> toscaYamlRes = getToscaYaml(csar, csarUUID, componentsUtils);
338 if (toscaYamlRes.isRight() || toscaYamlRes.left().value() == null || toscaYamlRes.left().value().getRight() == null) {
339 log.debug("Faild to create toscaYamlChecksum for csar, csar ID {}", csarUUID);
340 return Either.right(toscaYamlRes.right().value());
343 String newCheckSum = GeneralUtility.calculateMD5Base64EncodedByByteArray(toscaYamlRes.left().value().getRight().getBytes());
344 return Either.left(newCheckSum);
348 public static boolean isCsarPayloadName(String payloadName) {
349 if (payloadName == null)
351 return payloadName.toLowerCase().endsWith(TOSCA_CSAR_EXTENSION);