From c28c83fbe81f11ba5b636da29f969b72d58df9b5 Mon Sep 17 00:00:00 2001 From: aosull01 Date: Tue, 5 Mar 2019 10:43:15 +0000 Subject: [PATCH] Use SDC Tosca Jar to extract and parse SDC CSAR Change-Id: I0e8261ed338eaafa35bb663f95a19fe97be57652 Issue-ID: EXTAPI-205 Signed-off-by: aosull01 --- .../ServiceSpecificationService.java | 94 +++--- .../apis/servicecatalog/ToscaInfosProcessor.java | 373 ++++----------------- .../servicecatalog/ToscaInfosProcessorTest.java | 360 +++++++------------- .../service-Sdwanvpninfraservice-csar.csar | Bin 0 -> 33640 bytes .../karatetest/features/00--ServiceCatalog.feature | 7 + ...84e5-f0e5-44c5-ab95-38fb4bf77064_toscafile.json | 10 + ...84e5-f0e5-44c5-ab95-38fb4bf77064_withTosca.json | 213 ++++++++++++ 7 files changed, 467 insertions(+), 590 deletions(-) create mode 100644 src/test/resources/__files/toscafile/service-Sdwanvpninfraservice-csar.csar create mode 100644 src/test/resources/mappings/sdc_get_462f84e5-f0e5-44c5-ab95-38fb4bf77064_toscafile.json create mode 100644 src/test/resources/mappings/sdc_get_462f84e5-f0e5-44c5-ab95-38fb4bf77064_withTosca.json diff --git a/src/main/java/org/onap/nbi/apis/servicecatalog/ServiceSpecificationService.java b/src/main/java/org/onap/nbi/apis/servicecatalog/ServiceSpecificationService.java index 228e12d..5e3e4cf 100644 --- a/src/main/java/org/onap/nbi/apis/servicecatalog/ServiceSpecificationService.java +++ b/src/main/java/org/onap/nbi/apis/servicecatalog/ServiceSpecificationService.java @@ -1,27 +1,30 @@ /** - * Copyright (c) 2018 Orange + * Copyright (c) 2018 Orange * - * 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 + * 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 + * 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. + * 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. */ package org.onap.nbi.apis.servicecatalog; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.apache.commons.io.FileUtils; import org.onap.nbi.apis.servicecatalog.jolt.FindServiceSpecJsonTransformer; import org.onap.nbi.apis.servicecatalog.jolt.GetServiceSpecJsonTransformer; import org.onap.nbi.apis.serviceorder.ServiceCatalogUrl; +import org.onap.sdc.tosca.parser.exceptions.SdcToscaParserException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -32,45 +35,56 @@ import org.springframework.util.MultiValueMap; @Service public class ServiceSpecificationService { - @Autowired - SdcClient sdcClient; + @Autowired + SdcClient sdcClient; - @Autowired - GetServiceSpecJsonTransformer getServiceSpecJsonTransformer; + @Autowired + GetServiceSpecJsonTransformer getServiceSpecJsonTransformer; - @Autowired - FindServiceSpecJsonTransformer findServiceSpecJsonTransformer; + @Autowired + FindServiceSpecJsonTransformer findServiceSpecJsonTransformer; - @Autowired - ToscaInfosProcessor toscaInfosProcessor; + @Autowired + ToscaInfosProcessor toscaInfosProcessor; - @Autowired - private ServiceCatalogUrl serviceCatalogUrl; + @Autowired + private ServiceCatalogUrl serviceCatalogUrl; - private static final Logger LOGGER = LoggerFactory.getLogger(ServiceSpecificationService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceSpecificationService.class); - public Map get(String serviceSpecId) { - Map sdcResponse = sdcClient.callGet(serviceSpecId); - LinkedHashMap serviceCatalogResponse = (LinkedHashMap) getServiceSpecJsonTransformer.transform(sdcResponse); - Map toscaInfosTopologyTemplate = toscaInfosProcessor.getToscaInfos(serviceCatalogResponse); - if (toscaInfosTopologyTemplate != null) { - LOGGER.debug("tosca file found, retrieving informations"); - toscaInfosProcessor.buildResponseWithToscaInfos(toscaInfosTopologyTemplate, serviceCatalogResponse); - } else { - LOGGER.debug("no tosca file found, partial response"); - } - return serviceCatalogResponse; + public Map get(String serviceSpecId) { + Map sdcResponse = sdcClient.callGet(serviceSpecId); + LinkedHashMap serviceCatalogResponse = + (LinkedHashMap) getServiceSpecJsonTransformer.transform(sdcResponse); + String toscaModelUrl = (String) sdcResponse.get("toscaModelURL"); + String serviceId = (String) sdcResponse.get("id"); + File toscaFile = sdcClient.callGetWithAttachment(toscaModelUrl); + Path pathToToscaCsar = toscaFile.toPath().toAbsolutePath(); + try { + toscaInfosProcessor.buildResponseWithSdcToscaParser(pathToToscaCsar, serviceCatalogResponse); + } catch (SdcToscaParserException e) { + LOGGER.debug("unable to build response from tosca csar using sdc-parser, partial response : " + + pathToToscaCsar.toString() + " " + e.getMessage()); } + try { + if (toscaFile != null) { + LOGGER.debug("deleting tosca archive : " + toscaFile.getName()); + FileUtils.forceDelete(toscaFile); + } + } catch (IOException e) { + LOGGER.error("unable to delete temp directory tosca file for id : " + serviceId, e); + } + return serviceCatalogResponse; + } - public List find(MultiValueMap parametersMap) { - List sdcResponse = sdcClient.callFind(parametersMap); - List serviceCatalogResponse = new ArrayList<>(); - if(!CollectionUtils.isEmpty(sdcResponse)){ - serviceCatalogResponse = - findServiceSpecJsonTransformer.transform(sdcResponse); - } - return serviceCatalogResponse; + public List find(MultiValueMap parametersMap) { + List sdcResponse = sdcClient.callFind(parametersMap); + List serviceCatalogResponse = new ArrayList<>(); + if (!CollectionUtils.isEmpty(sdcResponse)) { + serviceCatalogResponse = findServiceSpecJsonTransformer.transform(sdcResponse); } + return serviceCatalogResponse; + } } diff --git a/src/main/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessor.java b/src/main/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessor.java index 99a1657..fff4444 100644 --- a/src/main/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessor.java +++ b/src/main/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessor.java @@ -13,24 +13,11 @@ */ package org.onap.nbi.apis.servicecatalog; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; import java.nio.file.Path; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import org.apache.commons.io.FileUtils; -import org.onap.nbi.exceptions.TechnicalException; import org.onap.sdc.tosca.parser.api.ISdcCsarHelper; import org.onap.sdc.tosca.parser.exceptions.SdcToscaParserException; import org.onap.sdc.tosca.parser.impl.SdcToscaParserFactory; @@ -40,313 +27,83 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @Service public class ToscaInfosProcessor { - @Autowired - SdcClient sdcClient; - - final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); // jackson databind - - private static final Logger LOGGER = LoggerFactory.getLogger(ToscaInfosProcessor.class); - - public void buildResponseWithToscaInfos(Map toscaInfosTopologyTemplate, - Map serviceCatalogResponse) { - if (toscaInfosTopologyTemplate.get("inputs") != null) { - ArrayList serviceSpecCharacteristic = new ArrayList(); - LinkedHashMap toscaInfos = (LinkedHashMap) toscaInfosTopologyTemplate.get("inputs"); - Set> stringLinkedHashMapEntry = (Set>) toscaInfos - .entrySet(); - - for (Map.Entry key :stringLinkedHashMapEntry) { - String keyString = key.getKey(); - LinkedHashMap inputParameter = key.getValue(); - LinkedHashMap mapParameter = new LinkedHashMap(); - String parameterType = (String) inputParameter.get("type"); - mapParameter.put("name", keyString); - mapParameter.put("description", inputParameter.get("description")); - mapParameter.put("valueType", parameterType); - mapParameter.put("@type", "ONAPserviceCharacteristic"); - mapParameter.put("required", inputParameter.get("required")); - mapParameter.put("status", inputParameter.get("status")); - List serviceSpecCharacteristicValues = - buildServiceSpecCharacteristicsValues(inputParameter, parameterType); - mapParameter.put("serviceSpecCharacteristicValue", serviceSpecCharacteristicValues); - serviceSpecCharacteristic.add(mapParameter); - } - - serviceCatalogResponse.put("serviceSpecCharacteristic", serviceSpecCharacteristic); - } - LinkedHashMap nodeTemplate = (LinkedHashMap) toscaInfosTopologyTemplate.get("node_templates"); - - List resourceSpecifications = - (List) serviceCatalogResponse.get("resourceSpecification"); - for (LinkedHashMap resourceSpecification : resourceSpecifications) { - if(resourceSpecification.get("id")!=null){ - String id = (String) resourceSpecification.get("id"); - LOGGER.debug("get tosca infos for service id: {}", id); - LinkedHashMap toscaInfosFromResourceId = getToscaInfosFromResourceUUID(nodeTemplate, id); - if (toscaInfosFromResourceId != null && toscaInfosFromResourceId.get("customizationUUID")!=null) { - resourceSpecification.put("modelCustomizationId", toscaInfosFromResourceId.get("customizationUUID")); - } - } - } - } - - public void buildResponseWithSdcToscaParser(Path path, Map serviceCatalogResponse) throws SdcToscaParserException { - - SdcToscaParserFactory factory = SdcToscaParserFactory.getInstance(); - ISdcCsarHelper sdcCsarHelper = factory.getSdcCsarHelper(path.toFile().getAbsolutePath(),false); - List inputs = sdcCsarHelper.getServiceInputs(); - if(inputs != null && inputs.size() > 0) { - ArrayList serviceSpecCharacteristic = new ArrayList(); - for(Input input : inputs) { - LinkedHashMap mapParameter = new LinkedHashMap(); - mapParameter.put("name", input.getName()); - mapParameter.put("description", input.getDescription()); - mapParameter.put("valueType", input.getType()); - mapParameter.put("@type", "ONAPserviceCharacteristic"); - mapParameter.put("required", input.isRequired()); - mapParameter.put("status", null); - mapParameter.put("serviceSpecCharacteristicValue", null); - // If this Input has a default value, then put it in serviceSpecCharacteristicValue - if (input.getDefault() != null) - { - List serviceSpecCharacteristicValues = - buildServiceSpecCharacteristicsValuesFromSdc(input); - mapParameter.put("serviceSpecCharacteristicValue", serviceSpecCharacteristicValues); - } - serviceSpecCharacteristic.add(mapParameter); - } - serviceCatalogResponse.put("serviceSpecCharacteristic", serviceSpecCharacteristic); - } - List nodeTemplates = sdcCsarHelper.getServiceNodeTemplates(); - - List resourceSpecifications = - (List) serviceCatalogResponse.get("resourceSpecification"); - for (LinkedHashMap resourceSpecification : resourceSpecifications) { - if(resourceSpecification.get("id")!=null){ - String id = (String) resourceSpecification.get("id"); - LOGGER.debug("get tosca infos for service id: {}", id); - NodeTemplate nodeTemplate = null; - for(NodeTemplate node : nodeTemplates) { - if(node.getMetaData().getValue("UUID").equals(id)) { - nodeTemplate = node; - break; - } - } - if(nodeTemplate == null) - continue; - resourceSpecification.put("modelCustomizationId", sdcCsarHelper.getNodeTemplateCustomizationUuid(nodeTemplate)); - } + @Autowired + SdcClient sdcClient; + + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); // jackson databind + + private static final Logger LOGGER = LoggerFactory.getLogger(ToscaInfosProcessor.class); + + + + public void buildResponseWithSdcToscaParser(Path path, Map serviceCatalogResponse) + throws SdcToscaParserException { + + SdcToscaParserFactory factory = SdcToscaParserFactory.getInstance(); + ISdcCsarHelper sdcCsarHelper = factory.getSdcCsarHelper(path.toFile().getAbsolutePath(), false); + List inputs = sdcCsarHelper.getServiceInputs(); + if (inputs != null && inputs.size() > 0) { + ArrayList serviceSpecCharacteristic = new ArrayList(); + for (Input input : inputs) { + LinkedHashMap mapParameter = new LinkedHashMap(); + mapParameter.put("name", input.getName()); + mapParameter.put("description", input.getDescription()); + mapParameter.put("valueType", input.getType()); + mapParameter.put("@type", "ONAPserviceCharacteristic"); + mapParameter.put("required", input.isRequired()); + mapParameter.put("status", null); + mapParameter.put("serviceSpecCharacteristicValue", null); + // If this Input has a default value, then put it in serviceSpecCharacteristicValue + if (input.getDefault() != null) { + List serviceSpecCharacteristicValues = + buildServiceSpecCharacteristicsValuesFromSdc(input); + mapParameter.put("serviceSpecCharacteristicValue", serviceSpecCharacteristicValues); } + serviceSpecCharacteristic.add(mapParameter); + } + serviceCatalogResponse.put("serviceSpecCharacteristic", serviceSpecCharacteristic); } - - - private List buildServiceSpecCharacteristicsValuesFromSdc(Input input) { - - List serviceSpecCharacteristicValues = new ArrayList<>(); - LinkedHashMap serviceSpecCharacteristicValue = new LinkedHashMap(); - - serviceSpecCharacteristicValue.put("isDefault", true); - serviceSpecCharacteristicValue.put("value", input.getDefault()); - serviceSpecCharacteristicValue.put("valueType", input.getType()); - serviceSpecCharacteristicValues.add(serviceSpecCharacteristicValue); - - return serviceSpecCharacteristicValues; - } - - private List buildServiceSpecCharacteristicsValues(LinkedHashMap parameter, String parameterType) { - List serviceSpecCharacteristicValues = new ArrayList<>(); - if (!"map".equalsIgnoreCase(parameterType) && !"list".equalsIgnoreCase(parameterType)) { - LOGGER.debug("get tosca infos for serviceSpecCharacteristicValues of type map or string : {}", parameter); - Object aDefault = parameter.get("default"); - if (parameter.get("entry_schema") != null) { - ArrayList entrySchema = (ArrayList) parameter.get("entry_schema"); - if (!CollectionUtils.isEmpty(entrySchema)) { - buildCharacteristicValuesFormShema(parameterType, serviceSpecCharacteristicValues, aDefault, - entrySchema); - } - } - } - return serviceSpecCharacteristicValues; - } - - private void buildCharacteristicValuesFormShema(String parameterType, - List serviceSpecCharacteristicValues, Object aDefault, ArrayList entrySchema) { - LinkedHashMap constraints = (LinkedHashMap) entrySchema.get(0); - if (constraints != null) { - ArrayList constraintsList = (ArrayList) constraints.get("constraints"); - if (!CollectionUtils.isEmpty(constraintsList)) { - LinkedHashMap valuesMap = (LinkedHashMap) constraintsList.get(0); - if (valuesMap != null) { - List values = (List) valuesMap.get("valid_values"); - for (Object value : values) { - String stringValue = value.toString(); - LinkedHashMap serviceSpecCharacteristicValue = new LinkedHashMap(); - serviceSpecCharacteristicValue.put("isDefault", - aDefault != null && aDefault.toString().equals(stringValue)); - serviceSpecCharacteristicValue.put("value", stringValue); - serviceSpecCharacteristicValue.put("valueType", parameterType); - serviceSpecCharacteristicValues.add(serviceSpecCharacteristicValue); - } - } - } + List nodeTemplates = sdcCsarHelper.getServiceNodeTemplates(); + + List resourceSpecifications = + (List) serviceCatalogResponse.get("resourceSpecification"); + for (LinkedHashMap resourceSpecification : resourceSpecifications) { + if (resourceSpecification.get("id") != null) { + String id = (String) resourceSpecification.get("id"); + LOGGER.debug("get tosca infos for service id: {}", id); + NodeTemplate nodeTemplate = null; + for (NodeTemplate node : nodeTemplates) { + if (node.getMetaData().getValue("UUID").equals(id)) { + nodeTemplate = node; + break; + } } + if (nodeTemplate == null) + continue; + resourceSpecification.put("modelCustomizationId", + sdcCsarHelper.getNodeTemplateCustomizationUuid(nodeTemplate)); + } } + } - private LinkedHashMap getToscaInfosFromResourceUUID(LinkedHashMap nodeTemplates, String name) { - if(nodeTemplates!=null) { - for (Object nodeTemplateObject : nodeTemplates.values()) { - LinkedHashMap nodeTemplate = (LinkedHashMap) nodeTemplateObject; - LinkedHashMap metadata = (LinkedHashMap) nodeTemplate.get("metadata"); - if(metadata.get("UUID")!=null && metadata.get("type")!=null) { - String metadataUUID = (String) metadata.get("UUID"); - String metadataType = (String) metadata.get("type"); - if ("VF".equalsIgnoreCase(metadataType) && name!=null && name.equalsIgnoreCase(metadataUUID)) { - return metadata; - } - } - } - } - return null; - } - + private List buildServiceSpecCharacteristicsValuesFromSdc(Input input) { - public Map getToscaInfos(Map sdcResponse) { + List serviceSpecCharacteristicValues = new ArrayList<>(); + LinkedHashMap serviceSpecCharacteristicValue = new LinkedHashMap(); - LinkedHashMap topologyTemplate = null; - - String toscaModelUrl = (String) sdcResponse.get("toscaModelURL"); - String serviceId = (String) sdcResponse.get("id"); - File toscaFile = sdcClient.callGetWithAttachment(toscaModelUrl); - Timestamp timestamp = new Timestamp(System.currentTimeMillis()); - String tempFolderName = serviceId + timestamp; - File folderTemp = null; - - try { - unZipArchive(toscaFile.getName(), tempFolderName); - folderTemp = new File(tempFolderName); - LOGGER.debug("temp folder for tosca files : " + folderTemp.getName()); - - LinkedHashMap toscaMetaFileHashMap = parseToscaFile(tempFolderName + "/TOSCA-Metadata/TOSCA.meta"); - topologyTemplate = getToscaTopologyTemplateNode(tempFolderName, toscaMetaFileHashMap); - return topologyTemplate; - } catch (TechnicalException e) { - LOGGER.error("unable to parse tosca file for id : " + serviceId, e); - return topologyTemplate; - } - finally { - deleteTempFiles(serviceId, toscaFile, folderTemp); - } - - } - - private LinkedHashMap getToscaTopologyTemplateNode(String tempFolderName,LinkedHashMap toscaMetaFileHashMap) { - LinkedHashMap topologyTemplate = null; - if (toscaMetaFileHashMap.get("Entry-Definitions") != null) { - String toscaFilePath = (String) toscaMetaFileHashMap.get("Entry-Definitions"); - LinkedHashMap toscaFileHashMap = parseToscaFile(tempFolderName + "/" + toscaFilePath); - if (toscaFileHashMap.get("topology_template") != null) { - topologyTemplate = (LinkedHashMap) toscaFileHashMap.get("topology_template"); - } else { - LOGGER.error("no Entry-Definitions node in TOSCA.meta"); - } - } else { - LOGGER.error("no topology_template node in tosca file"); - } - return topologyTemplate; - } - - - private void deleteTempFiles(String serviceId, File toscaFile, File folderTemp) { - try { - if(folderTemp!=null){ - LOGGER.debug("deleting temp folder for tosca files : " + folderTemp.getName()); - FileUtils.deleteDirectory(folderTemp); - } - LOGGER.debug("deleting tosca archive : " + toscaFile.getName()); - FileUtils.forceDelete(toscaFile); - } catch (IOException e) { - LOGGER.error("unable to delete temp directory tosca file for id : " + serviceId, e); - } - } - - private LinkedHashMap parseToscaFile(String fileName) { - - File toscaFile = new File(fileName); - if (!toscaFile.exists()) { - throw new TechnicalException("unable to find file : " + fileName); - } - try { - return (LinkedHashMap) mapper.readValue(toscaFile, Object.class); - } catch (IOException e) { - LOGGER.warn("unable to parse tosca file : " + fileName, e); - throw new TechnicalException("Unable to parse tosca file : " + fileName); - - } catch (NullPointerException e) { - LOGGER.warn("unable to find tosca file : " + fileName, e); - throw new TechnicalException("unable to find tosca file : " + fileName); - } - } - - - /** - * Unzip it - * - * @param zipFile input zip file - * @param outputFolder zip file output folder - */ - private void unZipArchive(String zipFile, String outputFolder) { - - byte[] buffer = new byte[1024]; - - try { - - // create output directory is not exists - File folder = new File(outputFolder); - if (!folder.exists()) { - folder.mkdir(); - } - - // get the zip file content - try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { - // get the zipped file list entry - ZipEntry ze = zis.getNextEntry(); - - while (ze != null) { - - String fileName = ze.getName(); - File newFile = new File(outputFolder + File.separator + fileName); - - LOGGER.debug("File to unzip : " + newFile.getAbsoluteFile()); - - // create all non exists folders - // else you will hit FileNotFoundException for compressed folder - new File(newFile.getParent()).mkdirs(); - - try (FileOutputStream fos = new FileOutputStream(newFile)) { - - int len; - while ((len = zis.read(buffer)) > 0) { - fos.write(buffer, 0, len); - } - } - ze = zis.getNextEntry(); - } - zis.closeEntry(); - } - - LOGGER.debug("Done"); - - } catch (IOException ex) { - LOGGER.error("Error while unzipping ToscaModel archive from ONAP", ex); - throw new TechnicalException("Error while unzipping ToscaModel archive from ONAP"); - } - } + serviceSpecCharacteristicValue.put("isDefault", true); + serviceSpecCharacteristicValue.put("value", input.getDefault()); + serviceSpecCharacteristicValue.put("valueType", input.getType()); + serviceSpecCharacteristicValues.add(serviceSpecCharacteristicValue); + return serviceSpecCharacteristicValues; + } } diff --git a/src/test/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessorTest.java b/src/test/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessorTest.java index 3a770ae..8849607 100644 --- a/src/test/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessorTest.java +++ b/src/test/java/org/onap/nbi/apis/servicecatalog/ToscaInfosProcessorTest.java @@ -1,273 +1,149 @@ /** * Copyright (c) 2018 Orange * - * 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 + * 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. + * 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. */ package org.onap.nbi.apis.servicecatalog; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertNull; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; - -import org.assertj.core.api.AbstractBooleanAssert; import org.junit.Test; import org.onap.nbi.exceptions.TechnicalException; import org.onap.sdc.tosca.parser.exceptions.SdcToscaParserException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; public class ToscaInfosProcessorTest { - final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); // jackson databind - - ToscaInfosProcessor toscaInfosProcessor = new ToscaInfosProcessor(); - - - private LinkedHashMap parseToscaFile(String fileName) { - - File toscaFile = new File(fileName); - if (!toscaFile.exists()) { - throw new TechnicalException("unable to find file : " + fileName); - } - try { - return (LinkedHashMap) mapper.readValue(toscaFile, Object.class); - } catch (IOException e) { - throw new TechnicalException("Unable to parse tosca file : " + fileName); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); // jackson databind - } catch (NullPointerException e) { - throw new TechnicalException("unable to find tosca file : " + fileName); - } - } - - - @Test - public void buildResponseWithToscaInfos() { - - ClassLoader classLoader = getClass().getClassLoader(); - File file = new File(classLoader.getResource("toscafile/service-TestNetwork-template.yml").getFile()); - List resources = new ArrayList<>(); - LinkedHashMap resource1 = new LinkedHashMap(); - resource1.put("id", "e2b12ac6-cbb6-4517-9c58-b846d1f68caf"); - resources.add(resource1); - LinkedHashMap toscaFile = parseToscaFile(file.getPath()); - LinkedHashMap response = new LinkedHashMap(); - response.put("resourceSpecification", resources); - toscaInfosProcessor.buildResponseWithToscaInfos((LinkedHashMap) toscaFile.get("topology_template"), response); - - resources = (List) response.get("resourceSpecification"); - assertNull(resources.get(0).get("modelCustomizationId")); - assertNull(resources.get(0).get("modelCustomizationName")); + ToscaInfosProcessor toscaInfosProcessor = new ToscaInfosProcessor(); - } - - @Test - public void buildResponseWithSdcToscaParser() { - - ClassLoader classLoader = getClass().getClassLoader(); - Path path = new File(classLoader.getResource("toscafile/service-Sdwanvpninfraservice-csar.csar").getFile()).toPath().toAbsolutePath(); - List resources = new ArrayList<>(); - LinkedHashMap resource1 = new LinkedHashMap(); - resource1.put("id", "7baa7742-3a13-4288-8330-868015adc340"); - resources.add(resource1); - LinkedHashMap resource2 = new LinkedHashMap(); - resource2.put("id", "81b9430b-8abe-45d6-8bf9-f41a8f5c735f"); - resources.add(resource2); - LinkedHashMap response = new LinkedHashMap(); - response.put("resourceSpecification", resources); - try { - toscaInfosProcessor.buildResponseWithSdcToscaParser(path, response); - } - catch(SdcToscaParserException e) { - throw new TechnicalException("unable to build response from tosca csar using sdc-parser : " + path.toString()+" "+e.getMessage()); - } - resources = (List) response.get("resourceSpecification"); - List serviceSpecCharacteristic = new ArrayList<>(); - serviceSpecCharacteristic = (List) response.get("serviceSpecCharacteristic"); - assertThat(serviceSpecCharacteristic.get(0).get("name")).isEqualTo("sdwanconnectivity0_topology"); - assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); - assertThat(serviceSpecCharacteristic.get(0).get("required")).isEqualTo(true); - assertThat(serviceSpecCharacteristic.get(1).get("name")).isEqualTo("sdwanconnectivity0_name"); - assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); - assertThat(serviceSpecCharacteristic.get(1).get("required")).isEqualTo(true); - assertThat(resources.get(0).get("modelCustomizationId")).isEqualTo("94ec574b-2306-4cbd-8214-09662b040f73"); - assertThat(resources.get(1).get("modelCustomizationId")).isEqualTo("a7baba5d-6ac3-42b5-b47d-070841303ab1"); - - } - @Test - public void buildResponseWithSdcToscaParserWithDefaultInputs() { + @Test + public void buildResponseWithSdcToscaParser() { - ClassLoader classLoader = getClass().getClassLoader(); - Path path = new File(classLoader.getResource("toscafile/service-Sotnvpninfraservice-csar.csar").getFile()).toPath().toAbsolutePath(); - List resources = new ArrayList<>(); - LinkedHashMap resource1 = new LinkedHashMap(); - resource1.put("id", "218df3c3-50dd-4c26-9e36-4771387bb771"); - resources.add(resource1); - LinkedHashMap resource2 = new LinkedHashMap(); - resource2.put("id", "81b9430b-8abe-45d6-8bf9-f41a8f5c735f"); - resources.add(resource2); - LinkedHashMap response = new LinkedHashMap(); - response.put("resourceSpecification", resources); + ClassLoader classLoader = getClass().getClassLoader(); + Path path = new File( + classLoader.getResource("toscafile/service-Sdwanvpninfraservice-csar.csar").getFile()) + .toPath().toAbsolutePath(); + List resources = new ArrayList<>(); + LinkedHashMap resource1 = new LinkedHashMap(); + resource1.put("id", "7baa7742-3a13-4288-8330-868015adc340"); + resources.add(resource1); + LinkedHashMap resource2 = new LinkedHashMap(); + resource2.put("id", "81b9430b-8abe-45d6-8bf9-f41a8f5c735f"); + resources.add(resource2); + LinkedHashMap response = new LinkedHashMap(); + response.put("resourceSpecification", resources); - try { - toscaInfosProcessor.buildResponseWithSdcToscaParser(path, response); - } - catch(SdcToscaParserException e) { - throw new TechnicalException("unable to build response from tosca csar using sdc-parser : " + path.toString()+" "+e.getMessage()); - } - resources = (List) response.get("resourceSpecification"); - List serviceSpecCharacteristic = new ArrayList<>(); - serviceSpecCharacteristic = (List) response.get("serviceSpecCharacteristic"); - assertThat(resources.get(0).get("modelCustomizationId")).isEqualTo("b44071c8-04fd-4d6b-b6af-772cbfaa1129"); - assertThat(resources.get(1).get("modelCustomizationId")).isEqualTo("c3612284-6c67-4d8c-8b41-b699cc90e76d"); - assertThat(serviceSpecCharacteristic.get(12).get("serviceSpecCharacteristicValue")).isNull(); - assertThat(serviceSpecCharacteristic.get(13).get("serviceSpecCharacteristicValue")).isNotNull(); + try { + toscaInfosProcessor.buildResponseWithSdcToscaParser(path, response); + } catch (SdcToscaParserException e) { + throw new TechnicalException("unable to build response from tosca csar using sdc-parser : " + + path.toString() + " " + e.getMessage()); } - - @Test - public void buildResponseWithSdcToscaParserwithMetaDataMisMatch() { - - ClassLoader classLoader = getClass().getClassLoader(); - Path path = new File(classLoader.getResource("toscafile/service-Sdwanvpninfraservice-csar.csar").getFile()).toPath().toAbsolutePath(); - List resources = new ArrayList<>(); - LinkedHashMap resource1 = new LinkedHashMap(); - resource1.put("id", "some bad resource id no in TOSCA CSAR"); - resources.add(resource1); - LinkedHashMap resource2 = new LinkedHashMap(); - resource2.put("id", "some bad resource id no in TOSCA CSAR"); - resources.add(resource2); - LinkedHashMap response = new LinkedHashMap(); - response.put("resourceSpecification", resources); - - try { - toscaInfosProcessor.buildResponseWithSdcToscaParser(path, response); - } - catch(SdcToscaParserException e) { - throw new TechnicalException("unable to build response from tosca csar using sdc-parser : " + path.toString()+" "+e.getMessage()); - } - resources = (List) response.get("resourceSpecification"); - List serviceSpecCharacteristic = new ArrayList<>(); - serviceSpecCharacteristic = (List) response.get("serviceSpecCharacteristic"); - assertThat(serviceSpecCharacteristic.get(0).get("name")).isEqualTo("sdwanconnectivity0_topology"); - assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); - assertThat(serviceSpecCharacteristic.get(0).get("required")).isEqualTo(true); - assertThat(serviceSpecCharacteristic.get(1).get("name")).isEqualTo("sdwanconnectivity0_name"); - assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); - assertThat(serviceSpecCharacteristic.get(1).get("required")).isEqualTo(true); - // Check that resources cannot be found in the TOSCA template - assertThat(resources.get(0).get("modelCustomizationId")).isNull(); - assertThat(resources.get(1).get("modelCustomizationId")).isNull(); - + resources = (List) response.get("resourceSpecification"); + List serviceSpecCharacteristic = new ArrayList<>(); + serviceSpecCharacteristic = (List) response.get("serviceSpecCharacteristic"); + assertThat(serviceSpecCharacteristic.get(0).get("name")) + .isEqualTo("sdwanconnectivity0_topology"); + assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); + assertThat(serviceSpecCharacteristic.get(0).get("required")).isEqualTo(true); + assertThat(serviceSpecCharacteristic.get(1).get("name")).isEqualTo("sdwanconnectivity0_name"); + assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); + assertThat(serviceSpecCharacteristic.get(1).get("required")).isEqualTo(true); + assertThat(resources.get(0).get("modelCustomizationId")) + .isEqualTo("94ec574b-2306-4cbd-8214-09662b040f73"); + assertThat(resources.get(1).get("modelCustomizationId")) + .isEqualTo("a7baba5d-6ac3-42b5-b47d-070841303ab1"); + + } + + @Test + public void buildResponseWithSdcToscaParserWithDefaultInputs() { + + ClassLoader classLoader = getClass().getClassLoader(); + Path path = new File( + classLoader.getResource("toscafile/service-Sotnvpninfraservice-csar.csar").getFile()) + .toPath().toAbsolutePath(); + List resources = new ArrayList<>(); + LinkedHashMap resource1 = new LinkedHashMap(); + resource1.put("id", "218df3c3-50dd-4c26-9e36-4771387bb771"); + resources.add(resource1); + LinkedHashMap resource2 = new LinkedHashMap(); + resource2.put("id", "81b9430b-8abe-45d6-8bf9-f41a8f5c735f"); + resources.add(resource2); + LinkedHashMap response = new LinkedHashMap(); + response.put("resourceSpecification", resources); + + try { + toscaInfosProcessor.buildResponseWithSdcToscaParser(path, response); + } catch (SdcToscaParserException e) { + throw new TechnicalException("unable to build response from tosca csar using sdc-parser : " + + path.toString() + " " + e.getMessage()); } - @Test - public void buildResponseWithToscaInfosOk() { - - ClassLoader classLoader = getClass().getClassLoader(); - File file = new File(classLoader.getResource("toscafile/service-VfwService2vfBased-template.yml").getFile()); - List resources = new ArrayList<>(); - LinkedHashMap resource1 = new LinkedHashMap(); - resource1.put("id", "e2b12ac6-cbb6-4517-9c58-b846d1f68caf"); - resources.add(resource1); - LinkedHashMap toscaFile = parseToscaFile(file.getPath()); - LinkedHashMap response = new LinkedHashMap(); - response.put("resourceSpecification", resources); - toscaInfosProcessor.buildResponseWithToscaInfos((LinkedHashMap) toscaFile.get("topology_template"), response); - - ArrayList toscaInfos = (ArrayList) response.get("serviceSpecCharacteristic"); - assertThat(toscaInfos.size()).isEqualTo(4); - - for (Object toscaInfo : toscaInfos) { - LinkedHashMap info = (LinkedHashMap) toscaInfo; - if (((String) info.get("name")).equalsIgnoreCase("fortigate_image_url")) { - assertThat(info.get("name")).isEqualTo("fortigate_image_url"); - assertThat(info.get("description")).isNull(); - assertThat(info.get("valueType")).isEqualTo("string"); - assertThat(info.get("@type")).isEqualTo("ONAPserviceCharacteristic"); - assertThat(info.get("required")).isEqualTo(false); - assertThat(info.get("status")).isNull(); - assertThat(((ArrayList) info.get("serviceSpecCharacteristicValue")).size()).isEqualTo(0); - - } - - if (((String) info.get("name")).equalsIgnoreCase("flavor")) { - assertThat(info.get("name")).isEqualTo("flavor"); - assertThat(info.get("description")).isNull(); - assertThat(info.get("valueType")).isEqualTo("string"); - assertThat(info.get("@type")).isEqualTo("ONAPserviceCharacteristic"); - assertThat(info.get("required")).isNull(); - assertThat(info.get("status")).isNull(); - assertThat(((ArrayList) info.get("serviceSpecCharacteristicValue")).size()).isEqualTo(0); - - } - - if (((String) info.get("name")).equalsIgnoreCase("external_network_name")) { - assertThat(info.get("name")).isEqualTo("external_network_name"); - assertThat(info.get("description")).isNull(); - assertThat(info.get("valueType")).isEqualTo("string"); - assertThat(info.get("@type")).isEqualTo("ONAPserviceCharacteristic"); - assertThat(info.get("required")).isNull(); - assertThat(info.get("status")).isEqualTo("inactive"); - ; - assertThat(((ArrayList) info.get("serviceSpecCharacteristicValue")).size()).isEqualTo(0); - - } - - if (((String) info.get("name")).equalsIgnoreCase("cpus")) { - assertThat(info.get("name")).isEqualTo("cpus"); - assertThat(info.get("description")).isEqualTo("Number of CPUs for the server."); - assertThat(info.get("valueType")).isEqualTo("integer"); - assertThat(info.get("@type")).isEqualTo("ONAPserviceCharacteristic"); - assertThat(info.get("required")).isNull(); - assertThat(info.get("status")).isNull(); - ; - assertThat(((ArrayList) info.get("serviceSpecCharacteristicValue")).size()).isEqualTo(4); - ArrayList serviceSpecCharacteristicValues = (ArrayList) info.get("serviceSpecCharacteristicValue"); - - for (Object serviceSpecCharacteristicValue : serviceSpecCharacteristicValues) { - LinkedHashMap serviceSpecValue = (LinkedHashMap) serviceSpecCharacteristicValue; - if (((String) serviceSpecValue.get("value")).equalsIgnoreCase("1")) { - assertThat(serviceSpecValue.get("isDefault")).isEqualTo(false); - assertThat(serviceSpecValue.get("value")).isEqualTo("1"); - assertThat(serviceSpecValue.get("valueType")).isEqualTo("integer"); - } - if (((String) serviceSpecValue.get("value")).equalsIgnoreCase("2")) { - assertThat(serviceSpecValue.get("isDefault")).isEqualTo(true); - assertThat(serviceSpecValue.get("value")).isEqualTo("2"); - assertThat(serviceSpecValue.get("valueType")).isEqualTo("integer"); - } - if (((String) serviceSpecValue.get("value")).equalsIgnoreCase("3")) { - assertThat(serviceSpecValue.get("isDefault")).isEqualTo(false); - assertThat(serviceSpecValue.get("value")).isEqualTo("3"); - assertThat(serviceSpecValue.get("valueType")).isEqualTo("integer"); - } - if (((String) serviceSpecValue.get("value")).equalsIgnoreCase("4")) { - assertThat(serviceSpecValue.get("isDefault")).isEqualTo(false); - assertThat(serviceSpecValue.get("value")).isEqualTo("4"); - assertThat(serviceSpecValue.get("valueType")).isEqualTo("integer"); - } - - } - - - } - - } - - + resources = (List) response.get("resourceSpecification"); + List serviceSpecCharacteristic = new ArrayList<>(); + serviceSpecCharacteristic = (List) response.get("serviceSpecCharacteristic"); + assertThat(resources.get(0).get("modelCustomizationId")) + .isEqualTo("b44071c8-04fd-4d6b-b6af-772cbfaa1129"); + assertThat(resources.get(1).get("modelCustomizationId")) + .isEqualTo("c3612284-6c67-4d8c-8b41-b699cc90e76d"); + assertThat(serviceSpecCharacteristic.get(12).get("serviceSpecCharacteristicValue")).isNull(); + assertThat(serviceSpecCharacteristic.get(13).get("serviceSpecCharacteristicValue")).isNotNull(); + } + + @Test + public void buildResponseWithSdcToscaParserwithMetaDataMisMatch() { + + ClassLoader classLoader = getClass().getClassLoader(); + Path path = new File( + classLoader.getResource("toscafile/service-Sdwanvpninfraservice-csar.csar").getFile()) + .toPath().toAbsolutePath(); + List resources = new ArrayList<>(); + LinkedHashMap resource1 = new LinkedHashMap(); + resource1.put("id", "some bad resource id no in TOSCA CSAR"); + resources.add(resource1); + LinkedHashMap resource2 = new LinkedHashMap(); + resource2.put("id", "some bad resource id no in TOSCA CSAR"); + resources.add(resource2); + LinkedHashMap response = new LinkedHashMap(); + response.put("resourceSpecification", resources); + + try { + toscaInfosProcessor.buildResponseWithSdcToscaParser(path, response); + } catch (SdcToscaParserException e) { + throw new TechnicalException("unable to build response from tosca csar using sdc-parser : " + + path.toString() + " " + e.getMessage()); } -} \ No newline at end of file + resources = (List) response.get("resourceSpecification"); + List serviceSpecCharacteristic = new ArrayList<>(); + serviceSpecCharacteristic = (List) response.get("serviceSpecCharacteristic"); + assertThat(serviceSpecCharacteristic.get(0).get("name")) + .isEqualTo("sdwanconnectivity0_topology"); + assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); + assertThat(serviceSpecCharacteristic.get(0).get("required")).isEqualTo(true); + assertThat(serviceSpecCharacteristic.get(1).get("name")).isEqualTo("sdwanconnectivity0_name"); + assertThat(serviceSpecCharacteristic.get(1).get("valueType")).isEqualTo("string"); + assertThat(serviceSpecCharacteristic.get(1).get("required")).isEqualTo(true); + // Check that resources cannot be found in the TOSCA template + assertThat(resources.get(0).get("modelCustomizationId")).isNull(); + assertThat(resources.get(1).get("modelCustomizationId")).isNull(); + + } +} diff --git a/src/test/resources/__files/toscafile/service-Sdwanvpninfraservice-csar.csar b/src/test/resources/__files/toscafile/service-Sdwanvpninfraservice-csar.csar new file mode 100644 index 0000000000000000000000000000000000000000..52bdfe2e8491776e2222e6654e2774012085d125 GIT binary patch literal 33640 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