Performance Improvement:save cmhandles capability 54/130454/5
authormpriyank <priyank.maheshwari@est.tech>
Fri, 26 Aug 2022 12:26:01 +0000 (13:26 +0100)
committermpriyank <priyank.maheshwari@est.tech>
Mon, 29 Aug 2022 14:15:54 +0000 (15:15 +0100)
- add saveCmHandleBatch in InventoryPersistence
- add saveListElementsBatch in CpsDataService
- have addListElementsBatch in CpsDataPersistenceService
- Test scenarios for the same

Issue-ID: CPS-1229
Issue-ID: CPS-1126
Change-Id: I0a1401818da5a4e523d7d0751cac6a526d1611b2
Signed-off-by: mpriyank <priyank.maheshwari@est.tech>
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy

index c059ece..7a7ef66 100644 (file)
@@ -27,8 +27,10 @@ import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
 
 import java.time.OffsetDateTime;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -156,6 +158,19 @@ public class InventoryPersistence {
                 cmHandleJsonData, NO_TIMESTAMP);
     }
 
+    /**
+     * Method to save batch of cm handles.
+     *
+     * @param yangModelCmHandles cm handle represented as Yang Models
+     */
+    public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) {
+        final List<String> cmHandlesJsonData = new ArrayList<>();
+        yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add(
+                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle))));
+        cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP);
+    }
+
     /**
      * Method to delete a list or a list element.
      *
index 7ffec1a..76f10de 100644 (file)
@@ -202,6 +202,22 @@ class InventoryPersistenceSpec extends Specification {
             }
     }
 
+    def 'Save Multiple Cmhandles'() {
+        given: 'cm handles represented as Yang Model'
+            def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
+            def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
+        when: 'the cm handles are saved'
+            objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
+        then: 'CPS Data Service persists both cm handles as a batch'
+            1 * mockCpsDataService.saveListElementsBatch('NCMP-Admin','ncmp-dmi-registry','/dmi-registry',_,null) >> {
+                args -> {
+                    def jsonDataList = (args[3] as List)
+                    (jsonDataList[0] as String).contains('cmhandle1')
+                    (jsonDataList[0] as String).contains('cmhandle2')
+                }
+            }
+    }
+
     def 'Delete list or list elements'() {
         when: 'the method to delete list or list elements is called'
             objectUnderTest.deleteListOrListElement('sample xPath')
index c4a2c2f..61e1d5b 100644 (file)
@@ -101,6 +101,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
     }
 
+    @Override
+    @Transactional
+    public void addListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+            final Collection<Collection<DataNode>> newListsElements) {
+
+        newListsElements.forEach(
+                newListElement -> addListElements(dataspaceName, anchorName, parentNodeXpath, newListElement));
+
+    }
+
     private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                    final Collection<DataNode> newChildren) {
         final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
index fee489d..acc243b 100755 (executable)
@@ -157,7 +157,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Add multiple new list elements including an element with a child datanode.'() {
+    def 'Add collection of multiple new list elements including an element with a child datanode.'() {
         given: 'two new child list elements for an existing parent'
             def listElementXpaths = ['/parent-201/child-204[@key="NEW1"]', '/parent-201/child-204[@key="NEW2"]']
             def listElements = toDataNodes(listElementXpaths)
@@ -165,7 +165,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             def grandChild = buildDataNode('/parent-201/child-204[@key="NEW1"]/grand-child-204[@key2="NEW1-CHILD"]', [leave:'value'], [])
             listElements[0].childDataNodes = [grandChild]
         when: 'the new data node (list elements) are added to an existing parent node'
-            objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listElements)
+            objectUnderTest.addListElementsBatch(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
         then: 'new entries are successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
             def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
             def allChildXpaths = parentFragment.childFragments.collect { it.xpath }
index decf67d..b2e8c5b 100644 (file)
@@ -68,6 +68,19 @@ public interface CpsDataService {
     void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData,
         OffsetDateTime observedTimestamp);
 
+    /**
+     * Persists child data fragment representing one or more list elements under existing data node for the
+     * given anchor and dataspace.
+     *
+     * @param dataspaceName     dataspace name
+     * @param anchorName        anchor name
+     * @param parentNodeXpath   parent node xpath
+     * @param jsonDataList      collection of json data representing list element(s)
+     * @param observedTimestamp observedTimestamp
+     */
+    void saveListElementsBatch(String dataspaceName, String anchorName, String parentNodeXpath,
+            Collection<String> jsonDataList, OffsetDateTime observedTimestamp);
+
     /**
      * Retrieves datanode by XPath for given dataspace and anchor.
      *
index 092fd31..6bf4935 100755 (executable)
@@ -91,6 +91,17 @@ public class CpsDataServiceImpl implements CpsDataService {
         processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
     }
 
+    @Override
+    public void saveListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+            final Collection<String> jsonDataList, final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
+        final Collection<Collection<DataNode>> listElementDataNodeCollections =
+                buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList);
+        cpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, parentNodeXpath,
+                listElementDataNodeCollections);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
+    }
+
     @Override
     public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
         final FetchDescendantsOption fetchDescendantsOption) {
@@ -252,6 +263,13 @@ public class CpsDataServiceImpl implements CpsDataService {
 
     }
 
+    private Collection<Collection<DataNode>> buildDataNodes(final String dataspaceName, final String anchorName,
+            final String parentNodeXpath, final Collection<String> jsonDataList) {
+        return jsonDataList.stream()
+                .map(jsonData -> buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData))
+                .collect(Collectors.toList());
+    }
+
     private void processDataUpdatedEventAsync(final String dataspaceName, final String anchorName, final String xpath,
             final Operation operation, final OffsetDateTime observedTimestamp) {
         try {
index 686f0f3..8b45ae7 100644 (file)
@@ -65,6 +65,17 @@ public interface CpsDataPersistenceService {
     void addListElements(String dataspaceName, String anchorName, String parentNodeXpath,
         Collection<DataNode> listElementsCollection);
 
+    /**
+     * Adds list child elements to a Fragment.
+     *
+     * @param dataspaceName           dataspace name
+     * @param anchorName              anchor name
+     * @param parentNodeXpath         parent node xpath
+     * @param listElementsCollections collections of data nodes representing list elements
+     */
+    void addListElementsBatch(String dataspaceName, String anchorName, String parentNodeXpath,
+            Collection<Collection<DataNode>> listElementsCollections);
+
     /**
      * Retrieves datanode by XPath for given dataspace and anchor.
      *
index cb352bc..ab960df 100644 (file)
@@ -37,6 +37,7 @@ import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import spock.lang.Specification
 
 import java.time.OffsetDateTime
+import java.util.stream.Collectors
 
 class CpsDataServiceImplSpec extends Specification {
     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
@@ -135,6 +136,26 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
     }
 
+    def 'Saving collection of a batch with data fragment under existing node.'() {
+        given: 'schema set for given anchor and dataspace references test-tree model'
+            setupSchemaSetMocks('test-tree.yang')
+        when: 'save data method is invoked with list element json data'
+            def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
+            objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
+        then: 'the persistence service method is invoked with correct parameters'
+            1 * mockCpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, '/test-tree',_) >> {
+                args -> {
+                    def listElementsCollection = args[3] as Collection<Collection<DataNode>>
+                    assert listElementsCollection.size() == 1
+                    def listOfXpaths = listElementsCollection.stream().flatMap(x -> x.stream()).map(it-> it.xpath).collect(Collectors.toList())
+                    assert listOfXpaths.size() == 2
+                    assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']'])
+                }
+            }
+        and: 'data updated event is sent to notification service'
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
+    }
+
     def 'Saving empty list element data fragment.'() {
         given: 'schema set for given anchor and dataspace references test-tree model'
             setupSchemaSetMocks('test-tree.yang')