From 532b6da80ebd6977aa27300ab3cbe7b21d88609a Mon Sep 17 00:00:00 2001 From: shiria Date: Sun, 17 Mar 2019 09:41:10 +0200 Subject: [PATCH] Load TOSCA package into Tosca model object Change-Id: I24360bcd988df48359eb6092f6a80e989e35f026 Issue-ID: SDC-2192 Signed-off-by: shiria --- .../org/onap/sdc/tosca/datatypes/model/Status.java | 6 +- .../sdc/tosca/errors/InvalidToscaFile.java | 53 +++++ .../sdc/tosca/errors/InvalidToscaMetaFile.java | 51 +++++ .../errors/ToscaEntryDefinitionWasNotFound.java | 49 ++++ .../sdc/tosca/errors/ToscaErrorCodes.java | 13 +- .../sdc/tosca/services/ToscaAnalyzerService.java | 2 + .../services/impl/ToscaAnalyzerServiceImpl.java | 247 ++++++++++++++++++--- .../impl/ToscaAnalyzerServiceImplTest.java | 165 +++++++++++++- .../mock/analyzerService/importConvertTest.yml | 14 ++ .../mock/analyzerService/invalidTosca.meta | 6 + .../mock/analyzerService/invalidToscaFileTest.yml | 8 + .../toscaPackageInvalidEntryDef.csar | Bin 0 -> 13114 bytes .../analyzerService/toscaPackageWithMetadata.csar | Bin 0 -> 13070 bytes .../toscaPackageWithoutMetadata.csar | Bin 0 -> 12828 bytes .../resources/mock/analyzerService/validTosca.meta | 7 + 15 files changed, 581 insertions(+), 40 deletions(-) create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaFile.java create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaMetaFile.java create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaEntryDefinitionWasNotFound.java create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/importConvertTest.yml create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidTosca.meta create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidToscaFileTest.yml create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageInvalidEntryDef.csar create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageWithMetadata.csar create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageWithoutMetadata.csar create mode 100644 openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/validTosca.meta diff --git a/common/onap-tosca-datatype/src/main/java/org/onap/sdc/tosca/datatypes/model/Status.java b/common/onap-tosca-datatype/src/main/java/org/onap/sdc/tosca/datatypes/model/Status.java index c0f6d874d7..222fdd0a61 100644 --- a/common/onap-tosca-datatype/src/main/java/org/onap/sdc/tosca/datatypes/model/Status.java +++ b/common/onap-tosca-datatype/src/main/java/org/onap/sdc/tosca/datatypes/model/Status.java @@ -25,9 +25,13 @@ package org.onap.sdc.tosca.datatypes.model; public enum Status { SUPPORTED("supported"), + supported("supported"), UNSUPPORTED("unsupported"), + unsupported("unsupported"), EXPERIMENTAL("experimental"), - DEPRECATED("deprecated"),; + experimental("experimental"), + DEPRECATED("deprecated"), + deprecated("deprecated"),; private String displayName; Status(String displayName) { diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaFile.java b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaFile.java new file mode 100644 index 0000000000..1bea2bbce6 --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaFile.java @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2019, Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.tosca.errors; + +import org.openecomp.sdc.common.errors.ErrorCategory; +import org.openecomp.sdc.common.errors.ErrorCode; + + +public class InvalidToscaFile { + + private static final String INVALID_TOSCA_FILE = + "Tosca file '%s' is not following TOSCA spec, can't be parsed. Related error - '%s'"; + private final ErrorCode.ErrorCodeBuilder builder = new ErrorCode.ErrorCodeBuilder(); + + /** + * Instantiates a new invalid TOSCA file error builder. + * + * @param toscaFileName tosca file name + * @param parseError parse error message + */ + public InvalidToscaFile(String toscaFileName, String parseError) { + builder.withId(ToscaErrorCodes.INVALID_TOSCA_FILE); + builder.withCategory(ErrorCategory.APPLICATION); + builder.withMessage(String.format(INVALID_TOSCA_FILE, toscaFileName, parseError)); + } + + /** + * Build error code. + * + * @return the error code + */ + public ErrorCode build() { + return builder.build(); + } +} diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaMetaFile.java b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaMetaFile.java new file mode 100644 index 0000000000..54e22c97ae --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/InvalidToscaMetaFile.java @@ -0,0 +1,51 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2019, Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.tosca.errors; + +import org.openecomp.sdc.common.errors.ErrorCategory; +import org.openecomp.sdc.common.errors.ErrorCode; + + +public class InvalidToscaMetaFile { + + private static final String INVALID_TOSCA_META_FILE = "Missing data - TOSCA.meta file must include '%s' data."; + private final ErrorCode.ErrorCodeBuilder builder = new ErrorCode.ErrorCodeBuilder(); + + /** + * Instantiates a new invalid TOSCA meta file error builder. + * + * @param missingData name of the missing data + */ + public InvalidToscaMetaFile(String missingData) { + builder.withId(ToscaErrorCodes.INVALID_TOSCA_META_FILE); + builder.withCategory(ErrorCategory.APPLICATION); + builder.withMessage(String.format(INVALID_TOSCA_META_FILE, missingData)); + } + + /** + * Build error code. + * + * @return the error code + */ + public ErrorCode build() { + return builder.build(); + } +} diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaEntryDefinitionWasNotFound.java b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaEntryDefinitionWasNotFound.java new file mode 100644 index 0000000000..bb34d3a485 --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaEntryDefinitionWasNotFound.java @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2019, Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.tosca.errors; + +import org.openecomp.sdc.common.errors.ErrorCategory; +import org.openecomp.sdc.common.errors.ErrorCode; + + +public class ToscaEntryDefinitionWasNotFound { + + private static final String ENTRY_DEFINITION_WAS_NOT_FOUND = "TOSCA Entry Definition was not found"; + private final ErrorCode.ErrorCodeBuilder builder = new ErrorCode.ErrorCodeBuilder(); + + /** + * Instantiates a new invalid TOSCA entry definition was not found error builder. + */ + public ToscaEntryDefinitionWasNotFound() { + builder.withId(ToscaErrorCodes.INVALID_TOSCA_ENTRY_DEF_WAS_NOT_FOUND); + builder.withCategory(ErrorCategory.APPLICATION); + builder.withMessage(ENTRY_DEFINITION_WAS_NOT_FOUND); + } + + /** + * Build error code. + * + * @return the error code + */ + public ErrorCode build() { + return builder.build(); + } +} diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaErrorCodes.java b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaErrorCodes.java index 2e449e4acc..aec4091e68 100644 --- a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaErrorCodes.java +++ b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/errors/ToscaErrorCodes.java @@ -2,14 +2,14 @@ * ============LICENSE_START======================================================= * SDC * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2019, Nordix Foundation. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,6 +22,10 @@ package org.openecomp.sdc.tosca.errors; class ToscaErrorCodes { + private ToscaErrorCodes() { + throw new IllegalStateException("Utility class"); + } + static final String INVALID_SUBSTITUTE_NODE_TEMPLATE = "INVALID_SUBSTITUTE_NODE_TEMPLATE"; static final String INVALID_SUBSTITUTION_SERVICE_TEMPLATE = "INVALID_SUBSTITUTION_SERVICE_TEMPLATE"; @@ -32,6 +36,9 @@ class ToscaErrorCodes { "TOSCA_INVALID_SUBSTITUTE_NODE_TEMPLATE"; static final String TOSCA_INVALID_ADD_ACTION_NULL_ENTITY = "TOSCA_INVALID_ADD_ACTION_NULL_ENTITY"; static final String INVALID_INTERFACE_VALUE = "INVALID_INTERFACE_VALUE"; + static final String INVALID_TOSCA_FILE = "INVALID_TOSCA_FILE"; + static final String INVALID_TOSCA_META_FILE = "INVALID_TOSCA_META_FILE"; + static final String INVALID_TOSCA_ENTRY_DEF_WAS_NOT_FOUND = "INVALID_TOSCA_ENTRY_DEF_WAS_NOT_FOUND"; } diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/ToscaAnalyzerService.java b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/ToscaAnalyzerService.java index 7e2c463e29..b21d847a07 100644 --- a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/ToscaAnalyzerService.java +++ b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/ToscaAnalyzerService.java @@ -85,4 +85,6 @@ public interface ToscaAnalyzerService { List> calculateExposedRequirements(List> nodeTypeRequirementsDefinitionList, Map nodeTemplateRequirementsAssignment); + + ToscaServiceModel loadToscaCsarPackage(byte[] toscaCsarPackage); } diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImpl.java b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImpl.java index b254671dc7..1f0b728c53 100644 --- a/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImpl.java +++ b/openecomp-be/lib/openecomp-tosca-lib/src/main/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImpl.java @@ -16,7 +16,11 @@ package org.openecomp.sdc.tosca.services.impl; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -27,6 +31,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; @@ -46,13 +52,19 @@ import org.onap.sdc.tosca.datatypes.model.RequirementAssignment; import org.onap.sdc.tosca.datatypes.model.RequirementDefinition; import org.onap.sdc.tosca.datatypes.model.ServiceTemplate; import org.onap.sdc.tosca.services.ToscaExtensionYamlUtil; +import org.onap.sdc.tosca.services.YamlUtil; import org.openecomp.core.utilities.CommonMethods; +import org.openecomp.core.utilities.file.FileContentHandler; +import org.openecomp.core.utilities.file.FileUtils; import org.openecomp.sdc.common.errors.CoreException; import org.openecomp.sdc.common.errors.SdcRuntimeException; import org.openecomp.sdc.tosca.datatypes.ToscaElementTypes; import org.openecomp.sdc.tosca.datatypes.ToscaFlatData; import org.openecomp.sdc.tosca.datatypes.ToscaServiceModel; +import org.openecomp.sdc.tosca.errors.InvalidToscaFile; +import org.openecomp.sdc.tosca.errors.InvalidToscaMetaFile; import org.openecomp.sdc.tosca.errors.ToscaElementTypeNotFoundErrorBuilder; +import org.openecomp.sdc.tosca.errors.ToscaEntryDefinitionWasNotFound; import org.openecomp.sdc.tosca.errors.ToscaFileNotFoundErrorBuilder; import org.openecomp.sdc.tosca.errors.ToscaInvalidEntryNotFoundErrorBuilder; import org.openecomp.sdc.tosca.errors.ToscaInvalidSubstituteNodeTemplatePropertiesErrorBuilder; @@ -72,6 +84,9 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { private static final String GET_CAPABILITY_TYPE_METHOD_NAME = "getCapability_types"; private static final String TOSCA_DOT = "tosca."; private static final String DOT_ROOT = ".Root"; + private static final String IMPORTS = "imports"; + private static final String TOSCA_META_FILE = "TOSCA-Metadata/TOSCA.meta"; + private static final String ENTRY_DEFINITIONS = "Entry-Definitions"; @Override public List> calculateExposedRequirements( @@ -99,6 +114,151 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { return nodeTypeRequirementsDefinitionList; } + @Override + public ToscaServiceModel loadToscaCsarPackage(byte[] toscaCsarPackage) { + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + ToscaExtensionYamlUtil toscaExtensionYamlUtil = new ToscaExtensionYamlUtil(); + FileContentHandler artifactFiles = new FileContentHandler(); + + try (ZipInputStream inputZipStream = new ZipInputStream(new ByteArrayInputStream(toscaCsarPackage))) { + ZipEntry zipEntry; + while ((zipEntry = inputZipStream.getNextEntry()) != null) { + byte[] fileContent = FileUtils.toByteArray(inputZipStream); + String currentEntryName = zipEntry.getName(); + if (!isFile(currentEntryName)) { + continue; + } + if (isYamlFile(currentEntryName) && isToscaYamlFile(fileContent)) { + loadToscaYamlFile(toscaServiceModel, toscaExtensionYamlUtil, fileContent, currentEntryName); + } else if (currentEntryName.equals(TOSCA_META_FILE)) { + loadToscaMetaFile(toscaServiceModel, fileContent); + } else { + artifactFiles.addFile(currentEntryName, fileContent); + } + } + toscaServiceModel.setArtifactFiles(artifactFiles); + if (StringUtils.isEmpty(toscaServiceModel.getEntryDefinitionServiceTemplate())) { + handleToscaCsarWithoutToscaMetadata(toscaServiceModel); + } + + } catch (IOException exc) { + throw new SdcRuntimeException(exc.getMessage(), exc); + } + return toscaServiceModel; + } + + private void handleToscaCsarWithoutToscaMetadata(ToscaServiceModel toscaServiceModel) { + for (String fileName : toscaServiceModel.getServiceTemplates().keySet()) { + if (!fileName.contains("/")) { + if (StringUtils.isNotEmpty(toscaServiceModel.getEntryDefinitionServiceTemplate())) { + throw new CoreException(new ToscaEntryDefinitionWasNotFound().build()); + } + toscaServiceModel.setEntryDefinitionServiceTemplate(fileName); + } + } + } + + void loadToscaMetaFile(ToscaServiceModel toscaServiceModel, byte[] toscaMetaFileContent) { + String toscaMeta = new String(toscaMetaFileContent); + Map toscaMetaMap = new YamlUtil().yamlToObject(toscaMeta, Map.class); + if (Objects.isNull(toscaMetaMap.get(ENTRY_DEFINITIONS))) { + throw new CoreException(new InvalidToscaMetaFile(ENTRY_DEFINITIONS).build()); + } + String entryDefinition = (String) toscaMetaMap.get(ENTRY_DEFINITIONS); + toscaServiceModel.setEntryDefinitionServiceTemplate(entryDefinition); + } + + void loadToscaYamlFile(ToscaServiceModel toscaServiceModel, ToscaExtensionYamlUtil toscaExtensionYamlUtil, + byte[] fileContent, String fileFullName) { + try { + String serviceTemplateYamlString = convertServiceTemplateImport(toscaExtensionYamlUtil, fileContent); + ServiceTemplate serviceTemplate = + toscaExtensionYamlUtil.yamlToObject(serviceTemplateYamlString, ServiceTemplate.class); + toscaServiceModel.addServiceTemplate(fileFullName, serviceTemplate); + + } catch (Exception exc) { + throw new CoreException(new InvalidToscaFile(fileFullName, exc.getMessage()).build()); + } + } + + String convertServiceTemplateImport(ToscaExtensionYamlUtil toscaExtensionYamlUtil, byte[] fileContent) { + + Map serviceTemplateMap = toscaExtensionYamlUtil.yamlToObject(new String(fileContent), Map.class); + convertToscaImports(serviceTemplateMap, toscaExtensionYamlUtil); + return toscaExtensionYamlUtil.objectToYaml(serviceTemplateMap); + } + + private void convertToscaImports(Map serviceTemplateMap, ToscaExtensionYamlUtil toscaExtensionYamlUtil) { + List> convertedImport = new ArrayList<>(); + Object importObj = serviceTemplateMap.get(IMPORTS); + if (!(importObj instanceof List)) { + throw new SdcRuntimeException("Illegal Statement"); + } + List imports = (List) importObj; + if (CollectionUtils.isEmpty(imports)) { + return; + } + for (Object importEntry : imports) { + convertToscaImportEntry(convertedImport, importEntry, toscaExtensionYamlUtil); + } + serviceTemplateMap.remove(IMPORTS); + serviceTemplateMap.put(IMPORTS, convertedImport); + } + + private void convertToscaImportEntry(List> convertedImport, Object importEntry, + ToscaExtensionYamlUtil toscaExtensionYamlUtil) { + if (importEntry instanceof String) { + convertImportShortNotation(convertedImport, importEntry.toString()); + } else if (importEntry instanceof Map) { + if (((Map) importEntry).containsKey("file")) { + Import importObject = toscaExtensionYamlUtil + .yamlToObject(toscaExtensionYamlUtil.objectToYaml(importEntry), + Import.class); + convertImportExtendNotation(convertedImport, importObject); + } else { + convertedImport.add((Map) importEntry); + } + } + } + + private void convertImportExtendNotation(List> convertedImport, Import importEntry) { + Map importMap = new HashMap(); + importMap.put(FileUtils.getFileWithoutExtention(getFileName(importEntry.getFile()).replaceAll("/", "_")), + importEntry); + convertedImport.add(importMap); + } + + private void convertImportShortNotation(List> convertImport, String fileFullName) { + Import importObject = new Import(); + importObject.setFile(fileFullName); + Map importMap = new HashMap(); + importMap + .put((FileUtils.getFileWithoutExtention(getFileName(fileFullName)).replaceAll("/", "_")), importObject); + convertImport.add(importMap); + } + + private static String getFileName(String relativeFileName) { + if (relativeFileName.contains("../")) { + return relativeFileName.replace("../", ""); + } else { + return relativeFileName; + } + + } + + private static boolean isFile(String currentEntryName) { + return !(currentEntryName.endsWith("\\") || currentEntryName.endsWith("/")); + } + + private boolean isYamlFile(String fileName) { + return fileName.endsWith("yaml") || fileName.endsWith("yml"); + } + + private boolean isToscaYamlFile(byte[] fileContent) { + Map fileMap = new YamlUtil().yamlToObject(new String(fileContent), Map.class); + return fileMap.containsKey("tosca_definitions_version"); + } + private void updateMinMaxOccurencesForNodeTypeRequirement(Map.Entry entry, Map nodeTypeRequirementsMap) { Object max = nodeTypeRequirementsMap.get(entry.getKey()).getOccurrences() != null @@ -209,7 +369,7 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { if (Objects.nonNull(serviceTemplate.getTopology_template()) && MapUtils.isNotEmpty( serviceTemplate.getTopology_template().getNode_templates())) { for (Map.Entry nodeTemplateEntry : serviceTemplate.getTopology_template() - .getNode_templates().entrySet()) { + .getNode_templates().entrySet()) { if (isTypeOf(nodeTemplateEntry.getValue(), nodeType, serviceTemplate, toscaServiceModel)) { nodeTemplates.put(nodeTemplateEntry.getKey(), nodeTemplateEntry.getValue()); } @@ -222,8 +382,11 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { @Override public Optional fetchNodeType(String nodeTypeKey, Collection serviceTemplates) { Optional> nodeTypeMap = serviceTemplates.stream().map(ServiceTemplate::getNode_types) - .filter(nodeTypes -> Objects.nonNull(nodeTypes) - && nodeTypes.containsKey(nodeTypeKey)).findFirst(); + .filter(nodeTypes -> Objects.nonNull(nodeTypes) + && nodeTypes + .containsKey( + nodeTypeKey)) + .findFirst(); return nodeTypeMap.map(stringNodeTypeMap -> stringNodeTypeMap.get(nodeTypeKey)); } @@ -285,9 +448,9 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { return Optional.empty(); } - if (substitutableNodeTemplate.getProperties() != null - && substitutableNodeTemplate.getProperties().get(ToscaConstants.SERVICE_TEMPLATE_FILTER_PROPERTY_NAME) - != null) { + if (substitutableNodeTemplate.getProperties() != null && + substitutableNodeTemplate.getProperties().get(ToscaConstants.SERVICE_TEMPLATE_FILTER_PROPERTY_NAME) + != null) { Object serviceTemplateFilter = substitutableNodeTemplate.getProperties().get(ToscaConstants.SERVICE_TEMPLATE_FILTER_PROPERTY_NAME); if (serviceTemplateFilter instanceof Map) { @@ -389,14 +552,14 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { } private boolean isSameRelationship(RequirementAssignment requirementAssignment, String relationship) { - return relationship != null - && (requirementAssignment.getRelationship() == null - || !requirementAssignment.getRelationship().equals(relationship)); + return relationship != null && (requirementAssignment.getRelationship() == null || !requirementAssignment + .getRelationship() + .equals(relationship)); } private boolean isSameRequirement(RequirementAssignment requirementAssignment, String node) { return node != null && (requirementAssignment.getNode() == null || !requirementAssignment.getNode() - .equals(node)); + .equals(node)); } private boolean isSameCapability(RequirementAssignment requirementAssignment, String capability) { @@ -486,8 +649,11 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { Import.class); handleImportWithNoFileEntry(anImport); String importFile = anImport.getFile(); - ServiceTemplate template = toscaServiceModel.getServiceTemplates().get(fetchFileNameForImport(importFile, - serviceTemplate.getMetadata() == null ? null : serviceTemplate.getMetadata().get("filename"))); + ServiceTemplate template = toscaServiceModel.getServiceTemplates() + .get(fetchFullFileNameForImport(importFile, + serviceTemplate.getMetadata() == null ? null : + serviceTemplate.getMetadata().get("filename"), + serviceTemplate, toscaServiceModel)); if (Objects.isNull(template) || createdFilesScanned .contains(ToscaUtil.getServiceTemplateFileName(template))) { continue; @@ -573,8 +739,9 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { Import importServiceTemplate = toscaExtensionYamlUtil .yamlToObject(toscaExtensionYamlUtil.objectToYaml(importObject), Import.class); - String fileName = fetchFileNameForImport(importServiceTemplate.getFile(), - serviceTemplate.getMetadata() == null ? null : serviceTemplate.getMetadata().get("filename")); + String fileName = fetchFullFileNameForImport(importServiceTemplate.getFile(), + serviceTemplate.getMetadata() == null ? null : serviceTemplate.getMetadata().get("filename"), + serviceTemplate, toscaModel); if (filesScanned.contains(fileName)) { return false; } else { @@ -590,16 +757,27 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { return found; } - private String fetchFileNameForImport(String importServiceTemplateFile, String currentMetadatafileName) { - if (importServiceTemplateFile.contains("../")) { - return importServiceTemplateFile.replace("../", ""); - } else if (currentMetadatafileName != null && currentMetadatafileName.indexOf('/') != -1) { - return currentMetadatafileName.substring(0, currentMetadatafileName.indexOf('/')) + "/" - + importServiceTemplateFile; - } else { - return importServiceTemplateFile; + String fetchFullFileNameForImport(String importServiceTemplateFile, String currentMetadatafileName, + ServiceTemplate serviceTemplate, ToscaServiceModel toscaServiceModel) { + Optional> serviceTemplateEntry = + toscaServiceModel.getServiceTemplates().entrySet().stream() + .filter(entry -> entry.getValue().equals(serviceTemplate)).findFirst(); + if (!serviceTemplateEntry.isPresent()) { + if (importServiceTemplateFile.contains("../")) { + return importServiceTemplateFile.replace("../", ""); + } else if (currentMetadatafileName != null && currentMetadatafileName.indexOf('/') != -1) { + return currentMetadatafileName.substring(0, currentMetadatafileName.indexOf('/')) + "/" + + importServiceTemplateFile; + } else { + return importServiceTemplateFile; + } } + Path currentPath = Paths.get(serviceTemplateEntry.get().getKey()).getParent(); + if (currentPath == null) { + currentPath = Paths.get(""); + } + return currentPath.resolve(importServiceTemplateFile).normalize().toString().replaceAll("\\\\", "/"); } private boolean enrichEntityFromCurrentServiceTemplate(ToscaElementTypes elementType, String typeId, @@ -696,7 +874,7 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { int rootScanStartInx) { String derivedFrom; if (serviceTemplate.getCapability_types() != null && serviceTemplate.getCapability_types() - .containsKey(typeId)) { + .containsKey(typeId)) { filesScanned.clear(); flatData.addInheritanceHierarchyType(typeId); @@ -750,10 +928,9 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { for (Map.Entry sourceInterfaceDefEntry : sourceNodeType.getInterfaces().entrySet()) { String interfaceName = sourceInterfaceDefEntry.getKey(); if (!MapUtils.isEmpty(targetNodeType.getInterfaces()) && targetNodeType.getInterfaces() - .containsKey(interfaceName)) { - combineInterfaces.put(interfaceName, - combineInterfaceDefinition(sourceInterfaceDefEntry.getValue(), - targetNodeType.getInterfaces().get(interfaceName))); + .containsKey(interfaceName)) { + combineInterfaces.put(interfaceName, combineInterfaceDefinition(sourceInterfaceDefEntry.getValue(), + targetNodeType.getInterfaces().get(interfaceName))); } else { combineInterfaces.put(sourceInterfaceDefEntry.getKey(), sourceInterfaceDefEntry.getValue()); } @@ -836,13 +1013,13 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { /* - * Create node type according to the input substitution service template, while the substitution - * service template can be mappted to this node type, for substitution mapping. - * - * @param substitutionServiceTemplate substitution serivce template - * @param nodeTypeDerivedFromValue derived from value for the created node type - * @return the node type - */ + * Create node type according to the input substitution service template, while the substitution + * service template can be mappted to this node type, for substitution mapping. + * + * @param substitutionServiceTemplate substitution serivce template + * @param nodeTypeDerivedFromValue derived from value for the created node type + * @return the node type + */ @Override public NodeType createInitSubstitutionNodeType(ServiceTemplate substitutionServiceTemplate, String nodeTypeDerivedFromValue) { @@ -893,7 +1070,7 @@ public class ToscaAnalyzerServiceImpl implements ToscaAnalyzerService { private Map manageSubstitutionNodeTypeAttributes( - ServiceTemplate substitutionServiceTemplate) { + ServiceTemplate substitutionServiceTemplate) { Map substitutionNodeTypeAttributes = new HashMap<>(); Map attributes = substitutionServiceTemplate.getTopology_template().getOutputs(); if (attributes == null) { diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/test/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImplTest.java b/openecomp-be/lib/openecomp-tosca-lib/src/test/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImplTest.java index 2814f0143b..4962f243ca 100644 --- a/openecomp-be/lib/openecomp-tosca-lib/src/test/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImplTest.java +++ b/openecomp-be/lib/openecomp-tosca-lib/src/test/java/org/openecomp/sdc/tosca/services/impl/ToscaAnalyzerServiceImplTest.java @@ -30,7 +30,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; - +import org.apache.commons.io.IOUtils; +import org.hamcrest.core.StringContains; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -61,6 +62,7 @@ import org.onap.sdc.tosca.datatypes.model.Status; import org.onap.sdc.tosca.datatypes.model.SubstitutionMapping; import org.onap.sdc.tosca.datatypes.model.TopologyTemplate; import org.onap.sdc.tosca.services.ToscaExtensionYamlUtil; +import org.onap.sdc.tosca.services.YamlUtil; import org.openecomp.sdc.common.errors.CoreException; import org.openecomp.sdc.common.errors.SdcRuntimeException; import org.openecomp.sdc.tosca.TestUtil; @@ -1104,5 +1106,166 @@ public class ToscaAnalyzerServiceImplTest { public void testGetFlatEntityThrowsExceptionIncorrectSwitchProvided() { toscaAnalyzerService.getFlatEntity(ToscaElementTypes.RELATIONSHIP_TYPE, null, null, null); } + + @Test + public void getFullPathFromRelativePathBackwards(){ + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + String importFile = "../ImportedServiceTemplate"; + ServiceTemplate mainServiceTemplate = new ServiceTemplate(); + ServiceTemplate importedServiceTemplate = new ServiceTemplate(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + toscaServiceModel.addServiceTemplate("Definitions/service/MainServiceTemplate", mainServiceTemplate); + toscaServiceModel.addServiceTemplate("Definitions/ImportedServiceTemplate", importedServiceTemplate); + + String fileNameForImport = toscaAnalyzerServiceImpl + .fetchFullFileNameForImport(importFile, null, mainServiceTemplate, toscaServiceModel); + assertEquals("Definitions/ImportedServiceTemplate", fileNameForImport); + } + + @Test + public void getFullPathFromRelativePathForwards(){ + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + String importFile = "services/ImportedServiceTemplate"; + ServiceTemplate mainServiceTemplate = new ServiceTemplate(); + ServiceTemplate importedServiceTemplate = new ServiceTemplate(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + toscaServiceModel.addServiceTemplate("Definitions/MainServiceTemplate", mainServiceTemplate); + toscaServiceModel.addServiceTemplate("Definitions/services/ImportedServiceTemplate", importedServiceTemplate); + + String fileNameForImport = toscaAnalyzerServiceImpl + .fetchFullFileNameForImport(importFile, null, mainServiceTemplate, toscaServiceModel); + assertEquals("Definitions/services/ImportedServiceTemplate", fileNameForImport); + } + + @Test + public void getFullPathFromRelativePathMix(){ + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + String importFile = "../types/global/ImportedServiceTemplate"; + ServiceTemplate mainServiceTemplate = new ServiceTemplate(); + ServiceTemplate importedServiceTemplate = new ServiceTemplate(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + toscaServiceModel.addServiceTemplate("Definitions/services/MainServiceTemplate", mainServiceTemplate); + toscaServiceModel.addServiceTemplate("Definitions/types/global/ImportedServiceTemplate", importedServiceTemplate); + + String fileNameForImport = toscaAnalyzerServiceImpl + .fetchFullFileNameForImport(importFile, null, mainServiceTemplate, toscaServiceModel); + assertEquals("Definitions/types/global/ImportedServiceTemplate", fileNameForImport); + } + + @Test + public void testConvertToscaImport() throws Exception { + String inputResourceName = "/mock/analyzerService/importConvertTest.yml"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + + ToscaExtensionYamlUtil toscaExtensionYamlUtil = new ToscaExtensionYamlUtil(); + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + String convertServiceTemplateImport = + toscaAnalyzerServiceImpl.convertServiceTemplateImport(toscaExtensionYamlUtil, uploadedFileData); + + Assert.assertNotNull(convertServiceTemplateImport); + ServiceTemplate serviceTemplate = + new YamlUtil().yamlToObject(convertServiceTemplateImport, ServiceTemplate.class); + Assert.assertNotNull(serviceTemplate.getImports().get(0).get("data")); + Assert.assertNotNull(serviceTemplate.getImports().get(1).get("artifacts")); + Assert.assertNotNull(serviceTemplate.getImports().get(2).get("capabilities")); + Assert.assertNotNull(serviceTemplate.getImports().get(3).get("api_interfaces")); + Assert.assertNotNull(serviceTemplate.getImports().get(4).get("api_util_relationships")); + Assert.assertNotNull(serviceTemplate.getImports().get(5).get("common")); + Assert.assertNotNull(serviceTemplate.getImports().get(6).get("api_util")); + Assert.assertNotNull(serviceTemplate.getImports().get(7).get("relationshipsExt")); + } + + @Test + public void loadValidToscaYamlFileTest() throws Exception { + String inputResourceName = "/mock/analyzerService/ServiceTemplateInterfaceInheritanceTest.yaml"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + + ToscaExtensionYamlUtil toscaExtensionYamlUtil = new ToscaExtensionYamlUtil(); + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + String fileFullName = "Definition/service.yaml"; + toscaAnalyzerServiceImpl + .loadToscaYamlFile(toscaServiceModel, toscaExtensionYamlUtil, uploadedFileData, fileFullName); + Assert.assertNotNull(toscaServiceModel.getServiceTemplate(fileFullName)); + } + + @Test + public void loadInvalidToscaYamlFileTest() throws Exception { + thrown.expect(CoreException.class); + thrown.expectMessage(StringContains.containsString( + "Tosca file 'Definition/service.yaml' is not following TOSCA spec, can't be parsed. Related error - ")); + String inputResourceName = "/mock/analyzerService/invalidToscaFileTest.yml"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + + ToscaExtensionYamlUtil toscaExtensionYamlUtil = new ToscaExtensionYamlUtil(); + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + String fileFullName = "Definition/service.yaml"; + toscaAnalyzerServiceImpl + .loadToscaYamlFile(toscaServiceModel, toscaExtensionYamlUtil, uploadedFileData, fileFullName); + } + + @Test + public void loadValidToscaMetadataFileTest() throws Exception { + String inputResourceName = "/mock/analyzerService/validTosca.meta"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + toscaAnalyzerServiceImpl + .loadToscaMetaFile(toscaServiceModel, uploadedFileData); + Assert.assertEquals("Definitions/service-Service2-template.yml", + toscaServiceModel.getEntryDefinitionServiceTemplate()); + } + + @Test + public void loadInvalidToscaMetadataFileTest() throws Exception { + thrown.expect(CoreException.class); + thrown.expectMessage("Missing data - TOSCA.meta file must include 'Entry-Definitions' data."); + String inputResourceName = "/mock/analyzerService/invalidTosca.meta"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + ToscaServiceModel toscaServiceModel = new ToscaServiceModel(); + toscaAnalyzerServiceImpl + .loadToscaMetaFile(toscaServiceModel, uploadedFileData); + } + + @Test + public void loadToscaCsarPackageWithMetadataTest() throws Exception { + String inputResourceName = "/mock/analyzerService/toscaPackageWithMetadata.csar"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + //InputStream toscaPackage = new ByteArrayInputStream(uploadedFileData); + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + ToscaServiceModel toscaServiceModel = toscaAnalyzerServiceImpl.loadToscaCsarPackage(uploadedFileData); + assertNotNull(toscaServiceModel); + assertEquals("Definitions/service.yaml", toscaServiceModel.getEntryDefinitionServiceTemplate()); + assertEquals(10, toscaServiceModel.getServiceTemplates().size()); + assertEquals(1, toscaServiceModel.getArtifactFiles().getFiles().size()); + } + + @Test + public void loadToscaCsarPackageWithoutMetadataTest() throws Exception { + String inputResourceName = "/mock/analyzerService/toscaPackageWithoutMetadata.csar"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + //InputStream toscaPackage = new ByteArrayInputStream(uploadedFileData); + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + ToscaServiceModel toscaServiceModel = toscaAnalyzerServiceImpl.loadToscaCsarPackage(uploadedFileData); + assertNotNull(toscaServiceModel); + assertEquals("service.yaml", toscaServiceModel.getEntryDefinitionServiceTemplate()); + assertEquals(10, toscaServiceModel.getServiceTemplates().size()); + assertEquals(1, toscaServiceModel.getArtifactFiles().getFiles().size()); + } + + @Test + public void loadInvalidToscaCsarPackageWithoutEntryDefTest() throws Exception { + thrown.expect(CoreException.class); + thrown.expectMessage("TOSCA Entry Definition was not found"); + String inputResourceName = "/mock/analyzerService/toscaPackageInvalidEntryDef.csar"; + byte[] uploadedFileData = IOUtils.toByteArray(this.getClass().getResource(inputResourceName)); + //InputStream toscaPackage = new ByteArrayInputStream(uploadedFileData); + ToscaAnalyzerServiceImpl toscaAnalyzerServiceImpl = new ToscaAnalyzerServiceImpl(); + toscaAnalyzerServiceImpl.loadToscaCsarPackage(uploadedFileData); + } } diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/importConvertTest.yml b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/importConvertTest.yml new file mode 100644 index 0000000000..08ddc3c206 --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/importConvertTest.yml @@ -0,0 +1,14 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +imports: +- data.yml +- artifacts.yml +- capabilities.yml +- ../../api/interfaces.yml +- api/util/relationships.yml +- common: + file: api/common.yaml +- file: api/util.yaml +- relationshipsExt.yml +node_types: + tosca.nodes.Root: + description: The TOSCA Node Type all other TOSCA base Node Types derive from \ No newline at end of file diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidTosca.meta b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidTosca.meta new file mode 100644 index 0000000000..c9be3f4d2e --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidTosca.meta @@ -0,0 +1,6 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-By: Carlos Santana + +Name: csar.meta +Content-Type: text/plain \ No newline at end of file diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidToscaFileTest.yml b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidToscaFileTest.yml new file mode 100644 index 0000000000..f572a3a196 --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/invalidToscaFileTest.yml @@ -0,0 +1,8 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +imports: +- data.yml +- artifacts.yml +node_types: + tosca.nodes.Root: + description: The TOSCA Node Type all other TOSCA base Node Types derive from + invalidFiled: invalid filed \ No newline at end of file diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageInvalidEntryDef.csar b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageInvalidEntryDef.csar new file mode 100644 index 0000000000000000000000000000000000000000..02c2caf555a3dbf77c10ed2d301cafbee0acd037 GIT binary patch literal 13114 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 T_~TB5_qXwTni@;{$JPG_Q6_yZ literal 0 HcmV?d00001 diff --git a/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageWithoutMetadata.csar b/openecomp-be/lib/openecomp-tosca-lib/src/test/resources/mock/analyzerService/toscaPackageWithoutMetadata.csar new file mode 100644 index 0000000000000000000000000000000000000000..0256cc9797b8a400f90201cd204f59c80454dc99 GIT binary patch literal 12828 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