diff options
Diffstat (limited to 'cps-ri')
10 files changed, 85 insertions, 653 deletions
diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml index ea1efcb3e9..3ef57cf03a 100644 --- a/cps-ri/pom.xml +++ b/cps-ri/pom.xml @@ -33,7 +33,8 @@ <artifactId>cps-ri</artifactId>
<properties>
- <minimum-coverage>0.79</minimum-coverage>
+ <minimum-coverage>0.45</minimum-coverage>
+ <!-- Coverage is provided by integration-test module instead -->
</properties>
<dependencies>
@@ -147,18 +148,10 @@ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>%regex[.*PerfTest.*]</exclude>
- </excludes>
- </configuration>
</plugin>
</plugins>
</build>
</profile>
- <profile>
- <id>include-performance</id>
- </profile>
</profiles>
<build>
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java index 49e2dd2530..b7ce98e1ac 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,9 +41,11 @@ public class FragmentEntityArranger { public static Collection<FragmentEntity> toFragmentEntityTrees(final AnchorEntity anchorEntity, final Collection<FragmentExtract> fragmentExtracts) { final Map<Long, FragmentEntity> fragmentEntityPerId = new HashMap<>(); - for (final FragmentExtract fragmentExtract : fragmentExtracts) { - final FragmentEntity fragmentEntity = toFragmentEntity(anchorEntity, fragmentExtract); - fragmentEntityPerId.put(fragmentEntity.getId(), fragmentEntity); + if (fragmentExtracts != null) { + for (final FragmentExtract fragmentExtract : fragmentExtracts) { + final FragmentEntity fragmentEntity = toFragmentEntity(anchorEntity, fragmentExtract); + fragmentEntityPerId.put(fragmentEntity.getId(), fragmentEntity); + } } return reuniteChildrenWithTheirParents(fragmentEntityPerId); } 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 c26cd2fea8..d0154e1163 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 @@ -87,13 +87,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private static final AnchorEntity ALL_ANCHORS = null; @Override - public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final DataNode newChildDataNode) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - addNewChildDataNode(anchorEntity, parentNodeXpath, newChildDataNode); - } - - @Override public void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection<DataNode> dataNodes) { final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); @@ -450,14 +443,25 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } @Override - public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath, - final Map<String, Serializable> updateLeaves) { + public void batchUpdateDataLeaves(final String dataspaceName, final String anchorName, + final Map<String, Map<String, Serializable>> updatedLeavesPerXPath) { final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, xpath); - final String currentLeavesAsString = fragmentEntity.getAttributes(); - final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString); - fragmentEntity.setAttributes(mergedLeaves); - fragmentRepository.save(fragmentEntity); + + final Collection<String> xpathsOfUpdatedLeaves = updatedLeavesPerXPath.keySet(); + final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpathsOfUpdatedLeaves, + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); + + for (final FragmentEntity fragmentEntity : fragmentEntities) { + final Map<String, Serializable> updatedLeaves = updatedLeavesPerXPath.get(fragmentEntity.getXpath()); + final String mergedLeaves = mergeLeaves(updatedLeaves, fragmentEntity.getAttributes()); + fragmentEntity.setAttributes(mergedLeaves); + } + + try { + fragmentRepository.saveAll(fragmentEntities); + } catch (final StaleStateException staleStateException) { + retryUpdateDataNodesIndividually(anchorEntity, fragmentEntities); + } } @Override @@ -687,9 +691,13 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private String mergeLeaves(final Map<String, Serializable> updateLeaves, final String currentLeavesAsString) { - final Map<String, Serializable> currentLeavesAsMap = currentLeavesAsString.isEmpty() - ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class); - currentLeavesAsMap.putAll(updateLeaves); + Map<String, Serializable> currentLeavesAsMap = new HashMap<>(); + if (currentLeavesAsString != null) { + currentLeavesAsMap = currentLeavesAsString.isEmpty() + ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class); + currentLeavesAsMap.putAll(updateLeaves); + } + if (currentLeavesAsMap.isEmpty()) { return ""; } 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 67ccc805ae..6d6dfd270a 100755..100644 --- 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 @@ -320,36 +320,6 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data node leaves.'() { - when: 'update is performed for leaves' - objectUnderTest.updateDataLeaves(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, - '/parent-200/child-201', ['leaf-value': 'new']) - then: 'leaves are updated for selected data node' - def updatedFragment = fragmentRepository.getReferenceById(DATA_NODE_202_FRAGMENT_ID) - def updatedLeaves = getLeavesMap(updatedFragment) - assert updatedLeaves.size() == 1 - assert updatedLeaves.'leaf-value' == 'new' - and: 'existing child entry remains as is' - def childFragment = updatedFragment.childFragments.iterator().next() - def childLeaves = getLeavesMap(childFragment) - assert childFragment.id == CHILD_OF_DATA_NODE_202_FRAGMENT_ID - assert childLeaves.'leaf-value' == 'original' - } - - @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data leaves error scenario: #scenario.'() { - when: 'attempt to update data node for #scenario' - objectUnderTest.updateDataLeaves(dataspaceName, anchorName, xpath, ['leaf-name': 'leaf-value']) - then: 'a #expectedException is thrown' - thrown(expectedException) - where: 'the following data is used' - scenario | dataspaceName | anchorName | xpath || expectedException - 'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' | '/not relevant' || DataspaceNotFoundException - 'non-existing anchor' | DATASPACE_NAME | 'NO ANCHOR' | '/not relevant' || AnchorNotFoundException - 'non-existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NON-EXISTING-XPATH' || DataNodeNotFoundException - } - - @Sql([CLEAR_DATA, SET_DATA]) def 'Update data nodes and descendants by removing descendants.'() { given: 'data nodes with leaves updated, no children' def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [])] @@ -592,7 +562,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { given: 'a data nodes with list-element child with "/" in index value (and grandchild)' def grandChild = new DataNodeBuilder().withXpath(deleteTestGrandChildXPath).build() def child = new DataNodeBuilder().withXpath(deleteTestChildXpath).withChildDataNodes([grandChild]).build() - objectUnderTest.addChildDataNode(DATASPACE_NAME, ANCHOR_NAME3, deleteTestParentXPath, child) + objectUnderTest.addChildDataNodes(DATASPACE_NAME, ANCHOR_NAME3, deleteTestParentXPath, [child]) and: 'number of children before delete is stored' def numberOfChildrenBeforeDelete = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS)[0].childDataNodes.size() when: 'target node is deleted' diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index 8a5838827a..e8921b3ed0 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -38,6 +38,7 @@ import org.onap.cps.spi.utils.SessionManager import org.onap.cps.utils.JsonObjectMapper import org.springframework.dao.DataIntegrityViolationException import spock.lang.Specification +import java.util.stream.Collectors class CpsDataPersistenceServiceSpec extends Specification { @@ -68,6 +69,53 @@ class CpsDataPersistenceServiceSpec extends Specification { 2 * mockFragmentRepository.save(_) } + def 'Handling of StaleStateException (caused by concurrent updates) during patch operation for data nodes.'() { + given: 'the system can update one datanode and has two more datanodes that throw an exception while updating' + def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([ + '/node1': 'OK', + '/node2': 'EXCEPTION', + '/node3': 'EXCEPTION']) + def updatedLeavesPerXPath = dataNodes.stream() + .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves)) + and: 'the batch update will therefore also fail' + mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") } + when: 'attempt batch update data nodes' + objectUnderTest.batchUpdateDataLeaves('some-dataspace', 'some-anchor', updatedLeavesPerXPath) + then: 'concurrency exception is thrown' + def thrown = thrown(ConcurrencyException) + assert thrown.message == 'Concurrent Transactions' + and: 'it does not contain the successful datanode' + assert !thrown.details.contains('/node1') + and: 'it contains the failed datanodes' + assert thrown.details.contains('/node2') + assert thrown.details.contains('/node3') + } + + def 'Batch update data node leaves and descendants: #scenario'(){ + given: 'the fragment repository returns fragment entities related to the xpath inputs' + mockFragmentRepository.findExtractsWithDescendants(_, [] as Set, _) >> [] + mockFragmentRepository.findExtractsWithDescendants(_, ['/test/xpath'] as Set, _) >> [ + mockFragmentExtract(1, null, 123, '/test/xpath', "{\"id\":\"testId1\"}") + ] + mockFragmentRepository.findExtractsWithDescendants(123, ['/test/xpath1', '/test/xpath2'] as Set, _) >> [ + mockFragmentExtract(1, null, 123, '/test/xpath1', "{\"id\":\"testId1\"}"), + mockFragmentExtract(2, null, 123, '/test/xpath2', "{\"id\":\"testId1\"}") + ] + when: 'replace data node tree' + objectUnderTest.batchUpdateDataLeaves('dataspaceName', 'anchorName', + dataNodes.stream().collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves))) + then: 'call fragment repository save all method' + 1 * mockFragmentRepository.saveAll({fragmentEntities -> + assert fragmentEntities as List == expectedFragmentEntities + assert fragmentEntities.size() == expectedSize + }) + where: 'the following Data Type is passed' + scenario | dataNodes | expectedSize || expectedFragmentEntities + 'empty data node list' | [] | 0 || [] + 'one data node in list' | [new DataNode(xpath: '/test/xpath', leaves: ['id': 'testId'])] | 1 || [new FragmentEntity(xpath: '/test/xpath', attributes: '{"id":"testId"}', anchor: anchorEntity)] + 'multiple data nodes' | [new DataNode(xpath: '/test/xpath1', leaves: ['id': 'newTestId1']), new DataNode(xpath: '/test/xpath2', leaves: ['id': 'newTestId2'])] | 2 || [new FragmentEntity(xpath: '/test/xpath2', attributes: '{"id":"newTestId2"}', anchor: anchorEntity), new FragmentEntity(xpath: '/test/xpath1', attributes: '{"id":"newTestId1"}', anchor: anchorEntity)] + } + def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() { given: 'the system can update one datanode and has two more datanodes that throw an exception while updating' def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([ @@ -81,7 +129,7 @@ class CpsDataPersistenceServiceSpec extends Specification { then: 'concurrency exception is thrown' def thrown = thrown(ConcurrencyException) assert thrown.message == 'Concurrent Transactions' - and: 'it does not contain the successfull datanode' + and: 'it does not contain the successful datanode' assert !thrown.details.contains('/node1') and: 'it contains the failed datanodes' assert thrown.details.contains('/node2') @@ -157,26 +205,7 @@ class CpsDataPersistenceServiceSpec extends Specification { 1 * mockSessionManager.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L) } - def 'update data node leaves: #scenario'(){ - given: 'A node exists for the given xpath' - mockFragmentRepository.getByAnchorAndXpath(_, '/some/xpath') >> new FragmentEntity(xpath: '/some/xpath', attributes: existingAttributes) - when: 'the node leaves are updated' - objectUnderTest.updateDataLeaves('some-dataspace', 'some-anchor', '/some/xpath', newAttributes as Map<String, Serializable>) - then: 'the fragment entity saved has the original and new attributes' - 1 * mockFragmentRepository.save({fragmentEntity -> { - assert fragmentEntity.getXpath() == '/some/xpath' - assert fragmentEntity.getAttributes() == mergedAttributes - }}) - where: 'the following attributes combinations are used' - scenario | existingAttributes | newAttributes | mergedAttributes - 'add new leaf' | '{"existing":"value"}' | ["new":"value"] | '{"existing":"value","new":"value"}' - 'update existing leaf' | '{"existing":"value"}' | ["existing":"value2"] | '{"existing":"value2"}' - 'update nothing with nothing' | '' | [] | '' - 'update with nothing' | '{"existing":"value"}' | [] | '{"existing":"value"}' - 'update with same value' | '{"existing":"value"}' | ["existing":"value"] | '{"existing":"value"}' - } - - def 'update data node and descendants: #scenario'(){ + def 'Replace data node and descendants: #scenario'(){ given: 'the fragment repository returns fragment entities related to the xpath inputs' mockFragmentRepository.findExtractsWithDescendants(_, [] as Set, _) >> [] mockFragmentRepository.findExtractsWithDescendants(_, ['/test/xpath'] as Set, _) >> [ @@ -192,7 +221,7 @@ class CpsDataPersistenceServiceSpec extends Specification { 'one data node in list' | [new DataNode(xpath: '/test/xpath', leaves: ['id': 'testId'], childDataNodes: [])] || [new FragmentEntity(xpath: '/test/xpath', attributes: '{"id":"testId"}', anchor: anchorEntity, childFragments: [])] } - def 'update data nodes and descendants'() { + def 'Replace data nodes and descendants'() { given: 'the fragment repository returns fragment entities related to the xpath inputs' mockFragmentRepository.findExtractsWithDescendants(_, ['/test/xpath1', '/test/xpath2'] as Set, _) >> [ mockFragmentExtract(1, null, 123, '/test/xpath1', null), diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy deleted file mode 100644 index daa774698f..0000000000 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy +++ /dev/null @@ -1,105 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.impl - -import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder -import org.springframework.util.StopWatch - -class CpsPersistencePerfSpecBase extends CpsPersistenceSpecBase { - - static final String PERF_TEST_DATA = '/data/perf-test.sql' - static final String PERF_DATASPACE = 'PERF-DATASPACE' - static final String PERF_ANCHOR = 'PERF-ANCHOR' - static final String PERF_TEST_PARENT = '/perf-parent-1' - - static def xpathsToAllGrandChildren = [] - - static def PERFORMANCE_RECORD = [] - - def stopWatch = new StopWatch() - - def cleanupSpec() { - println('#############################################################################') - println('## P E R F O R M A N C E T E S T R E S U L T S ##') - println('#############################################################################') - PERFORMANCE_RECORD.sort().each { println(it) } - PERFORMANCE_RECORD.clear() - } - - def createLineage(cpsDataPersistenceService, numberOfChildren, numberOfGrandChildren, createLists) { - xpathsToAllGrandChildren = [] - (1..numberOfChildren).each { - if (createLists) { - def xpathFormat = "${PERF_TEST_PARENT}/perf-test-list-${it}[@key='%d']" - def listElements = goForthAndMultiply(xpathFormat, numberOfGrandChildren) - cpsDataPersistenceService.addListElements(PERF_DATASPACE, PERF_ANCHOR, PERF_TEST_PARENT, listElements) - } else { - def xpathFormat = "${PERF_TEST_PARENT}/perf-test-child-${it}/perf-test-grand-child-%d" - def grandChildren = goForthAndMultiply(xpathFormat, numberOfGrandChildren) - def child = new DataNodeBuilder() - .withXpath("${PERF_TEST_PARENT}/perf-test-child-${it}") - .withChildDataNodes(grandChildren) - .build() - cpsDataPersistenceService.addChildDataNode(PERF_DATASPACE, PERF_ANCHOR, PERF_TEST_PARENT, child) - } - } - } - - def goForthAndMultiply(xpathFormat, numberOfGrandChildren) { - def grandChildren = [] - (1..numberOfGrandChildren).each { - def xpath = String.format(xpathFormat as String, it) - def grandChild = new DataNodeBuilder().withXpath(xpath).build() - xpathsToAllGrandChildren.add(grandChild.xpath) - grandChildren.add(grandChild) - } - return grandChildren - } - - def countDataNodes(Collection<DataNode> dataNodes) { - int nodeCount = 0 - for (DataNode parent : dataNodes) { - nodeCount = nodeCount + countDataNodes(parent) - } - return nodeCount - } - - def countDataNodes(DataNode dataNode) { - int nodeCount = 1 - for (DataNode child : dataNode.childDataNodes) { - nodeCount = nodeCount + countDataNodes(child) - } - return nodeCount - } - - def recordAndAssertPerformance(String shortTitle, thresholdInMs, recordedTimeInMs) { - def pass = recordedTimeInMs <= thresholdInMs - if (shortTitle.length()>40) { - shortTitle = shortTitle.substring(0,40) - } - def record = String.format('%2d.%-40s limit%,7d took %,7d ms ', PERFORMANCE_RECORD.size()+1, shortTitle, thresholdInMs, recordedTimeInMs) - record += pass?'PASS':'FAIL' - PERFORMANCE_RECORD.add(record) - assert recordedTimeInMs <= thresholdInMs - return true - } -} diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy deleted file mode 100644 index 428088135a..0000000000 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy +++ /dev/null @@ -1,239 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.performance - -import org.onap.cps.spi.CpsDataPersistenceService -import org.onap.cps.spi.impl.CpsPersistencePerfSpecBase -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.jdbc.Sql - -class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase { - - @Autowired - CpsDataPersistenceService objectUnderTest - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Create a node with many descendants (please note, subsequent tests depend on this running first).'() { - when: 'a node with a large number of descendants is created' - stopWatch.start() - createLineage(objectUnderTest, 150, 50, false) - stopWatch.stop() - def setupDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'setup duration is under 10 seconds' - recordAndAssertPerformance('Setup', 10_000, setupDurationInMillis) - } - - def 'Delete 10 children with grandchildren'() { - when: 'child nodes are deleted' - stopWatch.start() - (1..10).each { - def childPath = "${PERF_TEST_PARENT}/perf-test-child-${it}".toString() - objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, childPath) - } - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Delete 10 children', 300, deleteDurationInMillis) - } - - def 'Batch delete 100 children with grandchildren'() { - given: 'a list of xpaths to delete' - def xpathsToDelete = (11..110).collect { - "${PERF_TEST_PARENT}/perf-test-child-${it}".toString() - } - when: 'child nodes are deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Batch delete 100 children', 300, deleteDurationInMillis) - } - - def 'Delete 50 grandchildren (that have no descendants)'() { - when: 'target nodes are deleted' - stopWatch.start() - (1..50).each { - def grandchildPath = "${PERF_TEST_PARENT}/perf-test-child-111/perf-test-grand-child-${it}".toString() - objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, grandchildPath) - } - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 700 milliseconds' - recordAndAssertPerformance('Delete 50 grandchildren', 700, deleteDurationInMillis) - } - - def 'Batch delete 500 grandchildren (that have no descendants)'() { - given: 'a list of xpaths to delete' - def xpathsToDelete = [] - for (int childIndex = 0; childIndex < 10; childIndex++) { - xpathsToDelete.addAll((1..50).collect { - "${PERF_TEST_PARENT}/perf-test-child-${112+childIndex}/perf-test-grand-child-${it}".toString() - }) - } - when: 'target nodes are deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 100 milliseconds' - recordAndAssertPerformance('Batch delete 500 grandchildren', 100, deleteDurationInMillis) - } - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Create a node with many list elements (please note, subsequent tests depend on this running first).'() { - when: 'a node with a large number of lists is created' - stopWatch.start() - createLineage(objectUnderTest, 150, 50, true) - stopWatch.stop() - def setupDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'setup duration is under 6 seconds' - recordAndAssertPerformance('Setup lists', 6_000, setupDurationInMillis) - } - - def 'Delete 10 whole lists'() { - when: 'lists are deleted' - stopWatch.start() - (1..10).each { - def childPath = "${PERF_TEST_PARENT}/perf-test-list-${it}".toString() - objectUnderTest.deleteListDataNode(PERF_DATASPACE, PERF_ANCHOR, childPath) - } - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Delete 10 whole lists', 300, deleteDurationInMillis) - } - - def 'Batch delete 100 whole lists'() { - given: 'a list of xpaths to delete' - def xpathsToDelete = (11..110).collect { - "${PERF_TEST_PARENT}/perf-test-list-${it}".toString() - } - when: 'lists are deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 600 milliseconds' - recordAndAssertPerformance('Batch delete 100 whole lists', 600, deleteDurationInMillis) - } - - def 'Delete 10 list elements'() { - when: 'list elements are deleted' - stopWatch.start() - (1..10).each { - def grandchildPath = "${PERF_TEST_PARENT}/perf-test-list-111[@key='${it}']".toString() - objectUnderTest.deleteListDataNode(PERF_DATASPACE, PERF_ANCHOR, grandchildPath) - } - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 200 milliseconds' - recordAndAssertPerformance('Delete 10 lists elements', 200, deleteDurationInMillis) - } - - def 'Batch delete 500 list elements'() { - given: 'a list of xpaths to delete' - def xpathsToDelete = [] - for (int childIndex = 0; childIndex < 10; childIndex++) { - xpathsToDelete.addAll((1..50).collect { - "${PERF_TEST_PARENT}/perf-test-list-${112+childIndex}[@key='${it}']".toString() - }) - } - when: 'list elements are deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 100 milliseconds' - recordAndAssertPerformance('Batch delete 500 lists elements', 100, deleteDurationInMillis) - } - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Delete 1 large data node'() { - given: 'a node with a large number of descendants is created' - createLineage(objectUnderTest, 50, 50, false) - createLineage(objectUnderTest, 50, 50, true) - when: 'parent node is deleted' - stopWatch.start() - objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, PERF_TEST_PARENT) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Delete one large node', 300, deleteDurationInMillis) - } - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Batch delete 1 large data node'() { - given: 'a node with a large number of descendants is created' - createLineage(objectUnderTest, 50, 50, false) - createLineage(objectUnderTest, 50, 50, true) - when: 'parent node is batch deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, [PERF_TEST_PARENT]) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Batch delete one large node', 300, deleteDurationInMillis) - } - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Delete root node with many descendants'() { - given: 'a node with a large number of descendants is created' - createLineage(objectUnderTest, 50, 50, false) - createLineage(objectUnderTest, 50, 50, true) - when: 'root node is deleted' - stopWatch.start() - objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, '/') - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Delete root node', 300, deleteDurationInMillis) - } - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Delete data nodes for an anchor'() { - given: 'a node with a large number of descendants is created' - createLineage(objectUnderTest, 50, 50, false) - createLineage(objectUnderTest, 50, 50, true) - when: 'data nodes are deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Delete data nodes for anchor', 300, deleteDurationInMillis) - } - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Delete data nodes for multiple anchors'() { - given: 'a node with a large number of descendants is created' - createLineage(objectUnderTest, 50, 50, false) - createLineage(objectUnderTest, 50, 50, true) - when: 'data nodes are deleted' - stopWatch.start() - objectUnderTest.deleteDataNodes(PERF_DATASPACE, [PERF_ANCHOR]) - stopWatch.stop() - def deleteDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'delete duration is under 300 milliseconds' - recordAndAssertPerformance('Delete data nodes for anchors', 300, deleteDurationInMillis) - } - -} diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy deleted file mode 100644 index 2628e9697f..0000000000 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy +++ /dev/null @@ -1,100 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.performance - -import org.onap.cps.spi.impl.CpsPersistencePerfSpecBase -import org.onap.cps.spi.CpsDataPersistenceService -import org.onap.cps.spi.repository.AnchorRepository -import org.onap.cps.spi.repository.DataspaceRepository -import org.onap.cps.spi.repository.FragmentRepository -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.jdbc.Sql - -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS - -class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase { - - @Autowired - CpsDataPersistenceService objectUnderTest - - @Autowired - DataspaceRepository dataspaceRepository - - @Autowired - AnchorRepository anchorRepository - - @Autowired - FragmentRepository fragmentRepository - - static def NUMBER_OF_CHILDREN = 200 - static def NUMBER_OF_GRAND_CHILDREN = 50 - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Create a node with many descendants (please note, subsequent tests depend on this running first).'() { - given: 'a node with a large number of descendants is created' - stopWatch.start() - createLineage(objectUnderTest, NUMBER_OF_CHILDREN, NUMBER_OF_GRAND_CHILDREN, false) - stopWatch.stop() - def setupDurationInMillis = stopWatch.getTotalTimeMillis() - and: 'setup duration is under 10 seconds' - recordAndAssertPerformance('Setup', 10000, setupDurationInMillis) - } - - def 'Update data nodes with descendants'() { - given: 'a list of xpaths to data nodes with descendants (xpath for each child)' - def xpaths = (1..20).collect { - "${PERF_TEST_PARENT}/perf-test-child-${it}".toString() - } - and: 'the correct number of data nodes are fetched' - def dataNodes = objectUnderTest.getDataNodesForMultipleXpaths(PERF_DATASPACE, PERF_ANCHOR, xpaths, INCLUDE_ALL_DESCENDANTS) - assert dataNodes.size() == 20 - assert countDataNodes(dataNodes) == 20 + 20 * 50 - when: 'the fragment entities are updated by the data nodes' - stopWatch.start() - objectUnderTest.updateDataNodesAndDescendants(PERF_DATASPACE, PERF_ANCHOR, dataNodes) - stopWatch.stop() - def updateDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'update duration is under 600 milliseconds' - recordAndAssertPerformance('Update data nodes with descendants', 600, updateDurationInMillis) - } - - def 'Update data nodes without descendants'() { - given: 'a list of xpaths to data nodes without descendants (xpath for each grandchild)' - def xpaths = [] - for (int childIndex = 21; childIndex <= 40; childIndex++) { - xpaths.addAll((1..50).collect { - "${PERF_TEST_PARENT}/perf-test-child-${childIndex}/perf-test-grand-child-${it}".toString() - }) - } - and: 'the correct number of data nodes are fetched' - def dataNodes = objectUnderTest.getDataNodesForMultipleXpaths(PERF_DATASPACE, PERF_ANCHOR, xpaths, OMIT_DESCENDANTS) - assert dataNodes.size() == 20 * 50 - assert countDataNodes(dataNodes) == 20 * 50 - when: 'the fragment entities are updated by the data nodes' - stopWatch.start() - objectUnderTest.updateDataNodesAndDescendants(PERF_DATASPACE, PERF_ANCHOR, dataNodes) - stopWatch.stop() - def updateDurationInMillis = stopWatch.getTotalTimeMillis() - then: 'update duration is under 900 milliseconds' - recordAndAssertPerformance('Update data nodes without descendants', 900, updateDurationInMillis) - } -} diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy deleted file mode 100644 index 222a828b9f..0000000000 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy +++ /dev/null @@ -1,99 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.performance - -import org.onap.cps.spi.CpsModulePersistenceService -import org.onap.cps.spi.entities.SchemaSetEntity -import org.onap.cps.spi.impl.CpsPersistenceSpecBase -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.spi.repository.ModuleReferenceRepository -import org.onap.cps.spi.repository.SchemaSetRepository -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.jdbc.Sql -import org.springframework.util.StopWatch - -import java.util.concurrent.ThreadLocalRandom - -class CpsModuleReferenceRepositoryPerfTest extends CpsPersistenceSpecBase { - - static final String PERF_TEST_DATA = '/data/perf-test.sql' - - def NEW_RESOURCE_CONTENT = 'module stores {\n' + - ' yang-version 1.1;\n' + - ' namespace "org:onap:ccsdk:sample";\n' + - '\n' + - ' prefix book-store;\n' + - '\n' + - ' revision "2020-09-15" {\n' + - ' description\n' + - ' "Sample Model";\n' + - ' }' + - '}' - - @Autowired - CpsModulePersistenceService objectUnderTest - - @Autowired - SchemaSetRepository schemaSetRepository - - @Autowired - ModuleReferenceRepository moduleReferenceRepository - - @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Store new schema set with many modules'() { - when: 'a new schema set with 200 modules is stored' - def newYangResourcesNameToContentMap = [:] - (1..200).each { - def year = 2000 + it - def resourceName = "module${it}".toString() - def moduleName = "stores${it}" - def content = NEW_RESOURCE_CONTENT.replace('2020',String.valueOf(year)).replace('stores',moduleName) - newYangResourcesNameToContentMap.put(resourceName, content) - } - objectUnderTest.storeSchemaSet('PERF-DATASPACE', 'perfSchemaSet', newYangResourcesNameToContentMap) - then: 'the schema set is persisted correctly' - def dataspaceEntity = dataspaceRepository.getByName('PERF-DATASPACE') - SchemaSetEntity result = schemaSetRepository.getByDataspaceAndName(dataspaceEntity, 'perfSchemaSet') - result.yangResources.size() == 200 - and: 'identification of new module resources is fast enough (1,000 executions less then 6,000 milliseconds)' - def stopWatch = new StopWatch() - 1000.times() { - def moduleReferencesToCheck = createModuleReferencesWithRandomMatchingExistingModuleReferences() - stopWatch.start() - def newModuleReferences = moduleReferenceRepository.identifyNewModuleReferences(moduleReferencesToCheck) - stopWatch.stop() - assert newModuleReferences.size() > 0 && newModuleReferences.size() < 300 - } - assert stopWatch.getTotalTimeMillis() < 6000 - } - - def createModuleReferencesWithRandomMatchingExistingModuleReferences() { - def moduleReferences = [] - (1..250).each { - def randomNumber = ThreadLocalRandom.current().nextInt(1, 300) - def year = 2000 + randomNumber - def moduleName = "stores${randomNumber}" - moduleReferences.add(new ModuleReference(moduleName, "${year}-09-15")) - } - return moduleReferences - } - -} diff --git a/cps-ri/src/test/resources/data/perf-test.sql b/cps-ri/src/test/resources/data/perf-test.sql deleted file mode 100644 index 5119f26b24..0000000000 --- a/cps-ri/src/test/resources/data/perf-test.sql +++ /dev/null @@ -1,28 +0,0 @@ -/* - ============LICENSE_START======================================================= - Copyright (C) 2022 Nordix Foundation. - ================================================================================ - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - SPDX-License-Identifier: Apache-2.0 - ============LICENSE_END========================================================= -*/ - -INSERT INTO DATASPACE (ID, NAME) VALUES (9001, 'PERF-DATASPACE'); - -INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES (9002, 'PERF-SCHEMA-SET', 9001); - -INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (9003, 'PERF-ANCHOR', 9001, 9002); - -INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES (0, 9001, 9003, null, '/perf-parent-1'); - |