Use getDataNodes (plural version) into NCMP to get CM handles 19/133119/10
authorsourabh_sourabh <sourabh.sourabh@est.tech>
Thu, 2 Feb 2023 11:22:20 +0000 (11:22 +0000)
committerSourabh Sourabh <sourabh.sourabh@est.tech>
Wed, 8 Feb 2023 17:09:23 +0000 (17:09 +0000)
- Use plural getYangModelCmHandles get YangModelCmHandles by CmHandles in batches.

Issue-ID: CPS-1426

Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
Change-Id: Ie175b471bd98b02b572ca4a3ff699d721b5b782a
Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy

index 95ff48a..f8e0659 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.api.impl.utils;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.inventory.CompositeState;
 import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder;
@@ -32,8 +37,11 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.model.DataNode;
 
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
+@Slf4j
 public class YangDataConverter {
 
+    private static final Pattern cmHandleIdInXpathPattern = Pattern.compile("\\[@id='(.*?)']");
+
     /**
      * This method convert yang model cm handle to ncmp service cm handle.
      * @param yangModelCmHandle the yang model of the cm handle
@@ -78,10 +86,72 @@ public class YangDataConverter {
                 (String) cmHandleDataNode.getLeaves().get("dmi-service-name"),
                 (String) cmHandleDataNode.getLeaves().get("dmi-data-service-name"),
                 (String) cmHandleDataNode.getLeaves().get("dmi-model-service-name"),
-            ncmpServiceCmHandle
+                ncmpServiceCmHandle
+        );
+    }
+
+    /**
+     * This method convert cm handle data node to yang model cm handle without using NcmpServiceCmHandle.
+     *
+     * @param cmHandleDataNode the datanode of the cm handle
+     * @param cmHandleId       the id of the cm handle
+     * @return yang model cm handle
+     */
+    public static YangModelCmHandle convertCmHandleToYangModelWithoutNcmpServiceCmHandle(
+            final DataNode cmHandleDataNode,
+            final String cmHandleId) {
+        final Map<String, String> dmiProperties = new LinkedHashMap<>();
+        final Map<String, String> publicProperties = new LinkedHashMap<>();
+        final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder();
+        CompositeState compositeState = compositeStateBuilder.build();
+        for (final DataNode childDataNode : cmHandleDataNode.getChildDataNodes()) {
+            if (childDataNode.getXpath().contains("/additional-properties[@name=")) {
+                addProperty(childDataNode, dmiProperties);
+            } else if (childDataNode.getXpath().contains("/public-properties[@name=")) {
+                addProperty(childDataNode, publicProperties);
+            } else if (childDataNode.getXpath().endsWith("/state")) {
+                compositeState = compositeStateBuilder.fromDataNode(childDataNode).build();
+            }
+        }
+        return YangModelCmHandle.toYangModelCmHandleWithoutNcmpServiceHandle(
+                (String) cmHandleDataNode.getLeaves().get("dmi-service-name"),
+                (String) cmHandleDataNode.getLeaves().get("dmi-data-service-name"),
+                (String) cmHandleDataNode.getLeaves().get("dmi-model-service-name"),
+                cmHandleId,
+                dmiProperties,
+                publicProperties,
+                compositeState
         );
     }
 
+    /**
+     * This method convert cm handle data nodes to yang model cm handles.
+     * @param cmHandleDataNodes the datanode of the cm handle
+     * @return yang model cm handles
+     */
+    public static Collection<YangModelCmHandle> convertDataNodesToYangModelCmHandles(
+            final Collection<DataNode> cmHandleDataNodes) {
+        final Collection<YangModelCmHandle> yangModelCmHandles = new ArrayList<>(cmHandleDataNodes.size());
+        cmHandleDataNodes.forEach(dataNode -> {
+            final String cmHandleId = extractCmHandleIdFromXpath(dataNode.getXpath());
+            if (cmHandleId != null) {
+                yangModelCmHandles.add(convertCmHandleToYangModelWithoutNcmpServiceCmHandle(dataNode, cmHandleId));
+            }
+        });
+        return yangModelCmHandles;
+    }
+
+    private static String extractCmHandleIdFromXpath(final String xpath) {
+        final Matcher matcher = cmHandleIdInXpathPattern.matcher(xpath);
+        if (matcher.find()) {
+            return matcher.group(1);
+        } else {
+            log.error("Unexpected xpath {}", xpath);
+        }
+        return null;
+    }
+
+
     private static void populateCmHandleDetails(final DataNode cmHandleDataNode,
                                                 final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final Map<String, String> dmiProperties = new LinkedHashMap<>();
@@ -108,14 +178,14 @@ public class YangDataConverter {
     }
 
     private static void setDmiProperties(final List<YangModelCmHandle.Property> dmiProperties,
-                                  final NcmpServiceCmHandle ncmpServiceCmHandle) {
+                                         final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final Map<String, String> dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size());
         asPropertiesMap(dmiProperties, dmiPropertiesMap);
         ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap);
     }
 
     private static void setPublicProperties(final List<YangModelCmHandle.Property> publicProperties,
-                                     final NcmpServiceCmHandle ncmpServiceCmHandle) {
+                                            final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final Map<String, String> publicPropertiesMap = new LinkedHashMap<>();
         asPropertiesMap(publicProperties, publicPropertiesMap);
         ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap);
index aabc52c..5f98f14 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -115,6 +115,38 @@ public class YangModelCmHandle {
         return yangModelCmHandle;
     }
 
+
+    /**
+     * Create a yangModelCmHandle without the use of NcmpServiceHandle.
+     *
+     * @param dmiServiceName      dmi service name
+     * @param dmiDataServiceName  dmi data service name
+     * @param dmiModelServiceName dmi model service name
+     * @param cmHandleId          the cm handle id
+     * @param dmiProperties       dmiProperties
+     * @param publicProperties    publicProperties
+     * @param compositeState      compositeState
+     * @return instance of yangModelCmHandle
+     */
+    public static YangModelCmHandle toYangModelCmHandleWithoutNcmpServiceHandle(final String dmiServiceName,
+                                                        final String dmiDataServiceName,
+                                                        final String dmiModelServiceName,
+                                                        final String cmHandleId,
+                                                        final Map<String, String> dmiProperties,
+                                                        final Map<String, String> publicProperties,
+                                                        final CompositeState compositeState) {
+        final YangModelCmHandle yangModelCmHandle = new YangModelCmHandle();
+        yangModelCmHandle.setId(cmHandleId);
+        yangModelCmHandle.setDmiServiceName(dmiServiceName);
+        yangModelCmHandle.setDmiDataServiceName(dmiDataServiceName);
+        yangModelCmHandle.setDmiModelServiceName(dmiModelServiceName);
+        yangModelCmHandle.setDmiProperties(asYangModelCmHandleProperties(dmiProperties));
+        yangModelCmHandle.setPublicProperties(asYangModelCmHandleProperties(publicProperties));
+        yangModelCmHandle.setCompositeState(compositeState);
+        return yangModelCmHandle;
+    }
+
+
     /**
      * Resolve a dmi service name.
      *
index 55442ec..10227cf 100644 (file)
@@ -61,6 +61,14 @@ public interface InventoryPersistence {
      */
     YangModelCmHandle getYangModelCmHandle(String cmHandleId);
 
+    /**
+     * This method retrieves DMI service name, DMI properties and the state for a given cm handle.
+     *
+     * @param cmHandleIds a list of the ids of the cm handles
+     * @return collection of yang model cm handles
+     */
+    Collection<YangModelCmHandle> getYangModelCmHandles(Collection<String> cmHandleIds);
+
     /**
      * Method to return module definitions by cmHandleId.
      *
@@ -122,6 +130,23 @@ public interface InventoryPersistence {
      */
     DataNode getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption);
 
+    /**
+     * Get collection of data nodes via xpaths.
+     *
+     * @param xpaths collection of xpaths
+     * @return collection of data nodes
+     */
+    Collection<DataNode> getDataNodes(Collection<String> xpaths);
+
+    /**
+     * Get collection of data nodes via xpaths.
+     *
+     * @param xpaths collection of xpaths
+     * @param fetchDescendantsOption fetch descendants option
+     * @return collection of data nodes
+     */
+    Collection<DataNode> getDataNodes(Collection<String> xpaths, FetchDescendantsOption fetchDescendantsOption);
+
     /**
      * Get data node of given cm handle.
      *
@@ -130,6 +155,14 @@ public interface InventoryPersistence {
      */
     DataNode getCmHandleDataNode(String cmHandleId);
 
+    /**
+     * Get collection of data nodes of given cm handles.
+     *
+     * @param cmHandleIds collection of cmHandle IDs
+     * @return collection of data nodes
+     */
+    Collection<DataNode> getCmHandleDataNodes(Collection<String> cmHandleIds);
+
     /**
      * get CM handles that has given module names.
      *
index 29712f4..c2578cc 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,6 +33,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsAdminService;
@@ -41,6 +42,7 @@ import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
@@ -75,8 +77,8 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     @Override
     public CompositeState getCmHandleState(final String cmHandleId) {
         final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
-            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
+                FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
         cpsValidator.validateNameCharacters(cmHandleId);
         return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
     }
@@ -106,6 +108,21 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
         return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
     }
 
+    @Override
+    public Collection<YangModelCmHandle> getYangModelCmHandles(final Collection<String> cmHandleIds) {
+        final Collection<String> validCmHandleIds = new ArrayList<>();
+        cmHandleIds.forEach(cmHandleId -> {
+            try {
+                cpsValidator.validateNameCharacters(cmHandleId);
+                validCmHandleIds.add(cmHandleId);
+            } catch (final DataValidationException dataValidationException) {
+                log.error("DataValidationException in CmHandleId {} to be ignored",
+                        dataValidationException.getMessage());
+            }
+        });
+        return YangDataConverter.convertDataNodesToYangModelCmHandles(getCmHandleDataNodes(validCmHandleIds));
+    }
+
     @Override
     public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
         return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
@@ -142,7 +159,7 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
 
     @Override
     @Timed(value = "cps.ncmp.inventory.persistence.schemaset.delete",
-        description = "Time taken to delete a schemaset")
+            description = "Time taken to delete a schemaset")
     public void deleteSchemaSetWithCascade(final String schemaSetName) {
         try {
             cpsValidator.validateNameCharacters(schemaSetName);
@@ -154,23 +171,45 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     }
 
     @Override
+    @Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
+            description = "Time taken to get a data node (from ncmp dmi registry)")
     public DataNode getDataNode(final String xpath) {
         return getDataNode(xpath, INCLUDE_ALL_DESCENDANTS);
     }
 
     @Override
     @Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
-        description = "Time taken to get a data node (from ncmp dmi registry)")
+            description = "Time taken to get a data node (from ncmp dmi registry)")
     public DataNode getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
         return cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 xpath, fetchDescendantsOption);
     }
 
+    @Override
+    public Collection<DataNode> getDataNodes(final Collection<String> xpaths) {
+        return getDataNodes(xpaths, INCLUDE_ALL_DESCENDANTS);
+    }
+
+    @Override
+    public Collection<DataNode> getDataNodes(final Collection<String> xpaths,
+                                             final FetchDescendantsOption fetchDescendantsOption) {
+        return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                xpaths, fetchDescendantsOption);
+    }
+
     @Override
     public DataNode getCmHandleDataNode(final String cmHandleId) {
         return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId));
     }
 
+    @Override
+    public Collection<DataNode> getCmHandleDataNodes(final Collection<String> cmHandleIds) {
+        final Collection<String> xpaths = cmHandleIds.stream().map(cmHandleId ->
+                        String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId))
+                .collect(Collectors.toList());
+        return this.getDataNodes(xpaths);
+    }
+
     @Override
     public Collection<String> getCmHandleIdsWithGivenModules(final Collection<String> moduleNamesForQuery) {
         return cpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
@@ -191,4 +230,4 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     public void deleteDataNodes(final Collection<String> dataNodeXpaths) {
         cpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpaths, NO_TIMESTAMP);
     }
-}
+}
\ No newline at end of file
index 3ef3df5..dd673eb 100644 (file)
@@ -38,4 +38,18 @@ class YangDataConverterSpec extends Specification{
             assert yangModelCmHandle.dmiProperties[0].name == 'dmiProp1'
             assert yangModelCmHandle.dmiProperties[0].value == 'dmiValue1'
     }
+
+    def 'Convert multiple cm handle data nodes'(){
+        given: 'two data nodes in a collection one with private properties'
+            def dataNodeAdditionalProperties = new DataNode(xpath:'/additional-properties[@name="dmiProp1"]',
+                    leaves: ['name': 'dmiProp1', 'value': 'dmiValue1'])
+            def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']'),
+                             new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']', childDataNodes:[dataNodeAdditionalProperties])]
+        when: 'the data nodes are converted'
+            def yangModelCmHandles = YangDataConverter.convertDataNodesToYangModelCmHandles(dataNodes)
+        then: 'verify both have returned and cmhandleIds are correct'
+            assert yangModelCmHandles.size() == 2
+            assert yangModelCmHandles.id.containsAll(['some-cm-handle', 'another-cm-handle'])
+    }
+
 }
index 2ca0e99..93e79f6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,7 @@ import org.onap.cps.api.CpsModuleService
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.spi.CascadeDeleteAllowed
 import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
@@ -38,6 +39,7 @@ import spock.lang.Specification
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
+import java.util.stream.Collectors
 
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
@@ -64,6 +66,9 @@ class InventoryPersistenceImplSpec extends Specification {
     def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
 
+    def cmHandleId2 = 'another-cm-handle'
+    def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']"
+
     @Shared
     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
@@ -118,6 +123,26 @@ class InventoryPersistenceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
     }
 
+    def "Retrieve multiple YangModelCmHandles"() {
+        given: 'the cps data service returns 2 data nodes from the DMI registry'
+            def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)]
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
+        when: 'retrieving the yang modelled cm handle'
+            def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
+        then: 'verify both have returned and cmhandleIds are correct'
+            assert results.size() == 2
+            assert results.id.containsAll([cmHandleId, cmHandleId2])
+    }
+
+    def "Handling name validation errors in getYangModelCmHandles."() {
+        given: 'the cps data service returns one of two data nodes from the DMI registry with empty leaf attributes'
+            mockCpsValidator.validateNameCharacters(cmHandleId) >> {throw new DataValidationException('some message', 'some detail')}
+        when:
+            objectUnderTest.getYangModelCmHandle(cmHandleId)
+        then: 'exception is thrown'
+            thrown(DataValidationException)
+    }
+
     def 'Get a Cm Handle Composite State'() {
         given: 'a valid cm handle id'
             def cmHandleId = 'Some-Cm-Handle'