diff options
author | mpriyank <priyank.maheshwari@est.tech> | 2022-08-26 13:26:01 +0100 |
---|---|---|
committer | mpriyank <priyank.maheshwari@est.tech> | 2022-08-29 15:15:54 +0100 |
commit | 91d66108379d7cd397aedc30da4801d20643ee68 (patch) | |
tree | 63c73329d3478b641b479d56fb31e8d2e10c31be | |
parent | bf1fdbdeed0ba6f0e8420d15ab33d6c88a5ee4d5 (diff) |
Performance Improvement:save cmhandles capability
- 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>
8 files changed, 106 insertions, 2 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java index c059ece0d3..7a7ef66668 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java @@ -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; @@ -157,6 +159,19 @@ public class InventoryPersistence { } /** + * 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. * * @param listElementXpath list element xPath diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy index 7ffec1ab09..76f10de831 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy @@ -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') diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index c4a2c2fe98..61e1d5b569 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -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); diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index fee489d18b..acc243b5b4 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -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 } diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java index decf67d24e..b2e8c5ba42 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java @@ -69,6 +69,19 @@ public interface CpsDataService { 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. * * @param dataspaceName dataspace name diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index 092fd31fcf..6bf493556e 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -92,6 +92,17 @@ public class CpsDataServiceImpl implements CpsDataService { } @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) { CpsValidator.validateNameCharacters(dataspaceName, anchorName); @@ -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 { diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index 686f0f3fee..8b45ae78d9 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -66,6 +66,17 @@ public interface CpsDataPersistenceService { 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. * * @param dataspaceName dataspace name diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index cb352bccec..ab960df6aa 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -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') |