From 8caef088a9e5467cd0cb462acb217edceedac8fe Mon Sep 17 00:00:00 2001 From: Pavel Aharoni Date: Thu, 11 May 2017 19:32:07 +0300 Subject: [PATCH] [SDC-19] VFC to CP props Change-Id: I217934251fd8eeaf883b60161826306d6b7eaf3c Signed-off-by: Pavel Aharoni --- .gitignore | 2 +- .../openecomp/sdc/toscaparser/api/DataEntity.java | 15 +- .../sdc/toscaparser/api/EntityTemplate.java | 2 +- .../sdc/toscaparser/api/ToscaTemplate.java | 57 +- .../toscaparser/api/common/ExceptionCollector.java | 64 +- .../sdc/toscaparser/api/elements/ScalarUnit.java | 6 +- .../api/elements/constraints/Schema.java | 3 +- .../java/org/openecomp/test/CsarToscaTester.java | 66 +- .../sdc/tosca/parser/api/ISdcCsarHelper.java | 57 +- .../sdc/tosca/parser/impl/SdcCsarHelperImpl.java | 951 ++++++++++++--------- .../java/org/openecomp/sdc/impl/BasicTest.java | 66 +- .../openecomp/sdc/impl/ToscaParserGroupTest.java | 10 + .../sdc/impl/ToscaParserNodeTemplateTest.java | 27 + .../sdc/impl/ToscaParserServiceInputTest.java | 7 + .../resources/csars/1service-ServiceWithPorts.csar | Bin 0 -> 47257 bytes 15 files changed, 834 insertions(+), 499 deletions(-) create mode 100644 sdc-tosca-parser/src/test/resources/csars/1service-ServiceWithPorts.csar diff --git a/.gitignore b/.gitignore index 74f7cda..233ca40 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,5 @@ target/ *.class *.orig .idea/* - +/bin/ sdc-tosca-parser/test-output/**/* diff --git a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/DataEntity.java b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/DataEntity.java index a5d0467..3598d02 100644 --- a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/DataEntity.java +++ b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/DataEntity.java @@ -1,7 +1,9 @@ package org.openecomp.sdc.toscaparser.api; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import org.openecomp.sdc.toscaparser.api.common.ExceptionCollector; import org.openecomp.sdc.toscaparser.api.elements.*; @@ -49,8 +51,17 @@ public class DataEntity { ExceptionCollector.appendException(String.format( "TypeMismatchError: \"%s\" doesn't match \"%s\"", value.toString(),dataType.getType())); - } - LinkedHashMap valueDict = (LinkedHashMap)value; + + if (value instanceof List) + value = ((List) value).get(0); + + if (!(value instanceof LinkedHashMap)) + return value; + } + + + + LinkedHashMap valueDict = (LinkedHashMap)value; ArrayList allowedProps = new ArrayList<>(); ArrayList requiredProps = new ArrayList<>(); LinkedHashMap defaultProps = new LinkedHashMap<>(); diff --git a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/EntityTemplate.java b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/EntityTemplate.java index 3d9c470..cb765ec 100644 --- a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/EntityTemplate.java +++ b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/EntityTemplate.java @@ -351,7 +351,7 @@ public abstract class EntityTemplate { else { // Required properties in schema, but not in template if(!requiredProps.isEmpty()) { - ExceptionCollector.appendException(String.format( + ExceptionCollector.appendWarning(String.format( "MissingRequiredFieldError2: properties of template \"%s\" are missing field(s): %s", name,requiredProps.toString())); } diff --git a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/ToscaTemplate.java b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/ToscaTemplate.java index d1b0179..08b66bd 100644 --- a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/ToscaTemplate.java +++ b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/ToscaTemplate.java @@ -1,5 +1,15 @@ package org.openecomp.sdc.toscaparser.api; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + import org.openecomp.sdc.toscaparser.api.common.ExceptionCollector; import org.openecomp.sdc.toscaparser.api.common.JToscaException; import org.openecomp.sdc.toscaparser.api.elements.EntityType; @@ -12,11 +22,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; -import java.io.*; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; - public class ToscaTemplate extends Object { private static Logger log = LoggerFactory.getLogger(ToscaTemplate.class.getName()); @@ -501,24 +506,42 @@ public class ToscaTemplate extends Object { } private void verifyTemplate() throws JToscaException { - ArrayList exceptionStrings = ExceptionCollector.getExceptionReport(); - if (exceptionStrings != null && exceptionStrings.size() > 0) { - int nexc = ExceptionCollector.errorsCaught(); - log.error("ToscaTemplate - verifyTemplate - {} Parsing Exception{} occurred...", nexc, (nexc > 1 ? "s" : "")); - for (String s : exceptionStrings) { + //Warnings + List warningsStrings = ExceptionCollector.getWarningsReport(); + if (warningsStrings != null && warningsStrings.size() > 0) { + int nexcw = ExceptionCollector.warningsCaught(); + log.warn("####################################################################################################"); + log.warn("ToscaTemplate - verifyTemplate - {} Parsing Warning{} occurred...", nexcw, (nexcw > 1 ? "s" : "")); + for (String s : warningsStrings) { if (s != null) { log.debug("ToscaTemplate - verifyTemplate - {}", s); } } - if(bAbortOnParsingErrors) { - throw new JToscaException("Aborting because of parsing errors"); + log.warn("####################################################################################################"); + + + List exceptionStrings = ExceptionCollector.getCriticalsReport(); + if (exceptionStrings != null && exceptionStrings.size() > 0) { + int nexc = ExceptionCollector.errorsCaught(); + log.error("####################################################################################################"); + log.error("ToscaTemplate - verifyTemplate - {} Parsing Critical{} occurred...", nexc, (nexc > 1 ? "s" : "")); + for (String s : exceptionStrings) { + if (s != null) { + log.debug("ToscaTemplate - verifyTemplate - {}", s); + } + } + log.error("####################################################################################################"); + if(bAbortOnParsingErrors) { + throw new JToscaException("Aborting because of parsing errors"); + } + } + else { + if (inputPath != null) { + log.debug("ToscaTemplate - verifyTemplate - The input {} passed validation", inputPath); + } } + } - else { - if (inputPath != null) { - log.debug("ToscaTemplate - verifyTemplate - The input {} passed validation", inputPath); - } - } } public String getPath() { diff --git a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/common/ExceptionCollector.java b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/common/ExceptionCollector.java index 07dabf8..b810e87 100644 --- a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/common/ExceptionCollector.java +++ b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/common/ExceptionCollector.java @@ -1,6 +1,7 @@ package org.openecomp.sdc.toscaparser.api.common; import java.util.ArrayList; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,13 +14,15 @@ public class ExceptionCollector { //private static boolean isCollecting = false; private static ArrayList exceptionStrings = new ArrayList<>(); - private static ArrayList traceStrings = new ArrayList<>(); + private static ArrayList exceptionTraceStrings = new ArrayList<>(); + private static ArrayList warningStrings = new ArrayList<>(); + private static ArrayList warningTraceStrings = new ArrayList<>(); private static boolean bWantTrace = true; /*public static void start() { if(exceptionStrings == null) { exceptionStrings = new ArrayList(); - traceStrings = new ArrayList(); + exceptionTraceStrings = new ArrayList(); } isCollecting = true; }*/ @@ -30,7 +33,9 @@ public class ExceptionCollector { public static void clear() { exceptionStrings = new ArrayList<>(); - traceStrings = new ArrayList<>(); + exceptionTraceStrings = new ArrayList<>(); + warningStrings = new ArrayList<>(); + warningTraceStrings = new ArrayList<>(); } public static void appendException(String strExc) { // throws Exception { @@ -50,28 +55,67 @@ public class ExceptionCollector { sb.append(String.format(" %s(%s:%d)%s",ste[i].getClassName(),ste[i].getFileName(), ste[i].getLineNumber(),i==ste.length-1?" ":"\n")); } - traceStrings.add(sb.toString()); + exceptionTraceStrings.add(sb.toString()); } } + + public static void appendWarning(String strExc) { // throws Exception { - public static ArrayList getExceptionReport() { + /*if(!isCollecting) { + // throw new Exception("Can't append exception " + strExc); + log.error("ExceptionCollector - appendException - Can't append exception {}", strExc); + }*/ + + if(!warningStrings.contains(strExc)) { + warningStrings.add(strExc); + // get stack trace + StackTraceElement[] ste = Thread.currentThread().getStackTrace(); + StringBuilder sb = new StringBuilder(); + // skip the last 2 (getStackTrace and this) + for(int i=2; i getCriticalsReport() { + + List res = new ArrayList<>(); if(exceptionStrings.size() > 0) { - ArrayList report = new ArrayList<>(); for(int i=0; i getWarningsReport() { + + List res = new ArrayList<>(); + if(warningStrings.size() > 0) { + for(int i=0; i(); + return res; } public static int errorsCaught() { return exceptionStrings.size(); } + public static int warningsCaught() { + return warningStrings.size(); + } + public static void setWantTrace(boolean b) { bWantTrace = b; } diff --git a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/ScalarUnit.java b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/ScalarUnit.java index 1150e19..5b17b9a 100644 --- a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/ScalarUnit.java +++ b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/ScalarUnit.java @@ -86,9 +86,9 @@ public abstract class ScalarUnit { ValidateUtils.strToNum(matcher.group(1)); String scalarUnit = _checkUnitInScalarStandardUnits(matcher.group(2)); value = matcher.group(1) + " " + scalarUnit; - Object on1 = ValidateUtils.strToNum(matcher.group(1)); - Object on2 = SCALAR_UNIT_DICT.get(matcher.group(2)); - Object on3 = SCALAR_UNIT_DICT.get(unit); + Object on1 = ValidateUtils.strToNum(matcher.group(1)) != null ? ValidateUtils.strToNum(matcher.group(1)) : 0; + Object on2 = SCALAR_UNIT_DICT.get(matcher.group(2)) != null ? SCALAR_UNIT_DICT.get(matcher.group(2)) : 0; + Object on3 = SCALAR_UNIT_DICT.get(unit) != null ? SCALAR_UNIT_DICT.get(unit) : 0; Double n1 = new Double(on1.toString()); Double n2 = new Double(on2.toString()); diff --git a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/constraints/Schema.java b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/constraints/Schema.java index c21bd7b..5fa7547 100644 --- a/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/constraints/Schema.java +++ b/jtosca/src/main/java/org/openecomp/sdc/toscaparser/api/elements/constraints/Schema.java @@ -35,11 +35,12 @@ public class Schema { public static final String VERSION = "version"; public static final String PORTDEF = "PortDef"; public static final String PORTSPEC = "PortSpec"; //??? PortSpec.SHORTNAME + public static final String JSON = "json"; public static final String PROPERTY_TYPES[] = { INTEGER, STRING, BOOLEAN, FLOAT, RANGE,NUMBER, TIMESTAMP, LIST, MAP, SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME, - VERSION, PORTDEF, PORTSPEC}; + VERSION, PORTDEF, PORTSPEC, JSON}; @SuppressWarnings("unused") private static final String SCALAR_UNIT_SIZE_DEFAULT = "B"; diff --git a/sdc-distribution-ci/src/main/java/org/openecomp/test/CsarToscaTester.java b/sdc-distribution-ci/src/main/java/org/openecomp/test/CsarToscaTester.java index 4dae5eb..0ac842c 100644 --- a/sdc-distribution-ci/src/main/java/org/openecomp/test/CsarToscaTester.java +++ b/sdc-distribution-ci/src/main/java/org/openecomp/test/CsarToscaTester.java @@ -1,18 +1,74 @@ package org.openecomp.test; -import java.util.ArrayList; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; +import java.util.List; +import org.openecomp.sdc.tosca.parser.api.ISdcCsarHelper; import org.openecomp.sdc.tosca.parser.impl.SdcToscaParserFactory; +import org.openecomp.sdc.toscaparser.api.NodeTemplate; import org.openecomp.sdc.toscaparser.api.common.ExceptionCollector; +import org.openecomp.sdc.toscaparser.api.parameters.Input; public class CsarToscaTester { public static void main(String[] args) throws Exception { ClassLoader loader = CsarToscaTester.class.getClassLoader(); - System.out.println("CsarToscaParser - path to CSAR is "+Arrays.toString(args)); + System.out.println("CsarToscaParser - path to CSAR's Directory is " + Arrays.toString(args)); SdcToscaParserFactory factory = SdcToscaParserFactory.getInstance(); - factory.getSdcCsarHelper(args[0]); - ArrayList exceptionReport = ExceptionCollector.getExceptionReport(); - System.out.println("Errors during CSAR parsing are: "+(exceptionReport != null ? exceptionReport.toString() : "none")); + + File folder = new File(args[0].toString()); + File[] listOfFiles = folder.listFiles(); + FileWriter fw; + + Date now = new Date(); + SimpleDateFormat dateFormat = new SimpleDateFormat("d-MM-y-HH_mm_ss"); + String time = dateFormat.format(now); + File dir = new File(args[1].toString() + "/csar-reports-" + time); + dir.mkdir(); + + + for (File file : listOfFiles) { + if (file.isFile()) { + System.out.println("File " + file.getAbsolutePath()); + ExceptionCollector.clear(); + + ISdcCsarHelper csarHelper = factory.getSdcCsarHelper(file.getAbsolutePath()); + List vflist = csarHelper.getServiceVfList(); + List inputs = csarHelper.getServiceInputs(); + List exceptionReport = ExceptionCollector.getCriticalsReport(); + System.out.println("CRITICALS during CSAR parsing are: " + (exceptionReport != null ? exceptionReport.toString() : "none")); + List warningsReport = ExceptionCollector.getWarningsReport(); + System.out.println("WARNINGS during CSAR parsing are: " + (warningsReport != null ? warningsReport.toString() : "none")); + + + if (!exceptionReport.isEmpty()) { + + try { + fw = new FileWriter(new File(dir + "/critical-" + file.getName() + ".txt")); + for (String exception : exceptionReport) { + fw.write(exception); + fw.write("\r\n"); + } + fw.close(); + + fw = new FileWriter(new File(dir + "/warning-" + file.getName() + ".txt")); + for (String warning : warningsReport) { + fw.write(warning); + fw.write("\r\n"); + } + fw.close(); + + + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + } } } diff --git a/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/api/ISdcCsarHelper.java b/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/api/ISdcCsarHelper.java index 7cc9022..e1c1802 100644 --- a/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/api/ISdcCsarHelper.java +++ b/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/api/ISdcCsarHelper.java @@ -20,6 +20,7 @@ package org.openecomp.sdc.tosca.parser.api; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.tuple.Pair; import org.openecomp.sdc.toscaparser.api.Group; @@ -72,7 +73,33 @@ public interface ISdcCsarHelper { * @return the leaf value as String, or null if there's no such property, or it's not a leaf. */ public String getNodeTemplatePropertyLeafValue(NodeTemplate nodeTemplate, String pathToPropertyLeafValue); - + + /** + * Get any property leaf value for node template by full path separated by #.
+ * For example, for node template with this property:

+ * network_assignments:
+   ecomp_generated_network_assignment: true
+   is_shared_network: false
+   is_external_network: false
+   ipv4_subnet_default_assignments:
+     use_ipv4: true
+     ip_network_address_plan: 1.2.3.4
+     dhcp_enabled: true
+     ip_version: 4
+     cidr_mask: 24
+     min_subnets_count: 1
+   ipv6_subnet_default_assignments:
+     use_ipv6: false

+ + * calling
+ * getNodeTemplatePropertyLeafValue(nodeTemplate, "network_assignments#ipv6_subnet_default_assignments#use_ipv6")
+ * will return "false". + * @param nodeTemplate - nodeTemplate where the property should be looked up. + * @param pathToPropertyLeafValue - the full path of the required property. + * @return the leaf value as Object, or null if there's no such property, or it's not a leaf. + */ + public Object getNodeTemplatePropertyAsObject(NodeTemplate nodeTemplate, String pathToPropertyLeafValue); + /** * Get any property leaf value for a group definition by full path separated by #. * Same logic as in {@link #getNodeTemplatePropertyLeafValue(NodeTemplate, String) getNodeTemplatePropertyLeafValue}, only for a group. @@ -82,6 +109,14 @@ public interface ISdcCsarHelper { */ public String getGroupPropertyLeafValue(Group group, String propertyName); + /** + * Get any property leaf value for a group definition by full path separated by #. + * Same logic as in {@link #getNodeTemplatePropertyLeafValue(NodeTemplate, String) getNodeTemplatePropertyLeafValue}, only for a group. + * @param group - group where the property should be looked up. + * @param propertyName - the name of the required property. + * @return the leaf value as Object, or null if there's no such property, or it's not a leaf. + */ + public Object getGroupPropertyAsObject(Group group, String propertyName); /** * Get all VL node templates of the CSAR service. @@ -133,7 +168,16 @@ public interface ISdcCsarHelper { * @return input leaf value for the service. */ public String getServiceInputLeafValueOfDefault(String inputLeafValuePath); - + + /** + * Get input leaf value for the CSAR service, by full path separated by #.
+ * Same logic as in {@link #getNodeTemplatePropertyLeafValue(NodeTemplate, String) getNodeTemplatePropertyLeafValue}, only for an input full path. + * The expected format is "input_name#default[optionally #rest_of_path]" + * @param inputLeafValuePath by full path separated by #. + * @return input leaf value for the service as Service. + */ + public Object getServiceInputLeafValueOfDefaultAsObject(String inputLeafValuePath); + /** * Get the type name of the CSAR service's substitution mappings element.
* @@ -247,6 +291,15 @@ public interface ISdcCsarHelper { */ public List getServiceInputs(); + public String getConformanceLevel(); + + + /** + * Get the map of CP-related props from + * @param vfc - VFC to look for CP-related props. + * @return map of CP node template name to a map of CP-related properties key-value for this CP. + */ + public Map> getCpPropertiesFromVfc(NodeTemplate vfc); } diff --git a/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/impl/SdcCsarHelperImpl.java b/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/impl/SdcCsarHelperImpl.java index 9280322..b9ce069 100644 --- a/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/impl/SdcCsarHelperImpl.java +++ b/sdc-tosca-parser/src/main/java/org/openecomp/sdc/tosca/parser/impl/SdcCsarHelperImpl.java @@ -20,17 +20,14 @@ package org.openecomp.sdc.tosca.parser.impl; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +//import org.json.JSONObject; import org.openecomp.sdc.tosca.parser.api.ISdcCsarHelper; import org.openecomp.sdc.tosca.parser.utils.GeneralUtility; import org.openecomp.sdc.tosca.parser.utils.SdcToscaUtility; @@ -46,109 +43,169 @@ import org.openecomp.sdc.toscaparser.api.parameters.Input; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SdcCsarHelperImpl implements ISdcCsarHelper { - - private static final String PATH_DELIMITER = "#"; - private ToscaTemplate toscaTemplate; - private static Logger log = LoggerFactory.getLogger(SdcCsarHelperImpl.class.getName()); - - public SdcCsarHelperImpl(ToscaTemplate toscaTemplate) { - this.toscaTemplate = toscaTemplate; - } - - @Override - //Sunny flow - covered with UT, flat and nested - public String getNodeTemplatePropertyLeafValue(NodeTemplate nodeTemplate, String leafValuePath) { - if (nodeTemplate == null) { - log.error("getNodeTemplatePropertyLeafValue - nodeTemplate is null"); - return null; - } - if (GeneralUtility.isEmptyString(leafValuePath)) { - log.error("getNodeTemplatePropertyLeafValue - leafValuePath is null or empty"); - return null; - } - log.debug("getNodeTemplatePropertyLeafValue - nodeTemplate is : {}, leafValuePath is {} ", nodeTemplate, leafValuePath); - String[] split = getSplittedPath(leafValuePath); - LinkedHashMap properties = nodeTemplate.getProperties(); - log.debug("getNodeTemplatePropertyLeafValue - properties of nodeTemplate are : {}", properties); - return processProperties(split, properties); - } - @Override - //Sunny flow - covered with UT - public List getServiceVlList() { - List serviceVlList = getNodeTemplateBySdcType(toscaTemplate.getTopologyTemplate(), Types.TYPE_VL); - log.debug("getServiceVlList - the VL list is {}", serviceVlList); - return serviceVlList; - } - - @Override - //Sunny flow - covered with UT - public List getServiceVfList() { - List serviceVfList = getNodeTemplateBySdcType(toscaTemplate.getTopologyTemplate(), Types.TYPE_VF); - log.debug("getServiceVfList - the VF list is {}", serviceVfList); - return serviceVfList; - } - - @Override - //Sunny flow - covered with UT - public String getMetadataPropertyValue(Metadata metadata, String metadataPropertyName) { - if (GeneralUtility.isEmptyString(metadataPropertyName)) { - log.error("getMetadataPropertyValue - the metadataPropertyName is null or empty"); - return null; - } - if (metadata == null) { - log.error("getMetadataPropertyValue - the metadata is null"); - return null; - } - String metadataPropertyValue = metadata.getValue(metadataPropertyName); - log.debug("getMetadataPropertyValue - metadata is {} metadataPropertyName is {} the value is : {}", metadata, metadataPropertyName , metadataPropertyValue); - return metadataPropertyValue; - } - - - @Override - //Sunny flow - covered with UT - public List getServiceNodeTemplatesByType(String nodeType) { - if (GeneralUtility.isEmptyString(nodeType)) { - log.error("getServiceNodeTemplatesByType - nodeType - is null or empty"); - return new ArrayList<>(); - } - - List res = new ArrayList<>(); - List nodeTemplates = toscaTemplate.getNodeTemplates(); - for (NodeTemplate nodeTemplate : nodeTemplates){ - if (nodeType.equals(nodeTemplate.getTypeDefinition().getType())){ - res.add(nodeTemplate); - } - } - - log.debug("getServiceNodeTemplatesByType - For Node Type : {} - NodeTemplate list value is: {}", nodeType, res); - return res; - } - - @Override - //Sunny flow - covered with UT - public List getVfcListByVf(String vfCustomizationId) { - if (GeneralUtility.isEmptyString(vfCustomizationId)) { - log.error("getVfcListByVf - vfCustomizationId - is null or empty"); - return new ArrayList<>(); - } +import static org.openecomp.sdc.tosca.parser.impl.SdcPropertyNames.PROPERTY_NAME_CUSTOMIZATIONUUID; - List serviceVfList = getServiceVfList(); - NodeTemplate vfInstance = getNodeTemplateByCustomizationUuid(serviceVfList, vfCustomizationId); - log.debug("getVfcListByVf - serviceVfList value: {}, vfInstance value: {}", serviceVfList, vfInstance); - return getNodeTemplateBySdcType(vfInstance, Types.TYPE_VFC); - } +public class SdcCsarHelperImpl implements ISdcCsarHelper { - @Override - //Sunny flow - covered with UT - public List getVfModulesByVf(String vfCustomizationUuid) { - List serviceVfList = getServiceVfList(); - log.debug("getVfModulesByVf - VF list is {}", serviceVfList); - NodeTemplate nodeTemplateByCustomizationUuid = getNodeTemplateByCustomizationUuid(serviceVfList, vfCustomizationUuid); - log.debug("getVfModulesByVf - getNodeTemplateByCustomizationUuid is {}, customizationUuid {}", nodeTemplateByCustomizationUuid, vfCustomizationUuid); - if (nodeTemplateByCustomizationUuid != null){ - /*SubstitutionMappings substitutionMappings = nodeTemplateByCustomizationUuid.getSubstitutionMappings(); + private static final String PATH_DELIMITER = "#"; + private static final String PREFIX = "port_"; + private static final String[] SUFFIX = new String[]{"_network_role_tag", "_ip_requirements", "_subnetpoolid"}; + private ToscaTemplate toscaTemplate; + private static Logger log = LoggerFactory.getLogger(SdcCsarHelperImpl.class.getName()); + + public SdcCsarHelperImpl(ToscaTemplate toscaTemplate) { + this.toscaTemplate = toscaTemplate; + } + + @Override + //Sunny flow - covered with UT, flat and nested + public String getNodeTemplatePropertyLeafValue(NodeTemplate nodeTemplate, String leafValuePath) { + if (nodeTemplate == null) { + log.error("getNodeTemplatePropertyLeafValue - nodeTemplate is null"); + return null; + } + if (GeneralUtility.isEmptyString(leafValuePath)) { + log.error("getNodeTemplatePropertyLeafValue - leafValuePath is null or empty"); + return null; + } + log.debug("getNodeTemplatePropertyLeafValue - nodeTemplate is : {}, leafValuePath is {} ", nodeTemplate, leafValuePath); + String[] split = getSplittedPath(leafValuePath); + LinkedHashMap properties = nodeTemplate.getProperties(); + log.debug("getNodeTemplatePropertyLeafValue - properties of nodeTemplate are : {}", properties); + Object property = processProperties(split, properties); + return property == null ? null : String.valueOf(property); + } + + @Override + public Object getNodeTemplatePropertyAsObject(NodeTemplate nodeTemplate, String leafValuePath) { + if (nodeTemplate == null) { + log.error("getNodeTemplatePropertyAsObject - nodeTemplate is null"); + return null; + } + if (GeneralUtility.isEmptyString(leafValuePath)) { + log.error("getNodeTemplatePropertyAsObject - leafValuePath is null or empty"); + return null; + } + log.debug("getNodeTemplatePropertyAsObject - nodeTemplate is : {}, leafValuePath is {} ", nodeTemplate, leafValuePath); + String[] split = getSplittedPath(leafValuePath); + LinkedHashMap properties = nodeTemplate.getProperties(); + log.debug("getNodeTemplatePropertyAsObject - properties of nodeTemplate are : {}", properties); + return processProperties(split, properties); + } + + public Map> getCpPropertiesFromVfc(NodeTemplate vfc) { + + List paths = new ArrayList<>(); + paths.add("network_role_tag"); + paths.add("ip_requirements#ip_count_required#count"); + paths.add("ip_requirements#dhcp_enabled"); + paths.add("ip_requirements#ip_version"); + paths.add("subnetpoolid"); + + Map props = vfc.getProperties(); + + Map> cps = new HashMap<>(); + + for (Map.Entry entry : props.entrySet()) { + String fullCpName = entry.getKey(); + + if (fullCpName.startsWith(PREFIX) && + Arrays.stream(SUFFIX).parallel().anyMatch(fullCpName::endsWith)) + { + //this is CP - get all it's properties according to paths list + String cpName = fullCpName.replaceAll("^("+PREFIX+")", "").replaceAll("("+String.join("|", SUFFIX)+")$", ""); + cps.put(cpName, new HashMap<>()); + for (String path: paths) { + String fullPathToSearch = PREFIX + cpName + "_" + path; + String value = getNodeTemplatePropertyLeafValue(vfc, fullPathToSearch); + if (value != null) { + value = StringUtils.stripStart(value, "["); + value = StringUtils.stripEnd(value, "]"); + cps.get(cpName).put(path, value); + } + } + } + } + + return cps; + } + + @Override + //Sunny flow - covered with UT + public List getServiceVlList() { + List serviceVlList = getNodeTemplateBySdcType(toscaTemplate.getTopologyTemplate(), Types.TYPE_VL); + log.debug("getServiceVlList - the VL list is {}", serviceVlList); + return serviceVlList; + } + + @Override + //Sunny flow - covered with UT + public List getServiceVfList() { + List serviceVfList = getNodeTemplateBySdcType(toscaTemplate.getTopologyTemplate(), Types.TYPE_VF); + log.debug("getServiceVfList - the VF list is {}", serviceVfList); + return serviceVfList; + } + + @Override + //Sunny flow - covered with UT + public String getMetadataPropertyValue(Metadata metadata, String metadataPropertyName) { + if (GeneralUtility.isEmptyString(metadataPropertyName)) { + log.error("getMetadataPropertyValue - the metadataPropertyName is null or empty"); + return null; + } + if (metadata == null) { + log.error("getMetadataPropertyValue - the metadata is null"); + return null; + } + String metadataPropertyValue = metadata.getValue(metadataPropertyName); + log.debug("getMetadataPropertyValue - metadata is {} metadataPropertyName is {} the value is : {}", metadata, metadataPropertyName, metadataPropertyValue); + return metadataPropertyValue; + } + + + @Override + //Sunny flow - covered with UT + public List getServiceNodeTemplatesByType(String nodeType) { + if (GeneralUtility.isEmptyString(nodeType)) { + log.error("getServiceNodeTemplatesByType - nodeType - is null or empty"); + return new ArrayList<>(); + } + + List res = new ArrayList<>(); + List nodeTemplates = toscaTemplate.getNodeTemplates(); + for (NodeTemplate nodeTemplate : nodeTemplates) { + if (nodeType.equals(nodeTemplate.getTypeDefinition().getType())) { + res.add(nodeTemplate); + } + } + + log.debug("getServiceNodeTemplatesByType - For Node Type : {} - NodeTemplate list value is: {}", nodeType, res); + return res; + } + + @Override + //Sunny flow - covered with UT + public List getVfcListByVf(String vfCustomizationId) { + if (GeneralUtility.isEmptyString(vfCustomizationId)) { + log.error("getVfcListByVf - vfCustomizationId - is null or empty"); + return new ArrayList<>(); + } + + List serviceVfList = getServiceVfList(); + NodeTemplate vfInstance = getNodeTemplateByCustomizationUuid(serviceVfList, vfCustomizationId); + log.debug("getVfcListByVf - serviceVfList value: {}, vfInstance value: {}", serviceVfList, vfInstance); + return getNodeTemplateBySdcType(vfInstance, Types.TYPE_VFC); + } + + @Override + //Sunny flow - covered with UT + public List getVfModulesByVf(String vfCustomizationUuid) { + List serviceVfList = getServiceVfList(); + log.debug("getVfModulesByVf - VF list is {}", serviceVfList); + NodeTemplate nodeTemplateByCustomizationUuid = getNodeTemplateByCustomizationUuid(serviceVfList, vfCustomizationUuid); + log.debug("getVfModulesByVf - getNodeTemplateByCustomizationUuid is {}, customizationUuid {}", nodeTemplateByCustomizationUuid, vfCustomizationUuid); + if (nodeTemplateByCustomizationUuid != null) { + /*SubstitutionMappings substitutionMappings = nodeTemplateByCustomizationUuid.getSubstitutionMappings(); if (substitutionMappings != null){ List groups = substitutionMappings.getGroups(); if (groups != null){ @@ -157,255 +214,307 @@ public class SdcCsarHelperImpl implements ISdcCsarHelper { return collect; } }*/ - String name = nodeTemplateByCustomizationUuid.getName(); - String normaliseComponentInstanceName = SdcToscaUtility.normaliseComponentInstanceName(name); - List serviceLevelGroups = toscaTemplate.getTopologyTemplate().getGroups(); - log.debug("getVfModulesByVf - VF node template name {}, normalized name {}. Searching groups on service level starting with VF normalized name...", name, normaliseComponentInstanceName); - if (serviceLevelGroups != null){ - List collect = serviceLevelGroups - .stream() - .filter(x -> "org.openecomp.groups.VfModule".equals(x.getTypeDefinition().getType()) && x.getName().startsWith(normaliseComponentInstanceName)) - .collect(Collectors.toList()); - log.debug("getVfModulesByVf - VfModules are {}", collect); - return collect; - } - } - return new ArrayList<>(); - } - - @Override - //Sunny flow - covered with UT - public String getServiceInputLeafValueOfDefault(String inputLeafValuePath) { - if (GeneralUtility.isEmptyString(inputLeafValuePath)) { - log.error("getServiceInputLeafValueOfDefault - inputLeafValuePath is null or empty"); - return null; - } - - String[] split = getSplittedPath(inputLeafValuePath); - if (split.length < 2 || !split[1].equals("default")){ - log.error("getServiceInputLeafValue - inputLeafValuePath should be of format #default[optionally #] "); - return null; - } - - List inputs = toscaTemplate.getInputs(); - log.debug("getServiceInputLeafValue - the leafValuePath is {} , the inputs are {}", inputLeafValuePath, inputs); - if (inputs != null){ - Optional findFirst = inputs.stream().filter(x -> x.getName().equals(split[0])).findFirst(); - if (findFirst.isPresent()){ - log.debug("getServiceInputLeafValue - find first item is {}", findFirst.get()); - Input input = findFirst.get(); - Object current = input.getDefault(); - return iterateProcessPath(2, current, split); - } - } - log.error("getServiceInputLeafValue - value not found"); - return null; - } - - private String iterateProcessPath(Integer index, Object current, String[] split) { - if (current == null) { - log.error("iterateProcessPath - this input has no default"); - return null; - } - if (split.length > index) { - for (int i = index; i < split.length; i++) { - if (current instanceof Map){ - current = ((Map)current).get(split[i]); - } else { - log.error("iterateProcessPath - found an unexpected leaf where expected to find a complex type"); - return null; - } - } - } - if (current != null) { - log.debug("iterateProcessPath - the input default leaf value is {}", String.valueOf(current)); - return String.valueOf(current); - } - log.error("iterateProcessPath - Path not Found"); - return null; - } - - private String[] getSplittedPath(String inputLeafValuePath) { - return inputLeafValuePath.split(PATH_DELIMITER); - } - - - @Override - //Sunny flow - covered with UT - public String getServiceSubstitutionMappingsTypeName() { - SubstitutionMappings substitutionMappings = toscaTemplate.getTopologyTemplate().getSubstitutionMappings(); - if (substitutionMappings == null) { - log.debug("getServiceSubstitutionMappingsTypeName - No Substitution Mappings defined"); - return null; - } - log.debug("getServiceSubstitutionMappingsTypeName - SubstitutionMappings value: {}", substitutionMappings); - - NodeType nodeType = substitutionMappings.getNodeDefinition(); - if (nodeType == null) { - log.debug("getServiceSubstitutionMappingsTypeName - No Substitution Mappings node defined"); - return null; - } - log.debug("getServiceSubstitutionMappingsTypeName - nodeType value: {}", nodeType); - - return nodeType.getType(); - } - - @Override - //Sunny flow - covered with UT - public Metadata getServiceMetadata() { - return toscaTemplate.getMetaData(); - } - - @Override - //Sunny flow - covered with UT - public List getServiceInputs() { - return toscaTemplate.getInputs(); - } - - @Override - //Sunny flow - covered with UT - public String getGroupPropertyLeafValue(Group group, String leafValuePath) { - if (group == null) { - log.error("getGroupPropertyLeafValue - group is null"); - return null; - } - - if (GeneralUtility.isEmptyString(leafValuePath)) { - log.error("getGroupPropertyLeafValue - leafValuePath is null or empty"); - return null; - } - - String[] split = getSplittedPath(leafValuePath); - LinkedHashMap properties = group.getProperties(); - return processProperties(split, properties); - } - - @Override - //Sunny flow - covered with UT - public List getCpListByVf(String vfCustomizationId) { - List cpList = new ArrayList<>(); - if (GeneralUtility.isEmptyString(vfCustomizationId)){ - log.error("getCpListByVf vfCustomizationId string is empty"); - return cpList; - } - - List serviceVfList = getServiceVfList(); - if (serviceVfList == null || serviceVfList.size() == 0){ - log.error("getCpListByVf Vfs not exist for vfCustomizationId {}",vfCustomizationId); - return cpList; - } - NodeTemplate vfInstance = getNodeTemplateByCustomizationUuid(serviceVfList, vfCustomizationId); - log.debug("getCpListByVf vf list is {}", vfInstance); - if (vfInstance == null) { - log.debug("getCpListByVf vf list is null"); - return cpList; - } - cpList = getNodeTemplateBySdcType(vfInstance, Types.TYPE_CP); - if(cpList == null || cpList.size()==0) - log.debug("getCpListByVf cps not exist for vfCustomizationId {}",vfCustomizationId); - return cpList; - } - - @Override - //Sunny flow - covered with UT - public List getMembersOfVfModule(NodeTemplate vf, Group serviceLevelVfModule) { - if (vf == null) { - log.error("getMembersOfVfModule - vf is null"); - return new ArrayList<>(); - } - - if (serviceLevelVfModule == null || serviceLevelVfModule.getMetadata() == null || serviceLevelVfModule.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID) == null) { - log.error("getMembersOfVfModule - vfModule or its metadata is null. Cannot match a VF group based on invariantUuid from missing metadata."); - return new ArrayList<>(); - } - - - SubstitutionMappings substitutionMappings = vf.getSubMappingToscaTemplate(); - if (substitutionMappings != null){ - List groups = substitutionMappings.getGroups(); - if (groups != null){ - Optional findFirst = groups - .stream() - .filter(x -> (x.getMetadata() != null && serviceLevelVfModule.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID).equals(x.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)))).findFirst(); - if (findFirst.isPresent()){ - log.debug("getMembersOfVfModule - Found VF level group with vfModuleModelInvariantUUID {}", serviceLevelVfModule.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)); - List members = findFirst.get().getMembers(); - log.debug("getMembersOfVfModule - members section is {}", members); - if (members != null){ - List collect = substitutionMappings.getNodeTemplates().stream().filter(x -> members.contains(x.getName())).collect(Collectors.toList()); - log.debug("getMembersOfVfModule - Node templates are {}", collect); - return collect; - } - } - } - } - return new ArrayList<>(); - } - - @Override - //Sunny flow - covered with UT - public List> getNodeTemplatePairsByReqName( - List listOfReqNodeTemplates, List listOfCapNodeTemplates, String reqName) { - if (listOfReqNodeTemplates == null || listOfCapNodeTemplates == null || reqName == null){ - //TODO error message - return new ArrayList<>(); - } - - List> pairsList = new ArrayList<>(); - - if (listOfReqNodeTemplates != null){ - for (NodeTemplate reqNodeTemplate : listOfReqNodeTemplates) { - List requirements = reqNodeTemplate.getRequirements(); - for (Object reqEntry : requirements) { - LinkedHashMap reqEntryHash = (LinkedHashMap) reqEntry; - Map reqEntryMap = (Map) reqEntryHash.get(reqName); - if (reqEntryMap != null){ - Object node = reqEntryMap.get("node"); - if (node != null){ - String nodeString = (String)node; - Optional findFirst = listOfCapNodeTemplates.stream().filter(x -> x.getName().equals(nodeString)).findFirst(); - if (findFirst.isPresent()){ - pairsList.add(new ImmutablePair(reqNodeTemplate, findFirst.get())); - } - } - } - } - } - } - return pairsList; - } - - @Override - //Sunny flow - covered with UT - //TODO constant strings - public List getAllottedResources() { - List nodeTemplates = null; - nodeTemplates = toscaTemplate.getTopologyTemplate().getNodeTemplates(); - if (nodeTemplates.isEmpty()) { - log.error("getAllottedResources nodeTemplates not exist"); - } - nodeTemplates = nodeTemplates.stream().filter( - x -> x.getMetaData() != null && x.getMetaData().getValue("category").equals("Allotted Resource")) - .collect(Collectors.toList()); - if (nodeTemplates.isEmpty()) { - log.debug("getAllottedResources - allotted resources not exist"); - } else { - log.debug("getAllottedResources - the allotted resources list is {}", nodeTemplates); - } - - return nodeTemplates; - } - @Override - //Sunny flow - covered with UT - public String getTypeOfNodeTemplate(NodeTemplate nodeTemplate) { - if(nodeTemplate == null){ - - log.error("getTypeOfNodeTemplate nodeTemplate is null"); - return null; - } - log.debug("getTypeOfNodeTemplate node template type is {}",nodeTemplate.getTypeDefinition().getType()); - return nodeTemplate.getTypeDefinition().getType(); - } + String name = nodeTemplateByCustomizationUuid.getName(); + String normaliseComponentInstanceName = SdcToscaUtility.normaliseComponentInstanceName(name); + List serviceLevelGroups = toscaTemplate.getTopologyTemplate().getGroups(); + log.debug("getVfModulesByVf - VF node template name {}, normalized name {}. Searching groups on service level starting with VF normalized name...", name, normaliseComponentInstanceName); + if (serviceLevelGroups != null) { + List collect = serviceLevelGroups + .stream() + .filter(x -> "org.openecomp.groups.VfModule".equals(x.getTypeDefinition().getType()) && x.getName().startsWith(normaliseComponentInstanceName)) + .collect(Collectors.toList()); + log.debug("getVfModulesByVf - VfModules are {}", collect); + return collect; + } + } + return new ArrayList<>(); + } + + @Override + //Sunny flow - covered with UT + public String getServiceInputLeafValueOfDefault(String inputLeafValuePath) { + if (GeneralUtility.isEmptyString(inputLeafValuePath)) { + log.error("getServiceInputLeafValueOfDefault - inputLeafValuePath is null or empty"); + return null; + } + + String[] split = getSplittedPath(inputLeafValuePath); + if (split.length < 2 || !split[1].equals("default")) { + log.error("getServiceInputLeafValue - inputLeafValuePath should be of format #default[optionally #] "); + return null; + } + + List inputs = toscaTemplate.getInputs(); + log.debug("getServiceInputLeafValue - the leafValuePath is {} , the inputs are {}", inputLeafValuePath, inputs); + if (inputs != null) { + Optional findFirst = inputs.stream().filter(x -> x.getName().equals(split[0])).findFirst(); + if (findFirst.isPresent()) { + log.debug("getServiceInputLeafValue - find first item is {}", findFirst.get()); + Input input = findFirst.get(); + Object current = input.getDefault(); + Object property = iterateProcessPath(2, current, split); + return property == null ? null : String.valueOf(property); + } + } + log.error("getServiceInputLeafValue - value not found"); + return null; + } + + @Override + public Object getServiceInputLeafValueOfDefaultAsObject(String inputLeafValuePath) { + if (GeneralUtility.isEmptyString(inputLeafValuePath)) { + log.error("getServiceInputLeafValueOfDefaultAsObject - inputLeafValuePath is null or empty"); + return null; + } + + String[] split = getSplittedPath(inputLeafValuePath); + if (split.length < 2 || !split[1].equals("default")) { + log.error("getServiceInputLeafValueOfDefaultAsObject - inputLeafValuePath should be of format #default[optionally #] "); + return null; + } + + List inputs = toscaTemplate.getInputs(); + log.debug("getServiceInputLeafValueOfDefaultAsObject - the leafValuePath is {} , the inputs are {}", inputLeafValuePath, inputs); + if (inputs != null) { + Optional findFirst = inputs.stream().filter(x -> x.getName().equals(split[0])).findFirst(); + if (findFirst.isPresent()) { + log.debug("getServiceInputLeafValueOfDefaultAsObject - find first item is {}", findFirst.get()); + Input input = findFirst.get(); + Object current = input.getDefault(); + return iterateProcessPath(2, current, split); + } + } + log.error("getServiceInputLeafValueOfDefaultAsObject - value not found"); + return null; + } + + private Object iterateProcessPath(Integer index, Object current, String[] split) { + if (current == null) { + log.error("iterateProcessPath - this input has no default"); + return null; + } + if (split.length > index) { + for (int i = index; i < split.length; i++) { + if (current instanceof Map) { + current = ((Map) current).get(split[i]); + } else if (current instanceof List) { + current = ((List) current).get(0); + i--; + } + else { + log.error("iterateProcessPath - found an unexpected leaf where expected to find a complex type"); + return null; + } + } + } + if (current != null) { + log.debug("iterateProcessPath - the input default leaf value is {}", String.valueOf(current)); + return current; + } + log.error("iterateProcessPath - Path not Found"); + return null; + } + + private String[] getSplittedPath(String inputLeafValuePath) { + return inputLeafValuePath.split(PATH_DELIMITER); + } + + + @Override + //Sunny flow - covered with UT + public String getServiceSubstitutionMappingsTypeName() { + SubstitutionMappings substitutionMappings = toscaTemplate.getTopologyTemplate().getSubstitutionMappings(); + if (substitutionMappings == null) { + log.debug("getServiceSubstitutionMappingsTypeName - No Substitution Mappings defined"); + return null; + } + log.debug("getServiceSubstitutionMappingsTypeName - SubstitutionMappings value: {}", substitutionMappings); + + NodeType nodeType = substitutionMappings.getNodeDefinition(); + if (nodeType == null) { + log.debug("getServiceSubstitutionMappingsTypeName - No Substitution Mappings node defined"); + return null; + } + log.debug("getServiceSubstitutionMappingsTypeName - nodeType value: {}", nodeType); + + return nodeType.getType(); + } + + @Override + //Sunny flow - covered with UT + public Metadata getServiceMetadata() { + return toscaTemplate.getMetaData(); + } + + @Override + //Sunny flow - covered with UT + public List getServiceInputs() { + return toscaTemplate.getInputs(); + } + + @Override + //Sunny flow - covered with UT + public String getGroupPropertyLeafValue(Group group, String leafValuePath) { + if (group == null) { + log.error("getGroupPropertyLeafValue - group is null"); + return null; + } + + if (GeneralUtility.isEmptyString(leafValuePath)) { + log.error("getGroupPropertyLeafValue - leafValuePath is null or empty"); + return null; + } + + String[] split = getSplittedPath(leafValuePath); + LinkedHashMap properties = group.getProperties(); + Object property = processProperties(split, properties); + return property == null ? null : String.valueOf(property); + } + + @Override + public Object getGroupPropertyAsObject(Group group, String leafValuePath) { + if (group == null) { + log.error("getGroupPropertyAsObject - group is null"); + return null; + } + + if (GeneralUtility.isEmptyString(leafValuePath)) { + log.error("getGroupPropertyAsObject - leafValuePath is null or empty"); + return null; + } + + String[] split = getSplittedPath(leafValuePath); + LinkedHashMap properties = group.getProperties(); + return processProperties(split, properties); + } + + @Override + //Sunny flow - covered with UT + public List getCpListByVf(String vfCustomizationId) { + List cpList = new ArrayList<>(); + if (GeneralUtility.isEmptyString(vfCustomizationId)) { + log.error("getCpListByVf vfCustomizationId string is empty"); + return cpList; + } + + List serviceVfList = getServiceVfList(); + if (serviceVfList == null || serviceVfList.size() == 0) { + log.error("getCpListByVf Vfs not exist for vfCustomizationId {}", vfCustomizationId); + return cpList; + } + NodeTemplate vfInstance = getNodeTemplateByCustomizationUuid(serviceVfList, vfCustomizationId); + log.debug("getCpListByVf vf list is {}", vfInstance); + if (vfInstance == null) { + log.debug("getCpListByVf vf list is null"); + return cpList; + } + cpList = getNodeTemplateBySdcType(vfInstance, Types.TYPE_CP); + if (cpList == null || cpList.size() == 0) + log.debug("getCpListByVf cps not exist for vfCustomizationId {}", vfCustomizationId); + return cpList; + } + + @Override + //Sunny flow - covered with UT + public List getMembersOfVfModule(NodeTemplate vf, Group serviceLevelVfModule) { + if (vf == null) { + log.error("getMembersOfVfModule - vf is null"); + return new ArrayList<>(); + } + + if (serviceLevelVfModule == null || serviceLevelVfModule.getMetadata() == null || serviceLevelVfModule.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID) == null) { + log.error("getMembersOfVfModule - vfModule or its metadata is null. Cannot match a VF group based on invariantUuid from missing metadata."); + return new ArrayList<>(); + } + + + SubstitutionMappings substitutionMappings = vf.getSubMappingToscaTemplate(); + if (substitutionMappings != null) { + List groups = substitutionMappings.getGroups(); + if (groups != null) { + Optional findFirst = groups + .stream() + .filter(x -> (x.getMetadata() != null && serviceLevelVfModule.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID).equals(x.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)))).findFirst(); + if (findFirst.isPresent()) { + log.debug("getMembersOfVfModule - Found VF level group with vfModuleModelInvariantUUID {}", serviceLevelVfModule.getMetadata().getValue(SdcPropertyNames.PROPERTY_NAME_VFMODULEMODELINVARIANTUUID)); + List members = findFirst.get().getMembers(); + log.debug("getMembersOfVfModule - members section is {}", members); + if (members != null) { + List collect = substitutionMappings.getNodeTemplates().stream().filter(x -> members.contains(x.getName())).collect(Collectors.toList()); + log.debug("getMembersOfVfModule - Node templates are {}", collect); + return collect; + } + } + } + } + return new ArrayList<>(); + } + + @Override + //Sunny flow - covered with UT + public List> getNodeTemplatePairsByReqName( + List listOfReqNodeTemplates, List listOfCapNodeTemplates, String reqName) { + if (listOfReqNodeTemplates == null || listOfCapNodeTemplates == null || reqName == null) { + //TODO error message + return new ArrayList<>(); + } + + List> pairsList = new ArrayList<>(); + + if (listOfReqNodeTemplates != null) { + for (NodeTemplate reqNodeTemplate : listOfReqNodeTemplates) { + List requirements = reqNodeTemplate.getRequirements(); + for (Object reqEntry : requirements) { + LinkedHashMap reqEntryHash = (LinkedHashMap) reqEntry; + Map reqEntryMap = (Map) reqEntryHash.get(reqName); + if (reqEntryMap != null) { + Object node = reqEntryMap.get("node"); + if (node != null) { + String nodeString = (String) node; + Optional findFirst = listOfCapNodeTemplates.stream().filter(x -> x.getName().equals(nodeString)).findFirst(); + if (findFirst.isPresent()) { + pairsList.add(new ImmutablePair(reqNodeTemplate, findFirst.get())); + } + } + } + } + } + } + return pairsList; + } + + @Override + //Sunny flow - covered with UT + //TODO constant strings + public List getAllottedResources() { + List nodeTemplates = null; + nodeTemplates = toscaTemplate.getTopologyTemplate().getNodeTemplates(); + if (nodeTemplates.isEmpty()) { + log.error("getAllottedResources nodeTemplates not exist"); + } + nodeTemplates = nodeTemplates.stream().filter( + x -> x.getMetaData() != null && x.getMetaData().getValue("category").equals("Allotted Resource")) + .collect(Collectors.toList()); + if (nodeTemplates.isEmpty()) { + log.debug("getAllottedResources - allotted resources not exist"); + } else { + log.debug("getAllottedResources - the allotted resources list is {}", nodeTemplates); + } + + return nodeTemplates; + } + + @Override + //Sunny flow - covered with UT + public String getTypeOfNodeTemplate(NodeTemplate nodeTemplate) { + if (nodeTemplate == null) { + + log.error("getTypeOfNodeTemplate nodeTemplate is null"); + return null; + } + log.debug("getTypeOfNodeTemplate node template type is {}", nodeTemplate.getTypeDefinition().getType()); + return nodeTemplate.getTypeDefinition().getType(); + } @Override public String getConformanceLevel() { @@ -426,69 +535,69 @@ public class SdcCsarHelperImpl implements ISdcCsarHelper { } } - /************************************* helper functions ***********************************/ - private List getNodeTemplateBySdcType(NodeTemplate nodeTemplate, String sdcType){ - if (nodeTemplate == null) { - log.error("getNodeTemplateBySdcType - nodeTemplate is null or empty"); - return new ArrayList<>(); - } - - if (GeneralUtility.isEmptyString(sdcType)) { - log.error("getNodeTemplateBySdcType - sdcType is null or empty"); - return new ArrayList<>(); - } - - SubstitutionMappings substitutionMappings = nodeTemplate.getSubMappingToscaTemplate(); - - if (substitutionMappings != null) { - List nodeTemplates = substitutionMappings.getNodeTemplates(); - if (nodeTemplates != null && nodeTemplates.size() > 0) - return nodeTemplates.stream().filter(x -> (x.getMetaData() != null && sdcType.equals(x.getMetaData().getValue(SdcPropertyNames.PROPERTY_NAME_TYPE)))).collect(Collectors.toList()); - else - log.debug("getNodeTemplateBySdcType - SubstitutionMappings' node Templates not exist"); - } else - log.debug("getNodeTemplateBySdcType - SubstitutionMappings not exist"); - - return new ArrayList<>(); - } - - private List getNodeTemplateBySdcType(TopologyTemplate topologyTemplate, String sdcType){ - if (GeneralUtility.isEmptyString(sdcType)) { - log.error("getNodeTemplateBySdcType - sdcType is null or empty"); - return new ArrayList<>(); - } - - if (topologyTemplate == null) { - log.error("getNodeTemplateBySdcType - topologyTemplate is null"); - return new ArrayList<>(); - } - - List nodeTemplates = topologyTemplate.getNodeTemplates(); - - if (nodeTemplates != null && nodeTemplates.size() > 0) - return nodeTemplates.stream().filter(x -> (x.getMetaData() != null && sdcType.equals(x.getMetaData().getValue(SdcPropertyNames.PROPERTY_NAME_TYPE)))).collect(Collectors.toList()); - - log.debug("getNodeTemplateBySdcType - topologyTemplate's nodeTemplates not exist"); - return new ArrayList<>(); - } - - //Assumed to be unique property for the list - private NodeTemplate getNodeTemplateByCustomizationUuid(List nodeTemplates, String customizationId){ - log.debug("getNodeTemplateByCustomizationUuid - nodeTemplates {}, customizationId {}", nodeTemplates, customizationId); - Optional findFirst = nodeTemplates.stream().filter(x -> (x.getMetaData() != null && customizationId.equals(x.getMetaData().getValue(SdcPropertyNames.PROPERTY_NAME_CUSTOMIZATIONUUID)))).findFirst(); - return findFirst.isPresent() ? findFirst.get() : null; - } - - private String processProperties(String[] split, LinkedHashMap properties) { - log.debug("processProperties - the leafValuePath is {} , the properties are {}", Arrays.toString(split), properties.toString()); - Optional> findFirst = properties.entrySet().stream().filter(x -> x.getKey().equals(split[0])).findFirst(); - if (findFirst.isPresent()){ - log.debug("processProperties - find first item is {}", findFirst.get()); - Property property = findFirst.get().getValue(); - Object current = property.getValue(); - return iterateProcessPath(1, current, split); - } - log.error("processProperties - Dont find property"); - return null; - } + /************************************* helper functions ***********************************/ + private List getNodeTemplateBySdcType(NodeTemplate nodeTemplate, String sdcType) { + if (nodeTemplate == null) { + log.error("getNodeTemplateBySdcType - nodeTemplate is null or empty"); + return new ArrayList<>(); + } + + if (GeneralUtility.isEmptyString(sdcType)) { + log.error("getNodeTemplateBySdcType - sdcType is null or empty"); + return new ArrayList<>(); + } + + SubstitutionMappings substitutionMappings = nodeTemplate.getSubMappingToscaTemplate(); + + if (substitutionMappings != null) { + List nodeTemplates = substitutionMappings.getNodeTemplates(); + if (nodeTemplates != null && nodeTemplates.size() > 0) + return nodeTemplates.stream().filter(x -> (x.getMetaData() != null && sdcType.equals(x.getMetaData().getValue(SdcPropertyNames.PROPERTY_NAME_TYPE)))).collect(Collectors.toList()); + else + log.debug("getNodeTemplateBySdcType - SubstitutionMappings' node Templates not exist"); + } else + log.debug("getNodeTemplateBySdcType - SubstitutionMappings not exist"); + + return new ArrayList<>(); + } + + private List getNodeTemplateBySdcType(TopologyTemplate topologyTemplate, String sdcType) { + if (GeneralUtility.isEmptyString(sdcType)) { + log.error("getNodeTemplateBySdcType - sdcType is null or empty"); + return new ArrayList<>(); + } + + if (topologyTemplate == null) { + log.error("getNodeTemplateBySdcType - topologyTemplate is null"); + return new ArrayList<>(); + } + + List nodeTemplates = topologyTemplate.getNodeTemplates(); + + if (nodeTemplates != null && nodeTemplates.size() > 0) + return nodeTemplates.stream().filter(x -> (x.getMetaData() != null && sdcType.equals(x.getMetaData().getValue(SdcPropertyNames.PROPERTY_NAME_TYPE)))).collect(Collectors.toList()); + + log.debug("getNodeTemplateBySdcType - topologyTemplate's nodeTemplates not exist"); + return new ArrayList<>(); + } + + //Assumed to be unique property for the list + private NodeTemplate getNodeTemplateByCustomizationUuid(List nodeTemplates, String customizationId) { + log.debug("getNodeTemplateByCustomizationUuid - nodeTemplates {}, customizationId {}", nodeTemplates, customizationId); + Optional findFirst = nodeTemplates.stream().filter(x -> (x.getMetaData() != null && customizationId.equals(x.getMetaData().getValue(PROPERTY_NAME_CUSTOMIZATIONUUID)))).findFirst(); + return findFirst.isPresent() ? findFirst.get() : null; + } + + private Object processProperties(String[] split, LinkedHashMap properties) { + log.debug("processProperties - the leafValuePath is {} , the properties are {}", Arrays.toString(split), properties.toString()); + Optional> findFirst = properties.entrySet().stream().filter(x -> x.getKey().equals(split[0])).findFirst(); + if (findFirst.isPresent()) { + log.debug("processProperties - find first item is {}", findFirst.get()); + Property property = findFirst.get().getValue(); + Object current = property.getValue(); + return iterateProcessPath(1, current, split); + } + log.error("processProperties - property not found"); + return null; + } } diff --git a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/BasicTest.java b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/BasicTest.java index 373ff97..0eb58f8 100644 --- a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/BasicTest.java +++ b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/BasicTest.java @@ -1,6 +1,7 @@ package org.openecomp.sdc.impl; import java.io.File; +import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; @@ -8,12 +9,13 @@ import java.util.List; import java.util.Map; import org.openecomp.sdc.tosca.parser.api.ISdcCsarHelper; +import org.openecomp.sdc.tosca.parser.exceptions.SdcToscaParserException; import org.openecomp.sdc.tosca.parser.impl.SdcToscaParserFactory; -import org.testng.ITestContext; +import org.openecomp.sdc.toscaparser.api.common.ExceptionCollector; +import org.openecomp.sdc.toscaparser.api.common.JToscaException; import org.testng.annotations.AfterMethod; -import org.testng.annotations.AfterSuite; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.BeforeSuite; public abstract class BasicTest { @@ -22,35 +24,18 @@ public abstract class BasicTest { static ISdcCsarHelper rainyCsarHelperSingleVf; static ISdcCsarHelper rainyCsarHelperMultiVfs; static ISdcCsarHelper fdntCsarHelper; + static ISdcCsarHelper complexCps; static Map>> fdntCsarHelper_Data; - @BeforeSuite - public static void init(ITestContext context) throws Exception { + + @BeforeClass + public static void init() throws SdcToscaParserException, JToscaException, IOException { factory = SdcToscaParserFactory.getInstance(); - long startTime = System.currentTimeMillis(); - long estimatedTime = System.currentTimeMillis() - startTime; - System.out.println("Time to init factory " + estimatedTime); - - String fileStr1 = BasicTest.class.getClassLoader().getResource("csars/service-ServiceFdnt-with-allotted.csar").getFile(); - File file1 = new File(fileStr1); - startTime = System.currentTimeMillis(); - - fdntCsarHelper = factory.getSdcCsarHelper(file1.getAbsolutePath()); - - estimatedTime = System.currentTimeMillis() - startTime; - System.out.println("init CSAR Execution time: " + estimatedTime); - - String fileStr2 = BasicTest.class.getClassLoader().getResource("csars/service-ServiceFdnt-csar-rainy.csar").getFile(); - File file2 = new File(fileStr2); - rainyCsarHelperMultiVfs = factory.getSdcCsarHelper(file2.getAbsolutePath()); - - String fileStr3 = BasicTest.class.getClassLoader().getResource("csars/service-ServiceFdnt-csar.csar").getFile(); - File file3 = new File(fileStr3); - rainyCsarHelperSingleVf = factory.getSdcCsarHelper(file3.getAbsolutePath()); - - /* Objects for QA Validation Tests */ - - fdntCsarHelper_Data = new HashMap>>(){ + fdntCsarHelper = getCsarHelper("csars/service-ServiceFdnt-with-allotted.csar"); + rainyCsarHelperMultiVfs = getCsarHelper("csars/service-ServiceFdnt-csar-rainy.csar"); + rainyCsarHelperSingleVf = getCsarHelper("csars/service-ServiceFdnt-csar.csar"); + complexCps = getCsarHelper("csars/1service-ServiceWithPorts.csar"); + fdntCsarHelper_Data = new HashMap>>(){ { HashMap> FDNT ; @@ -106,18 +91,27 @@ public abstract class BasicTest { "dnt_fw_rhrg.binding_DNT_FW_NIMBUS_HSL_RVMI", "dnt_fw_rsg_si_1.feature")); + put("FDNT", FDNT); } }; }; - @AfterSuite - public static void after(){ - long startTime = System.currentTimeMillis(); - long estimatedTime = System.currentTimeMillis() - startTime; - System.out.println("close Execution time: "+estimatedTime); - }; - + private static ISdcCsarHelper getCsarHelper(String path) throws JToscaException, IOException, SdcToscaParserException { + System.out.println("Parsing CSAR "+path+"..."); + String fileStr1 = BasicTest.class.getClassLoader().getResource(path).getFile(); + File file1 = new File(fileStr1); + ISdcCsarHelper sdcCsarHelper = factory.getSdcCsarHelper(file1.getAbsolutePath()); + List exceptionReport = ExceptionCollector.getCriticalsReport(); + if (!exceptionReport.isEmpty()){ + System.out.println("TOSCA Errors found in CSAR - failing the tests..."); + System.out.println(exceptionReport.toString()); + ExceptionCollector.clear(); + //throw new SdcToscaParserException("CSAR didn't pass validation"); + } + return sdcCsarHelper; + } + @BeforeMethod public void setupTest(Method method) { System.out.println("#### Starting Test " + method.getName() + " ###########"); diff --git a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserGroupTest.java b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserGroupTest.java index 706c864..58e967a 100644 --- a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserGroupTest.java +++ b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserGroupTest.java @@ -5,6 +5,7 @@ import org.openecomp.sdc.tosca.parser.exceptions.SdcToscaParserException; import org.openecomp.sdc.toscaparser.api.Group; import org.openecomp.sdc.toscaparser.api.elements.Metadata; +import java.util.Arrays; import java.util.List; import static org.testng.Assert.*; @@ -96,4 +97,13 @@ public class ToscaParserGroupTest extends BasicTest{ } //endregion + //region getGroupPropertyAsObject + @Test + public void testGetGroupPropertyAsObject() { + List vfModulesByVf = fdntCsarHelper.getVfModulesByVf(VF_CUSTOMIZATION_UUID); + Object volumeGroup = fdntCsarHelper.getGroupPropertyAsObject(vfModulesByVf.get(0), "volume_group"); + assertEquals(false, volumeGroup); + } + //getGroupPropertyAsObject + } diff --git a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserNodeTemplateTest.java b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserNodeTemplateTest.java index 9a78ed5..c9215a2 100644 --- a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserNodeTemplateTest.java +++ b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserNodeTemplateTest.java @@ -6,6 +6,7 @@ import static org.testng.Assert.assertNull; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.tuple.Pair; import org.testng.annotations.Test; @@ -304,4 +305,30 @@ public class ToscaParserNodeTemplateTest extends BasicTest { } //endregion + //region getCpPropertiesFromVfc + @Test + public void testGetCpPropertiesFromVfc() { + List vfcs = complexCps.getVfcListByVf(VF_CUSTOMIZATION_UUID); + Map> cps = complexCps.getCpPropertiesFromVfc(vfcs.get(0)); + + assertEquals("1", cps.get("port_fe1_sigtran").get("ip_requirements#ip_count_required#count")); + assertEquals("true", cps.get("port_fe1_sigtran").get("ip_requirements#dhcp_enabled")); + assertEquals("4", cps.get("port_fe1_sigtran").get("ip_requirements#ip_version")); + + assertEquals("2", cps.get("port_fe_cluster").get("ip_requirements#ip_count_required#count")); + assertEquals("true", cps.get("port_fe_cluster").get("ip_requirements#dhcp_enabled")); + assertEquals("4", cps.get("port_fe_cluster").get("ip_requirements#ip_version")); + } + //endregion + + //region getNodeTemplatePropertyAsObject + @Test + public void testGetNodeTemplatePropertyAsObject() { + List serviceVfList = fdntCsarHelper.getServiceVfList(); + assertEquals("2", fdntCsarHelper.getNodeTemplatePropertyAsObject(serviceVfList.get(0), "availability_zone_max_count")); + assertEquals(3, fdntCsarHelper.getNodeTemplatePropertyAsObject(serviceVfList.get(0), "max_instances")); + assertEquals("some code", fdntCsarHelper.getNodeTemplatePropertyAsObject(serviceVfList.get(0), "nf_naming_code")); + } + //endregion + } diff --git a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserServiceInputTest.java b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserServiceInputTest.java index d357d21..0599dcc 100644 --- a/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserServiceInputTest.java +++ b/sdc-tosca-parser/src/test/java/org/openecomp/sdc/impl/ToscaParserServiceInputTest.java @@ -47,4 +47,11 @@ public class ToscaParserServiceInputTest extends BasicTest { } //endregion + //region getServiceInputLeafValueOfDefaultAsObject + @Test + public void testGetServiceInputLeafValueOfDefaultAsObject() { + Object serviceInputLeafValue = fdntCsarHelper.getServiceInputLeafValueOfDefault("service_naming#default"); + assertEquals("test service naming", serviceInputLeafValue); + } + //endregion } diff --git a/sdc-tosca-parser/src/test/resources/csars/1service-ServiceWithPorts.csar b/sdc-tosca-parser/src/test/resources/csars/1service-ServiceWithPorts.csar new file mode 100644 index 0000000000000000000000000000000000000000..fa6577b5cfb310e195ecc586f5a38cbc1ad8dab1 GIT binary patch literal 47257 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 literal 0 HcmV?d00001 -- 2.16.6