-/*-
+/*
* ============LICENSE_START=======================================================
* Copyright (C) 2023 Nordix Foundation.
* ================================================================================
/**
* This method populates uri variables.
*
- * @param dataStoreName data store name
+ * @param dataStoreName data store name
* @param dmiServiceName dmi service name
* @param cmHandleId cm handle id for dmi registration
* @return {@code String} dmi service url as string
@Autowired
NcmpConfiguration.DmiProperties dmiProperties
-
+
@Autowired
HttpClientConfiguration httpClientConfiguration
-
+
def mockRestTemplateBuilder = new RestTemplateBuilder()
def 'NcmpConfiguration Construction.'() {
ncmp:
dmi:
+ httpclient:
+ connectionTimeoutInSeconds: 180
auth:
username: some-user
password: some-password
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2023 Nordix Foundation.
+ * Copyright (C) 2020-2024 Nordix Foundation.
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 TechMahindra Ltd.
import jakarta.transaction.Transactional;
import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.spi.entities.AnchorEntity;
import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.SchemaSetEntity;
-import org.onap.cps.spi.entities.YangResourceModuleReference;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.DataspaceInUseException;
-import org.onap.cps.spi.exceptions.DataspaceNotFoundException;
-import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException;
import org.onap.cps.spi.model.Anchor;
import org.onap.cps.spi.model.Dataspace;
import org.onap.cps.spi.repository.AnchorRepository;
import org.onap.cps.spi.repository.DataspaceRepository;
import org.onap.cps.spi.repository.SchemaSetRepository;
-import org.onap.cps.spi.repository.YangResourceRepository;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Component;
private final DataspaceRepository dataspaceRepository;
private final AnchorRepository anchorRepository;
private final SchemaSetRepository schemaSetRepository;
- private final YangResourceRepository yangResourceRepository;
@Override
public void createDataspace(final String dataspaceName) {
}
@Override
- public Collection<Anchor> queryAnchors(final String dataspaceName, final Collection<String> inputModuleNames) {
- try {
- validateDataspaceAndModuleNames(dataspaceName, inputModuleNames);
- } catch (DataspaceNotFoundException | ModuleNamesNotFoundException e) {
- log.info("Module search encountered unknown dataspace or modulename, treating this as nothing found");
- return Collections.emptySet();
- }
-
+ public Collection<String> queryAnchorNames(final String dataspaceName, final Collection<String> inputModuleNames) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
- final Collection<AnchorEntity> anchorEntities = anchorRepository
- .getAnchorsByDataspaceIdAndModuleNames(dataspaceEntity.getId(), inputModuleNames, inputModuleNames.size());
- return anchorEntities.stream().map(CpsAdminPersistenceServiceImpl::toAnchor).collect(Collectors.toSet());
+ return anchorRepository.getAnchorNamesByDataspaceIdAndModuleNames(dataspaceEntity.getId(), inputModuleNames,
+ inputModuleNames.size());
}
@Override
private static Dataspace toDataspace(final DataspaceEntity dataspaceEntity) {
return Dataspace.builder().name(dataspaceEntity.getName()).build();
}
-
- private void validateDataspaceAndModuleNames(final String dataspaceName,
- final Collection<String> inputModuleNames) {
- final Collection<String> retrievedModuleReferences =
- yangResourceRepository.findAllModuleReferencesByDataspaceAndModuleNames(dataspaceName, inputModuleNames)
- .stream().map(YangResourceModuleReference::getModuleName)
- .collect(Collectors.toList());
- if (retrievedModuleReferences.isEmpty()) {
- verifyDataspaceName(dataspaceName);
- }
- if (inputModuleNames.size() > retrievedModuleReferences.size()) {
- final List<String> unknownModules = inputModuleNames.stream()
- .filter(moduleName -> !retrievedModuleReferences.contains(moduleName))
- .collect(Collectors.toList());
- throw new ModuleNamesNotFoundException(dataspaceName, unknownModules);
- }
- }
-
- private void verifyDataspaceName(final String dataspaceName) {
- dataspaceRepository.getByName(dataspaceName);
- }
}
@Query(value = """
SELECT
- anchor.*
+ anchor.name
FROM
yang_resource
JOIN schema_set_yang_resources ON schema_set_yang_resources.yang_resource_id = yang_resource.id
HAVING
COUNT(DISTINCT module_name) = :sizeOfModuleNames
""", nativeQuery = true)
- Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId,
- @Param("moduleNames") String[] moduleNames,
- @Param("sizeOfModuleNames") int sizeOfModuleNames);
+ Collection<String> getAnchorNamesByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId,
+ @Param("moduleNames") String[] moduleNames,
+ @Param("sizeOfModuleNames") int sizeOfModuleNames);
- default Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(final int dataspaceId,
- final Collection<String> moduleNames,
- final int sizeOfModuleNames) {
+ default Collection<String> getAnchorNamesByDataspaceIdAndModuleNames(final int dataspaceId,
+ final Collection<String> moduleNames,
+ final int sizeOfModuleNames) {
final String[] moduleNamesArray = moduleNames.toArray(new String[0]);
- return getAnchorsByDataspaceIdAndModuleNames(dataspaceId, moduleNamesArray, sizeOfModuleNames);
+ return getAnchorNamesByDataspaceIdAndModuleNames(dataspaceId, moduleNamesArray, sizeOfModuleNames);
}
@Modifying
@Param("dataspaceName") String dataspaceName, @Param("anchorName") String anchorName,
@Param("moduleName") String moduleName, @Param("revision") String revision);
- @Query(value = """
- SELECT DISTINCT
- yang_resource.*
- FROM
- dataspace
- JOIN schema_set ON schema_set.dataspace_id = dataspace.id
- JOIN schema_set_yang_resources ON schema_set_yang_resources.schema_set_id = schema_set.id
- JOIN yang_resource ON yang_resource.id = schema_set_yang_resources.yang_resource_id
- WHERE
- dataspace.name = :dataspaceName
- AND yang_resource.module_name = ANY ( :moduleNames )
- """, nativeQuery = true)
- Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames(
- @Param("dataspaceName") String dataspaceName, @Param("moduleNames") String[] moduleNames);
-
- default Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames(
- final String dataspaceName, final Collection<String> moduleNames) {
- return findAllModuleReferencesByDataspaceAndModuleNames(dataspaceName, moduleNames.toArray(new String[0]));
- }
-
@Modifying
@Query(value = "DELETE FROM schema_set_yang_resources WHERE schema_set_id = :schemaSetId", nativeQuery = true)
void deleteSchemaSetYangResourceForSchemaSetId(@Param("schemaSetId") int schemaSetId);
package org.onap.cps.api.impl;
import java.util.Collection;
-import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.onap.cps.api.CpsAnchorService;
import org.onap.cps.spi.CpsAdminPersistenceService;
@Override
public Collection<String> queryAnchorNames(final String dataspaceName, final Collection<String> moduleNames) {
cpsValidator.validateNameCharacters(dataspaceName);
- final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
- return anchors.stream().map(Anchor::getName).collect(Collectors.toList());
+ return cpsAdminPersistenceService.queryAnchorNames(dataspaceName, moduleNames);
}
@Override
* @return a collection of anchor names in the given dataspace. The schema set for each anchor must include all the
* given module names
*/
- Collection<Anchor> queryAnchors(String dataspaceName, Collection<String> moduleNames);
+ Collection<String> queryAnchorNames(String dataspaceName, Collection<String> moduleNames);
/**
* Get an anchor in the given dataspace using the anchor name.
def 'Query all anchor identifiers for a dataspace and module names.'() {
given: 'the persistence service is invoked with the expected parameters and returns a list of anchors'
- mockCpsAdminPersistenceService.queryAnchors('some-dataspace-name', ['some-module-name']) >> [new Anchor(name:'some-anchor-identifier')]
+ mockCpsAdminPersistenceService.queryAnchorNames('some-dataspace-name', ['some-module-name']) >> ['some-anchor-identifier']
when: 'query anchor names is called using a dataspace name and module name'
def result = objectUnderTest.queryAnchorNames('some-dataspace-name', ['some-module-name'])
then: 'get anchor identifiers returns the same anchor identifier returned by the persistence layer'
def 'Query all anchors with Module Names Not Found Exception in persistence layer.'() {
given: 'the persistence layer throws a Module Names Not Found Exception'
def originalException = new ModuleNamesNotFoundException('exception-ds', ['m1', 'm2'])
- mockCpsAdminPersistenceService.queryAnchors(*_) >> { throw originalException}
+ mockCpsAdminPersistenceService.queryAnchorNames(*_) >> { throw originalException}
when: 'attempt query anchors'
objectUnderTest.queryAnchorNames('some-dataspace-name', [])
then: 'the same exception is thrown (up)'
objectUnderTest.deleteAnchor(GENERAL_TEST_DATASPACE, 'newAnchor')
}
- def 'Query anchors without any known modules and #scenario'() {
+ def 'Query anchors without any known modules'() {
when: 'querying for anchors with #scenario'
- def result = objectUnderTest.queryAnchorNames(dataspaceName, ['unknownModule'])
+ def result = objectUnderTest.queryAnchorNames(GENERAL_TEST_DATASPACE, ['unknownModule'])
then: 'an empty result is returned (no error)'
assert result == []
- where:
- scenario | dataspaceName
- 'non existing database' | 'nonExistingDataspace'
- 'just unknown module(s)' | GENERAL_TEST_DATASPACE
}
def 'Update anchor schema set.'() {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the operation completes within 12 seconds'
- recordAndAssertResourceUsage("Creating 33,000 books", 12, durationInSeconds, 150, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Creating 33,000 books", 16, durationInSeconds, 150, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Get data nodes from multiple xpaths 32K (2^15) limit exceeded.'() {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'test data is deleted in 1 second'
- recordAndAssertResourceUsage("Deleting test data", 1, durationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Deleting test data", 0.1, durationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
}
def countDataNodes() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete 100 containers', 2.5, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete 100 containers', 2.2, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 container nodes'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 containers', 0.6, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 containers', 0.7, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 100 list elements'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete 100 lists elements', 2.5, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete 100 lists elements', 2.1, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 list elements'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 lists elements', 0.6, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 lists elements', 0.7, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 100 whole lists'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete 100 whole lists', 6, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete 100 whole lists', 4, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 whole lists'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 whole lists', 5, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 whole lists', 3, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Delete 1 large data node'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Delete data nodes for anchor', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Delete data nodes for anchor', 1.9, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Batch delete 100 non-existing nodes'() {
resourceMeter.stop()
def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'delete duration is within expected time and memory used is within limit'
- recordAndAssertResourceUsage('Batch delete 100 non-existing', 7, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Batch delete 100 non-existing', 1, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Clean up test data'() {
recordAndAssertResourceUsage("Read datatrees with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'no descendants' | OMIT_DESCENDANTS || 0.02 | 1 | 1
- 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.06 | 5 | 1 + OPENROADM_DEVICES_PER_ANCHOR
- 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'no descendants' | OMIT_DESCENDANTS || 0.01 | 1 | 1
+ 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.05 | 5 | 1 + OPENROADM_DEVICES_PER_ANCHOR
+ 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
def 'Read data trees for multiple xpaths'() {
then: 'requested nodes and their descendants are returned'
assert countDataNodesInTree(result) == OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
and: 'all data is read within expected time and memory used is within limit'
- recordAndAssertResourceUsage("Read datatrees for multiple xpaths", 4 , durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Read datatrees for multiple xpaths", 1.8 , durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Read for multiple xpaths to non-existing datanodes'() {
then: 'no data is returned'
assert result.isEmpty()
and: 'the operation completes within within expected time'
- recordAndAssertResourceUsage("Read non-existing xpaths", 0.02, durationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Read non-existing xpaths", 0.01, durationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Read complete data trees using #scenario.'() {
recordAndAssertResourceUsage("Read datatrees using ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following xpaths are used'
scenario | xpath || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'openroadm root' | '/' || 2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
- 'openroadm top element' | '/openroadm-devices' || 2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
- 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 3 | 250 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'openroadm root' | '/' || 1.0 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'openroadm top element' | '/openroadm-devices' || 1.0 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 1.7 | 250 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
}
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 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.integration.performance.cps
+
+import org.onap.cps.integration.performance.base.CpsPerfTestBase
+import org.onap.cps.spi.model.ModuleReference
+
+class ModuleQueryPerfTest extends CpsPerfTestBase {
+
+ static final KILOBYTE = 1000
+ static final TOTAL_TEST_ANCHORS = 10_000
+ static final SCHEMA_SET_PREFIX = 'mySchemaSet'
+ static final ANCHOR_PREFIX = 'myAnchor'
+ static final MODULE_REVISION = '2024-04-25'
+ static final MODULE_TEMPLATE = """
+ module <MODULE_NAME> {
+ yang-version 1.1;
+ namespace "org:onap:cps:test:<MODULE_NAME>";
+ prefix tree;
+ revision "<MODULE_REVISION>" {
+ description "<DESCRIPTION>";
+ }
+ container tree {
+ list branch {
+ key "name";
+ leaf name {
+ type string;
+ }
+ }
+ }
+ }
+ """
+
+ def 'Module query - Preload test data (needed for other tests).'() {
+ given: 'a schema set with different sizes of Yang modules is created'
+ cpsModuleService.createSchemaSet(CPS_PERFORMANCE_TEST_DATASPACE, SCHEMA_SET_PREFIX + '0', [
+ 'module0.yang': makeYangModuleOfLength('module0', 1 * KILOBYTE),
+ 'module1.yang': makeYangModuleOfLength('module1', 1000 * KILOBYTE)
+ ])
+ and: 'these modules will be used again to create many schema sets'
+ def allModuleReferences = [
+ new ModuleReference('module0', MODULE_REVISION),
+ new ModuleReference('module1', MODULE_REVISION)
+ ]
+ when: 'many schema sets and anchors are created using those modules'
+ resourceMeter.start()
+ (1..TOTAL_TEST_ANCHORS).each {
+ def schemaSetName = SCHEMA_SET_PREFIX + it
+ def anchorName = ANCHOR_PREFIX + it
+ cpsModuleService.createSchemaSetFromModules(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetName, [:], allModuleReferences)
+ cpsAnchorService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetName, anchorName)
+ }
+ resourceMeter.stop()
+ then: 'operation takes less than expected duration'
+ recordAndAssertResourceUsage('Module query test setup',
+ 45, resourceMeter.totalTimeInSeconds,
+ 500, resourceMeter.totalMemoryUsageInMB
+ )
+ }
+
+ def 'Querying anchors by module name is NOT dependant on the file size of the module.'() {
+ when: 'we search for anchors with given Yang module name'
+ resourceMeter.start()
+ def result = cpsAnchorService.queryAnchorNames(CPS_PERFORMANCE_TEST_DATASPACE, [yangModuleName])
+ resourceMeter.stop()
+ then: 'expected number of anchors is returned'
+ assert result.size() == TOTAL_TEST_ANCHORS
+ and: 'operation completes with expected resource usage'
+ recordAndAssertResourceUsage("Query for anchors with ${scenario}",
+ expectedTimeInSeconds, resourceMeter.totalTimeInSeconds,
+ 5, resourceMeter.totalMemoryUsageInMB)
+ where: 'the following parameters are used'
+ scenario | yangModuleName || expectedTimeInSeconds
+ '1 KB module' | 'module0' || 0.05
+ '1000 KB module' | 'module1' || 0.05
+ }
+
+ def 'Module query - Clean up test data.'() {
+ cleanup:
+ // FIXME this API has extremely high memory usage, therefore external batching must be used
+ for (int i = 1; i <= TOTAL_TEST_ANCHORS; i += 100) {
+ cpsModuleService.deleteSchemaSetsWithCascade(CPS_PERFORMANCE_TEST_DATASPACE, (i..i+100).collect {SCHEMA_SET_PREFIX + it})
+ }
+ cpsModuleService.deleteSchemaSetsWithCascade(CPS_PERFORMANCE_TEST_DATASPACE, [SCHEMA_SET_PREFIX + '0'])
+ }
+
+ // This makes a Yang module of approximately target length in bytes by padding the description field with many '*'
+ private static def makeYangModuleOfLength(moduleName, targetLength) {
+ def padding = String.valueOf('*').repeat(targetLength - MODULE_TEMPLATE.size()) // not exact
+ return MODULE_TEMPLATE
+ .replaceAll('<MODULE_NAME>', moduleName)
+ .replaceAll('<MODULE_REVISION>', MODULE_REVISION)
+ .replaceAll('<DESCRIPTION>', padding)
+ }
+}
recordAndAssertResourceUsage("Query 1 anchor ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpsPath || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'top element' | '/openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
- 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
- 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
- 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
- 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.1 | 1 | 0
+ 'top element' | '/openroadm-devices' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1
+ 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.009 | 1 | 0
}
def 'Query complete data trees across all anchors with #scenario.'() {
recordAndAssertResourceUsage("Query across anchors ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpspath || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'top element' | '/openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
- 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE)
- 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
- 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
+ 'top element' | '/openroadm-devices' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
+ 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE)
+ 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
+ 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1)
}
def 'Query with leaf condition and #scenario.'() {
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
'no descendants' | OMIT_DESCENDANTS || 0.1 | 6 | OPENROADM_DEVICES_PER_ANCHOR
- 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.2 | 12 | OPENROADM_DEVICES_PER_ANCHOR * 2
- 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.1 | 12 | OPENROADM_DEVICES_PER_ANCHOR * 2
+ 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.1 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
def 'Query ancestors with #scenario.'() {
recordAndAssertResourceUsage("Query ancestors with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
- 'no descendants' | OMIT_DESCENDANTS || 0.1 | 3 | 1
- 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.2 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR
- 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 400 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
+ 'no descendants' | OMIT_DESCENDANTS || 0.08 | 3 | 1
+ 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.1 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR
+ 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.1 | 400 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
}
timeLimit, resourceMeter.getTotalTimeInSeconds(),
memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where:
- scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit
- 'Replace 0 nodes with 100' | 100 | 1 | false || 2.5 | 200
- 'Replace 100 using same data' | 100 | 1 | false || 3.0 | 200
- 'Replace 100 with new leaf values' | 100 | 1 | true || 3.0 | 200
- 'Replace 100 with 100 new nodes' | 100 | 101 | false || 6.0 | 200
- 'Replace 50 existing and 50 new' | 100 | 151 | true || 4.5 | 200
- 'Replace 100 nodes with 0' | 0 | 1 | false || 3.0 | 200
+ scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit
+ 'Replace 0 nodes with 100' | 100 | 1 | false || 3.2 | 200
+ 'Replace 100 using same data' | 100 | 1 | false || 5.6 | 200
+ 'Replace 100 with new leaf values' | 100 | 1 | true || 5.5 | 200
+ 'Replace 100 with 100 new nodes' | 100 | 101 | false || 10.0 | 200
+ 'Replace 50 existing and 50 new' | 100 | 151 | true || 8.0 | 200
+ 'Replace 100 nodes with 0' | 0 | 1 | false || 7.0 | 200
}
def 'Replace list content: #scenario.'() {
memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where:
scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit
- 'Replace list of 0 with 100' | 100 | 1 | false || 2.5 | 200
- 'Replace list of 100 using same data' | 100 | 1 | false || 3.0 | 200
- 'Replace list of 100 with new leaf values' | 100 | 1 | true || 3.0 | 200
- 'Replace list with 100 new nodes' | 100 | 101 | false || 6.0 | 200
- 'Replace list with 50 existing and 50 new' | 100 | 151 | true || 4.5 | 200
- 'Replace list of 100 nodes with 1' | 1 | 1 | false || 3.0 | 200
+ 'Replace list of 0 with 100' | 100 | 1 | false || 3.0 | 200
+ 'Replace list of 100 using same data' | 100 | 1 | false || 5.4 | 200
+ 'Replace list of 100 with new leaf values' | 100 | 1 | true || 5.6 | 200
+ 'Replace list with 100 new nodes' | 100 | 101 | false || 9.9 | 200
+ 'Replace list with 50 existing and 50 new' | 100 | 151 | true || 8.0 | 200
+ 'Replace list of 100 nodes with 1' | 1 | 1 | false || 7.0 | 200
}
def 'Update leaves for 100 data nodes.'() {
assert 100 == countDataNodes('/openroadm-devices/openroadm-device[@status="fail"]')
and: 'update completes within expected time and memory used is within limit'
recordAndAssertResourceUsage('Update leaves for 100 data nodes',
- 0.4, resourceMeter.getTotalTimeInSeconds(),
+ 0.3, resourceMeter.getTotalTimeInSeconds(),
120, resourceMeter.getTotalMemoryUsageInMB())
}
cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR)
where:
totalNodes || expectedDuration | memoryLimit
- 50 || 2 | 100
- 100 || 4 | 200
- 200 || 7 | 400
- 400 || 14 | 500
+ 50 || 1.6 | 100
+ 100 || 3.3 | 200
+ 200 || 6.8 | 400
+ 400 || 13.0 | 500
}
def 'Writing bookstore data has exponential time.'() {
cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR)
where:
totalBooks || expectedDuration | memoryLimit
- 800 || 0.5 | 50
- 1600 || 1.5 | 100
- 3200 || 6.0 | 150
- 6400 || 18.0 | 200
+ 800 || 0.3 | 50
+ 1600 || 0.8 | 100
+ 3200 || 2.6 | 150
+ 6400 || 6.7 | 200
}
def 'Writing openroadm list data using saveListElements.'() {
cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR)
where:
totalNodes || expectedDuration | memoryLimit
- 50 || 2 | 100
- 100 || 4 | 200
- 200 || 7 | 400
- 400 || 14 | 500
+ 50 || 1.5 | 100
+ 100 || 3.0 | 200
+ 200 || 6.3 | 400
+ 400 || 14.0 | 500
}
}
def resultAfter = objectUnderTest.queryDataNodes(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, cpsPath, INCLUDE_ALL_DESCENDANTS)
assert resultAfter.collect {it.leaves.subscribers.size()}.sum() == totalNumberOfEntries * (1 + numberOfCmDataSubscribers)
and: 'update matching subscription within 15 seconds'
- recordAndAssertResourceUsage("Update matching subscription", 15, durationInSeconds, 1000, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("Update matching subscription", 11, durationInSeconds, 1000, resourceMeter.getTotalMemoryUsageInMB())
}
def 'Worst case new subscription (200x10 new entries).'() {
package org.onap.cps.integration.performance.ncmp
-import org.apache.commons.lang3.StringUtils
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncService
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter
-import org.onap.cps.spi.FetchDescendantsOption
-import org.onap.cps.spi.model.DataNode
-import org.springframework.beans.factory.annotation.Autowired
-import java.util.stream.Collectors
import org.onap.cps.api.CpsQueryService
import org.onap.cps.integration.ResourceMeter
import org.onap.cps.integration.performance.base.NcmpPerfTestBase
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import java.util.stream.Collectors
+
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
class CmHandleQueryPerfTest extends NcmpPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the required operations are performed within required time'
- recordAndAssertResourceUsage("CpsPath Registry attributes Query", 2, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("CpsPath Registry attributes Query", 3.4, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
and: 'all nodes are returned'
result.size() == TOTAL_CM_HANDLES
and: 'the tree contains all the expected descendants too'
expectedAverageResponseTime, averageResponseTime,
15, resourceMeter.totalMemoryUsageInMB)
where:
- expectedAverageResponseTime = 1 * MILLISECONDS
+ expectedAverageResponseTime = 6 * MILLISECONDS
}
def 'CM-handle is looked up by alternate-id.'() {
expectedAverageResponseTime, averageResponseTime,
15, resourceMeter.totalMemoryUsageInMB)
where:
- expectedAverageResponseTime = 10 * MILLISECONDS
+ expectedAverageResponseTime = 20 * MILLISECONDS
}
def 'A batch of CM-handles is looked up by alternate-id.'() {
expectedAverageResponseTime, averageResponseTime,
500, resourceMeter.totalMemoryUsageInMB)
where:
- expectedAverageResponseTime = 100 * MILLISECONDS
+ expectedAverageResponseTime = 360 * MILLISECONDS
}
}