aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java10
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java10
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java2
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java8
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java2
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy8
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy4
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java7
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy16
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java83
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java163
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java107
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy32
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy196
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy5
-rw-r--r--docker-compose/docker-compose.yml2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy18
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/WriteDataJobPerfTest.groovy69
-rw-r--r--k6-tests/make-logs.sh43
-rw-r--r--k6-tests/ncmp/common/cmhandle-crud.js37
-rw-r--r--k6-tests/ncmp/common/passthrough-crud.js2
-rw-r--r--k6-tests/ncmp/common/search-base.js2
-rw-r--r--k6-tests/ncmp/common/utils.js2
-rwxr-xr-xk6-tests/setup.sh10
-rwxr-xr-xk6-tests/teardown.sh8
-rwxr-xr-xtest-tools/generate-metrics-report.sh12
26 files changed, 658 insertions, 200 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java
index f1f71dc57c..9cbc6b0650 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java
@@ -84,6 +84,16 @@ public interface CmHandleQueryService {
Collection<DataNode> queryNcmpRegistryByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption);
/**
+ * Method to return data nodes representing the cm handles.
+ *
+ * @param cpsPath cps path for which the cmHandle is requested
+ * @param queryResultLimit the maximum number of data nodes to return; if less than 1, returns all matching nodes
+ * @return a list of data nodes representing the cm handles.
+ */
+ Collection<DataNode> queryNcmpRegistryByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption,
+ int queryResultLimit);
+
+ /**
* Method to check the state of a cm handle with given id.
*
* @param cmHandleId cm handle id
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java
index 890522ca60..8b6934b54c 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java
@@ -54,6 +54,7 @@ import org.springframework.stereotype.Component;
public class CmHandleQueryServiceImpl implements CmHandleQueryService {
private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles";
private static final String ALTERNATE_ID = "alternate-id";
+ private static final Integer NO_LIMIT = 0;
private final CpsDataService cpsDataService;
private final CpsQueryService cpsQueryService;
@@ -99,8 +100,15 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService {
@Override
public Collection<DataNode> queryNcmpRegistryByCpsPath(final String cpsPath,
final FetchDescendantsOption fetchDescendantsOption) {
+ return queryNcmpRegistryByCpsPath(cpsPath, fetchDescendantsOption, NO_LIMIT);
+ }
+
+ @Override
+ public Collection<DataNode> queryNcmpRegistryByCpsPath(final String cpsPath,
+ final FetchDescendantsOption fetchDescendantsOption,
+ final int queryResultLimit) {
return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath,
- fetchDescendantsOption);
+ fetchDescendantsOption, queryResultLimit);
}
@Override
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java
index 75c52f3c60..ea8c3ca78d 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java
@@ -71,7 +71,7 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class CmHandleRegistrationService {
- private static final int DELETE_BATCH_SIZE = 100;
+ private static final int DELETE_BATCH_SIZE = 300;
private final CmHandleRegistrationServicePropertyHandler cmHandleRegistrationServicePropertyHandler;
private final InventoryPersistence inventoryPersistence;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java
index 7f6fe76dcc..cf98b31614 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java
@@ -59,7 +59,7 @@ import org.springframework.stereotype.Component;
@Component
public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements InventoryPersistence {
- private static final int CMHANDLE_BATCH_SIZE = 100;
+ private static final int CMHANDLE_BATCH_SIZE = 300;
private final CpsModuleService cpsModuleService;
private final CpsValidator cpsValidator;
@@ -145,7 +145,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
final Collection<DataNode> cmHandlesAsDataNodes =
cmHandleQueryService.queryNcmpRegistryByCpsPath(
- cpsPathForCmHandlesByReferences, INCLUDE_ALL_DESCENDANTS);
+ cpsPathForCmHandlesByReferences, INCLUDE_ALL_DESCENDANTS, cmHandleReferences.size());
return YangDataConverter.toYangModelCmHandles(cmHandlesAsDataNodes);
}
@@ -195,7 +195,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
public YangModelCmHandle getYangModelCmHandleByAlternateId(final String alternateId) {
final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId);
final Collection<DataNode> dataNodes = cmHandleQueryService
- .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS);
+ .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS, 1);
if (dataNodes.isEmpty()) {
throw new CmHandleNotFoundException(alternateId);
}
@@ -209,7 +209,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
}
final String cpsPathForCmHandlesByAlternateIds = getCpsPathForCmHandlesByAlternateIds(alternateIds);
final Collection<DataNode> dataNodes = cmHandleQueryService.queryNcmpRegistryByCpsPath(
- cpsPathForCmHandlesByAlternateIds, INCLUDE_ALL_DESCENDANTS);
+ cpsPathForCmHandlesByAlternateIds, INCLUDE_ALL_DESCENDANTS, alternateIds.size());
return YangDataConverter.toYangModelCmHandles(dataNodes);
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java
index 6eefedb633..8c9ec03dd9 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java
@@ -44,7 +44,7 @@ public class ModuleSyncWatchdog {
private final ModuleSyncTasks moduleSyncTasks;
private final IMap<String, String> cpsAndNcmpLock;
- private static final int MODULE_SYNC_BATCH_SIZE = 100;
+ private static final int MODULE_SYNC_BATCH_SIZE = 300;
private static final String VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP = "Started";
/**
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy
index 884d968c4f..6ac42db782 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy
@@ -131,7 +131,7 @@ class CmHandleQueryServiceImplSpec extends Specification {
def cmHandleState = CmHandleState.ADVISED
and: 'the persistence service returns a list of data nodes'
mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
- "//state[@cm-handle-state='ADVISED']", OMIT_DESCENDANTS) >> sampleDataNodes
+ "//state[@cm-handle-state='ADVISED']", OMIT_DESCENDANTS, 0) >> sampleDataNodes
when: 'cm handles are fetched by state'
def result = objectUnderTest.queryCmHandleIdsByState(cmHandleState)
then: 'the returned result matches the result from the persistence service'
@@ -171,7 +171,7 @@ class CmHandleQueryServiceImplSpec extends Specification {
def 'Retrieve Cm Handles By Operational Sync State : UNSYNCHRONIZED'() {
given: 'cps data service returns a list of data nodes'
mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
- '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
+ '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS, 0) >> sampleDataNodes
when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state'
def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED)
then: 'the returned result is a list of data nodes returned by cps data service'
@@ -184,7 +184,7 @@ class CmHandleQueryServiceImplSpec extends Specification {
def cpsPath = "//state[@cm-handle-state='LOCKED']"
and: 'cps data service returns a valid data node for cm handle ancestor'
mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
- cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS)
+ cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS, 0)
>> Arrays.asList(cmHandleDataNode)
when: 'get cm handles by cps path is invoked'
def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS)
@@ -198,7 +198,7 @@ class CmHandleQueryServiceImplSpec extends Specification {
def cpsPath = "//cm-handles[@alternate-id='1']"
and: 'cps data service returns a valid data node'
mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
- cpsPath, INCLUDE_ALL_DESCENDANTS)
+ cpsPath, INCLUDE_ALL_DESCENDANTS, 0)
>> Arrays.asList(cmHandleDataNode)
when: 'get cm handles by cps path is invoked'
def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS)
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
index 2ba8505aaa..5619c5ac57 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
@@ -164,7 +164,7 @@ class InventoryPersistenceImplSpec extends Specification {
def "Retrieve multiple YangModelCmHandles using cm handle references"() {
given: 'the cps data service returns 2 data nodes from the DMI registry'
def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId, 'alternate-id':alternateId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2,'alternate-id':alternateId2])]
- mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, INCLUDE_ALL_DESCENDANTS) >> dataNodes
+ mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, INCLUDE_ALL_DESCENDANTS, _) >> dataNodes
when: 'retrieving the yang modelled cm handle'
def results = objectUnderTest.getYangModelCmHandlesFromCmHandleReferences([cmHandleId, cmHandleId2])
then: 'verify both have returned and cmhandleIds are correct'
@@ -311,7 +311,7 @@ class InventoryPersistenceImplSpec extends Specification {
def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']'
def expectedDataNode = new DataNode(xpath: expectedXPath, leaves: [id: 'id', alternateId: 'alternate id'])
and: 'query service is invoked with expected xpath'
- mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS) >> [expectedDataNode]
+ mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS, _) >> [expectedDataNode]
mockYangDataConverter.toYangModelCmHandle(expectedDataNode) >> new YangModelCmHandle(id: 'id')
expect: 'getting the yang model cm handle'
assert objectUnderTest.getYangModelCmHandleByAlternateId('alternate id') == new YangModelCmHandle(id: 'id')
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
index 4ede0d9c90..2c896dc3cd 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022-2024 Nordix Foundation
+ * Modifications Copyright (C) 2025 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +40,9 @@ import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
@NoArgsConstructor(access = AccessLevel.PACKAGE)
public class CpsPathUtil {
+ public static final String ROOT_NODE_XPATH = "/";
+ public static final String NO_PARENT_PATH = "";
+
/**
* Returns a normalized xpath path query.
*
@@ -46,6 +50,9 @@ public class CpsPathUtil {
* @return a normalized xpath String.
*/
public static String getNormalizedXpath(final String xpathSource) {
+ if (ROOT_NODE_XPATH.equals(xpathSource)) {
+ return NO_PARENT_PATH;
+ }
return getCpsPathBuilder(xpathSource).build().getNormalizedXpath();
}
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
index 29bb3c7b58..03aecc2acd 100644
--- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022-2024 Nordix Foundation
+ * Modifications Copyright (C) 2025 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +25,11 @@ import spock.lang.Specification
class CpsPathUtilSpec extends Specification {
+ def 'Normalized xpath for root.'() {
+ expect: 'root node xpath is parsed'
+ assert CpsPathUtil.getNormalizedXpath('/') == ''
+ }
+
def 'Normalized xpaths for list index values using #scenario'() {
when: 'xpath with #scenario is parsed'
def result = CpsPathUtil.getNormalizedXpath(xpath)
@@ -36,7 +42,7 @@ class CpsPathUtilSpec extends Specification {
'single quotes' | "/parent/child[@common-leaf-name='123']"
}
- def 'Normalized parent paths of absolute paths'() {
+ def 'Normalized parent paths of absolute paths.'() {
when: 'a given cps path is parsed'
def result = CpsPathUtil.getNormalizedParentXpath(cpsPath)
then: 'the result is the expected parent path'
@@ -54,7 +60,7 @@ class CpsPathUtilSpec extends Specification {
'/parent/child/name[text()="value"]' || '/parent'
}
- def 'Normalized parent paths of descendant paths'() {
+ def 'Normalized parent paths of descendant paths.'() {
when: 'a given cps path is parsed'
def result = CpsPathUtil.getNormalizedParentXpath(cpsPath)
then: 'the result is the expected parent path'
@@ -72,7 +78,7 @@ class CpsPathUtilSpec extends Specification {
'//parent/child/name[text()="value"]' || '//parent'
}
- def 'Get node ID sequence for given xpath'() {
+ def 'Get node ID sequence for given xpath with #scenario.'() {
when: 'a given xpath with #scenario is parsed'
def result = CpsPathUtil.getXpathNodeIdSequence(xpath)
then: 'the result is the expected node ID sequence'
@@ -89,7 +95,7 @@ class CpsPathUtilSpec extends Specification {
'does not include ancestor node' | '/parent/child/ancestor::grandparent' || ["parent","child"]
}
- def 'Recognizing (absolute) xpaths to List elements'() {
+ def 'Recognizing (absolute) xpaths to List elements.'() {
expect: 'check for list returns the correct values'
assert CpsPathUtil.isPathToListElement(xpath) == expectList
where: 'the following xpaths are used'
@@ -101,7 +107,7 @@ class CpsPathUtilSpec extends Specification {
'/parent/ancestor::grandparent[@id=1]' || false
}
- def 'Parsing Exception'() {
+ def 'Parsing Exception.'() {
when: 'a invalid xpath is parsed'
CpsPathUtil.getNormalizedXpath('///')
then: 'a path parsing exception is thrown'
diff --git a/cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java b/cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java
new file mode 100644
index 0000000000..1e3410c7f4
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java
@@ -0,0 +1,83 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 TechMahindra Ltd.
+ * ================================================================================
+ * 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.api;
+
+import java.util.Collection;
+import java.util.Map;
+import org.onap.cps.api.model.Anchor;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.utils.ContentType;
+
+public interface DataNodeFactory {
+
+ /**
+ * Create data nodes using an anchor, xpath, and JSON/XML string.
+ *
+ * @param anchor name of Anchor sharing same schema structure as the JSON/XML string
+ * @param xpath xpath of the data node
+ * @param nodeData JSON/XML data string
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithAnchorXpathAndNodeData(Anchor anchor, String xpath,
+ String nodeData, ContentType contentType);
+
+ /**
+ * Create data nodes using an anchor, parent data node xpath, and JSON/XML string.
+ *
+ * @param anchor name of Anchor sharing same schema structure as the JSON/XML string
+ * @param parentNodeXpath xpath of the parent data node
+ * @param nodeData JSON/XML data string
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithAnchorParentXpathAndNodeData(Anchor anchor,
+ String parentNodeXpath,
+ String nodeData,
+ ContentType contentType);
+
+ /**
+ * Create data nodes using a map of xpath to JSON/XML data, and anchor name.
+ *
+ * @param anchor name of Anchor sharing same schema structure as the JSON/XML string
+ * @param nodesData map of xpath and node JSON/XML data
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithAnchorAndXpathToNodeData(Anchor anchor,
+ Map<String, String> nodesData,
+ ContentType contentType);
+
+ /**
+ * Create data nodes using a map of YANG resource name to content, xpath, and JSON/XML string.
+ *
+ * @param yangResourcesNameToContentMap map of YANG resource name to content
+ * @param xpath xpath of the data node
+ * @param nodeData JSON/XML data string
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithYangResourceXpathAndNodeData(
+ Map<String, String> yangResourcesNameToContentMap,
+ String xpath, String nodeData,
+ ContentType contentType);
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java
index 9f70ac9132..ab6f7a2df8 100644
--- a/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,9 @@
package org.onap.cps.impl;
+import static org.onap.cps.cpspath.parser.CpsPathUtil.NO_PARENT_PATH;
+import static org.onap.cps.cpspath.parser.CpsPathUtil.ROOT_NODE_XPATH;
+
import io.micrometer.core.annotation.Timed;
import java.io.Serializable;
import java.time.OffsetDateTime;
@@ -39,7 +42,7 @@ import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsAnchorService;
import org.onap.cps.api.CpsDataService;
import org.onap.cps.api.CpsDeltaService;
-import org.onap.cps.api.exceptions.DataValidationException;
+import org.onap.cps.api.DataNodeFactory;
import org.onap.cps.api.model.Anchor;
import org.onap.cps.api.model.DataNode;
import org.onap.cps.api.model.DeltaReport;
@@ -54,7 +57,6 @@ import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
import org.onap.cps.utils.PrefixResolver;
import org.onap.cps.utils.YangParser;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.springframework.stereotype.Service;
@Service
@@ -62,14 +64,12 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class CpsDataServiceImpl implements CpsDataService {
- private static final String ROOT_NODE_XPATH = "/";
- private static final String PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH = "";
private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
- private static final String NO_DATA_NODES = "No data nodes.";
private final CpsDataPersistenceService cpsDataPersistenceService;
private final CpsDataUpdateEventsService cpsDataUpdateEventsService;
private final CpsAnchorService cpsAnchorService;
+ private final DataNodeFactory dataNodeFactory;
private final CpsValidator cpsValidator;
private final YangParser yangParser;
@@ -90,8 +90,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, ROOT_NODE_XPATH, nodeData, contentType);
cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp);
}
@@ -110,8 +110,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
}
@@ -124,9 +124,9 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> listElementDataNodeCollection =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
- if (isRootNodeXpath(parentNodeXpath)) {
+ final Collection<DataNode> listElementDataNodeCollection = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
+ if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
} else {
cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
@@ -163,8 +163,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodesInPatch =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> dataNodesInPatch = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
.collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
@@ -180,8 +180,9 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodeUpdates =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
+ final Collection<DataNode> dataNodeUpdates = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, dataNodeUpdatesAsJson,
+ ContentType.JSON);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(anchor, dataNodeUpdate);
}
@@ -256,8 +257,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
}
@@ -266,13 +267,14 @@ public class CpsDataServiceImpl implements CpsDataService {
@Timed(value = "cps.data.service.datanode.descendants.batch.update",
description = "Time taken to update a batch of data nodes and descendants")
public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
- final Map<String, String> nodeDataPerXPath,
+ final Map<String, String> nodeDataPerParentNodeXPath,
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes = buildDataNodesWithParentNodeXpath(anchor, nodeDataPerXPath, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorAndXpathToNodeData(anchor, nodeDataPerParentNodeXPath, contentType);
cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
- nodeDataPerXPath.keySet().forEach(nodeXpath ->
+ nodeDataPerParentNodeXPath.keySet().forEach(nodeXpath ->
sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp));
}
@@ -283,8 +285,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> newListElements =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> newListElements = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
}
@@ -362,7 +364,7 @@ public class CpsDataServiceImpl implements CpsDataService {
public void validateData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String nodeData, final ContentType contentType) {
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH :
+ final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? NO_PARENT_PATH :
CpsPathUtil.getNormalizedXpath(parentNodeXpath);
yangParser.validateData(contentType, nodeData, anchor, xpath);
}
@@ -373,8 +375,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final Collection<DataNode> sourceDataNodesRebuilt = new ArrayList<>();
if (sourceDataNodes != null) {
final String sourceDataNodesAsJson = getDataNodesAsJson(sourceAnchor, sourceDataNodes);
- sourceDataNodesRebuilt.addAll(
- buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
+ sourceDataNodesRebuilt.addAll(dataNodeFactory.createDataNodesWithAnchorXpathAndNodeData(
+ sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
}
return sourceDataNodesRebuilt;
}
@@ -383,10 +385,12 @@ public class CpsDataServiceImpl implements CpsDataService {
final Map<String, String> yangResourceContentPerName,
final String targetData) {
if (yangResourceContentPerName.isEmpty()) {
- return buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, targetData, ContentType.JSON);
+ return dataNodeFactory
+ .createDataNodesWithAnchorXpathAndNodeData(sourceAnchor, xpath, targetData, ContentType.JSON);
} else {
- return buildDataNodesWithYangResourceAndXpath(yangResourceContentPerName, xpath,
- targetData, ContentType.JSON);
+ return dataNodeFactory
+ .createDataNodesWithYangResourceXpathAndNodeData(yangResourceContentPerName, xpath,
+ targetData, ContentType.JSON);
}
}
@@ -415,105 +419,6 @@ public class CpsDataServiceImpl implements CpsDataService {
return prefixToDataNodes;
}
- private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor,
- final Map<String, String> nodesJsonData,
- final ContentType contentType) {
- final Collection<DataNode> dataNodes = new ArrayList<>();
- for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
- dataNodes.addAll(buildDataNodesWithParentNodeXpath(anchor, nodeJsonData.getKey(),
- nodeJsonData.getValue(), contentType));
- }
- return dataNodes;
- }
-
- private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor, final String parentNodeXpath,
- final String nodeData, final ContentType contentType) {
-
- if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
- final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
- anchor, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
- }
- return dataNodes;
- }
- final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
- final ContainerNode containerNode =
- yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withParentNodeXpath(normalizedParentNodeXpath)
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
- }
- return dataNodes;
- }
-
- private Collection<DataNode> buildDataNodesWithParentNodeXpath(
- final Map<String, String> yangResourceContentPerName, final String xpath,
- final String nodeData, final ContentType contentType) {
-
- if (isRootNodeXpath(xpath)) {
- final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
- yangResourceContentPerName, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
- }
- return dataNodes;
- }
- final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(xpath);
- final ContainerNode containerNode =
- yangParser.parseData(contentType, nodeData, yangResourceContentPerName, normalizedParentNodeXpath);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withParentNodeXpath(normalizedParentNodeXpath)
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
- }
- return dataNodes;
- }
-
- private Collection<DataNode> buildDataNodesWithAnchorAndXpath(final Anchor anchor, final String xpath,
- final String nodeData,
- final ContentType contentType) {
-
- if (!isRootNodeXpath(xpath)) {
- final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
- if (parentNodeXpath.isEmpty()) {
- return buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(anchor, xpath, nodeData, contentType);
- }
-
- private Collection<DataNode> buildDataNodesWithYangResourceAndXpath(
- final Map<String, String> yangResourceContentPerName, final String xpath,
- final String nodeData, final ContentType contentType) {
- if (!isRootNodeXpath(xpath)) {
- final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
- if (parentNodeXpath.isEmpty()) {
- return buildDataNodesWithParentNodeXpath(yangResourceContentPerName, ROOT_NODE_XPATH,
- nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(yangResourceContentPerName, parentNodeXpath,
- nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(yangResourceContentPerName, xpath, nodeData, contentType);
- }
-
- private static boolean isRootNodeXpath(final String xpath) {
- return ROOT_NODE_XPATH.equals(xpath);
- }
-
private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
diff --git a/cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java b/cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java
new file mode 100644
index 0000000000..76db887c8e
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java
@@ -0,0 +1,107 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 TechMahindra Ltd.
+ * ================================================================================
+ * 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.impl;
+
+import static org.onap.cps.cpspath.parser.CpsPathUtil.NO_PARENT_PATH;
+import static org.onap.cps.cpspath.parser.CpsPathUtil.ROOT_NODE_XPATH;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.DataNodeFactory;
+import org.onap.cps.api.exceptions.DataValidationException;
+import org.onap.cps.api.model.Anchor;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.utils.ContentType;
+import org.onap.cps.utils.YangParser;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DataNodeFactoryImpl implements DataNodeFactory {
+
+ private final YangParser yangParser;
+
+ @Override
+ public Collection<DataNode> createDataNodesWithAnchorAndXpathToNodeData(final Anchor anchor,
+ final Map<String, String> nodesDataPerParentNodeXpath,
+ final ContentType contentType) {
+ final Collection<DataNode> dataNodes = new ArrayList<>();
+ for (final Map.Entry<String, String> nodeDataToParentNodeXpath : nodesDataPerParentNodeXpath.entrySet()) {
+ dataNodes.addAll(createDataNodesWithAnchorParentXpathAndNodeData(anchor, nodeDataToParentNodeXpath.getKey(),
+ nodeDataToParentNodeXpath.getValue(), contentType));
+ }
+ return dataNodes;
+ }
+
+ @Override
+ public Collection<DataNode> createDataNodesWithAnchorXpathAndNodeData(final Anchor anchor, final String xpath,
+ final String nodeData,
+ final ContentType contentType) {
+ final String xpathToBuildNodes = isRootNodeXpath(xpath) ? NO_PARENT_PATH :
+ CpsPathUtil.getNormalizedParentXpath(xpath);
+ final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, xpathToBuildNodes);
+ return convertToDataNodes(xpathToBuildNodes, containerNode);
+ }
+
+ @Override
+ public Collection<DataNode> createDataNodesWithAnchorParentXpathAndNodeData(final Anchor anchor,
+ final String parentNodeXpath,
+ final String nodeData,
+ final ContentType contentType) {
+
+ final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
+ final ContainerNode containerNode =
+ yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
+ return convertToDataNodes(normalizedParentNodeXpath, containerNode);
+ }
+
+ @Override
+ public Collection<DataNode> createDataNodesWithYangResourceXpathAndNodeData(
+ final Map<String, String> yangResourceContentPerName,
+ final String xpath, final String nodeData,
+ final ContentType contentType) {
+ final String normalizedParentNodeXpath = isRootNodeXpath(xpath) ? NO_PARENT_PATH :
+ CpsPathUtil.getNormalizedParentXpath(xpath);
+ final ContainerNode containerNode =
+ yangParser.parseData(contentType, nodeData, yangResourceContentPerName, normalizedParentNodeXpath);
+ return convertToDataNodes(normalizedParentNodeXpath, containerNode);
+ }
+
+ private static Collection<DataNode> convertToDataNodes(final String normalizedParentNodeXpath,
+ final ContainerNode containerNode) {
+ final Collection<DataNode> dataNodes = new DataNodeBuilder()
+ .withParentNodeXpath(normalizedParentNodeXpath)
+ .withContainerNode(containerNode)
+ .buildCollection();
+ if (dataNodes.isEmpty()) {
+ throw new DataValidationException("No Data Nodes", "The request did not return any data nodes for xpath "
+ + normalizedParentNodeXpath);
+ }
+ return dataNodes;
+ }
+
+ private static boolean isRootNodeXpath(final String xpath) {
+ return ROOT_NODE_XPATH.equals(xpath);
+ }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy
index abcda6c696..6b90e557dc 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
- * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,9 +68,10 @@ class CpsDataServiceImplSpec extends Specification {
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockPrefixResolver = Mock(PrefixResolver)
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService,
- mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
+ dataNodeFactory, mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
def loggingListAppender
@@ -107,8 +108,9 @@ class CpsDataServiceImplSpec extends Specification {
def 'Saving #scenario data.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
- when: 'save data method is invoked with test-tree #scenario data'
+ and: 'JSON/XML data is fetched from resource file'
def data = TestUtils.getResourceFileContent(dataFile)
+ when: 'save data method is invoked with test-tree #scenario data'
objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
then: 'the persistence service method is invoked with correct parameters'
1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
@@ -131,7 +133,7 @@ class CpsDataServiceImplSpec extends Specification {
assert exceptionThrown.message.startsWith(expectedMessage)
where: 'given parameters'
scenario | invalidData | contentType || expectedMessage
- 'no data nodes' | '{}' | ContentType.JSON || 'No data nodes'
+ 'no data nodes' | '{}' | ContentType.JSON || 'No Data Nodes'
'invalid json' | '{invalid json' | ContentType.JSON || 'Data Validation Failed'
'invalid xml' | '<invalid xml' | ContentType.XML || 'Data Validation Failed'
}
@@ -139,8 +141,9 @@ class CpsDataServiceImplSpec extends Specification {
def 'Saving list element data fragment under Root node.'() {
given: 'schema set for given anchor and dataspace references bookstore model'
setupSchemaSetMocks('bookstore.yang')
- when: 'save data method is invoked with list element json data'
+ and: 'JSON data associated with bookstore model'
def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}'
+ when: 'save data method is invoked with list element json data'
objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp, ContentType.JSON)
then: 'the persistence service method is invoked with correct parameters'
1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
@@ -159,8 +162,8 @@ class CpsDataServiceImplSpec extends Specification {
def 'Saving child 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 test-tree json data'
def jsonData = '{"branch": [{"name": "New"}]}'
+ when: 'save data method is invoked with test-tree json data'
objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
@@ -169,7 +172,7 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
}
- def 'Saving list element data fragment under existing JSON/XML node.'() {
+ def 'Saving list element data fragment under existing #scenario .'() {
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 data'
@@ -187,12 +190,13 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
where:
- data | contentType
- '{"branch": [{"name": "A"}, {"name": "B"}]}' | ContentType.JSON
- '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML
+ scenario | data | contentType
+ 'JSON data' | '{"branch": [{"name": "A"}, {"name": "B"}]}' | ContentType.JSON
+ 'XML data' | '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML
+
}
- def 'Saving empty list element data fragment for JSON/XML data.'() {
+ def 'Saving empty list element data fragment for #scenario.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
when: 'save data method is invoked with an empty list'
@@ -200,9 +204,9 @@ class CpsDataServiceImplSpec extends Specification {
then: 'invalid data exception is thrown'
thrown(DataValidationException)
where:
- data | contentType
- '{"branch": []}' | ContentType.JSON
- '<test-tree><branch></branch></test-tree>' | ContentType.XML
+ scenario | data | contentType
+ 'JSON data' | '{"branch": []}' | ContentType.JSON
+ 'XML data' | '<test-tree><branch></branch></test-tree>' | ContentType.XML
}
def 'Get all data nodes #scenario.'() {
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy
new file mode 100644
index 0000000000..082fb33a61
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy
@@ -0,0 +1,196 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 TechMahindra Ltd.
+ * ================================================================================
+ * 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.impl
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.core.read.ListAppender
+import org.onap.cps.TestUtils
+import org.onap.cps.api.CpsAnchorService
+import org.onap.cps.api.exceptions.DataValidationException
+import org.onap.cps.api.model.Anchor
+import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.YangParser
+import org.onap.cps.utils.YangParserHelper
+import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
+import org.onap.cps.yang.YangTextSchemaSourceSet
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import org.slf4j.LoggerFactory
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import spock.lang.Specification
+
+class DataNodeFactorySpec extends Specification {
+
+ def mockCpsAnchorService = Mock(CpsAnchorService)
+ def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
+ def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
+ def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
+ def objectUnderTest = new DataNodeFactoryImpl(yangParser)
+
+ def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
+ def loggingListAppender
+ def applicationContext = new AnnotationConfigApplicationContext()
+
+ def dataspaceName = 'some-dataspace'
+ def anchorName = 'some-anchor'
+ def schemaSetName = 'some-schema-set'
+ def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
+
+ def setup() {
+ mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor
+ logger.setLevel(Level.DEBUG)
+ loggingListAppender = new ListAppender()
+ logger.addAppender(loggingListAppender)
+ loggingListAppender.start()
+ applicationContext.refresh()
+ }
+
+ void cleanup() {
+ ((Logger) LoggerFactory.getLogger(DataNodeFactoryImpl.class)).detachAndStopAllAppenders()
+ applicationContext.close()
+ }
+
+ def 'Create data nodes using anchor and map of xpath to #scenario'() {
+ given:'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithAnchorAndXpathToNodeData(anchor, xpathToNodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == expectedDataNodes
+ and: 'data nodes have expected xpaths'
+ dataNodes.stream().map { it.getXpath() }.toList().containsAll(expectedXpaths)
+ where: 'the following data was used'
+ scenario | xpathToNodeData | contentType || expectedDataNodes | expectedXpaths
+ 'JSON Data' | ['/' : "{'test-tree': {'branch': []}}", '/test-tree' : "{'branch': [{'name':'Name'}]}"] | ContentType.JSON || 2 | ['/test-tree', "/test-tree/branch[@name='Name']"]
+ 'XML Data' | ['/test-tree' : '<branch><name>Name</name></branch>'] | ContentType.XML || 1 | ["/test-tree/branch[@name='Name']"]
+ }
+
+ def 'Create data nodes using anchor, xpath and #scenario string'() {
+ given:'xpath, json string and schema set for given anchor and dataspace references test-tree model'
+ def xpath = '/'
+ def nodeData = TestUtils.getResourceFileContent(data)
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithAnchorXpathAndNodeData(anchor, xpath, nodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == 1
+ and: 'data nodes have expected xpaths'
+ dataNodes[0].getXpath() == '/test-tree'
+ where: 'the following data was used'
+ scenario | data | contentType
+ 'JSON' | 'test-tree.json' | ContentType.JSON
+ 'XML' | 'test-tree.xml' | ContentType.XML
+ }
+
+ def 'Building data nodes using anchor, xpath and #scenario'() {
+ given:'xpath, invalid json string and schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ objectUnderTest.createDataNodesWithAnchorXpathAndNodeData(anchor, '/test-tree', invalidData, contentType)
+ then: 'expected number of data nodes are created'
+ def exceptionThrown = thrown(DataValidationException)
+ assert exceptionThrown.message.startsWith(expectedMessage)
+ where:
+ scenario | invalidData | contentType || expectedMessage
+ 'no data nodes' | '{}' | ContentType.JSON || 'No Data Nodes'
+ 'invalid json' | '{invalid json' | ContentType.JSON || 'Data Validation Failed'
+ 'invalid xml' | '<invalid xml' | ContentType.XML || 'Data Validation Failed'
+ }
+
+ def 'Create data nodes using anchor, parent node xpath and #scenario string'() {
+ given:'parent node xpath, json string and schema set for given anchor and dataspace references test-tree model'
+ def parentXpath = '/test-tree'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentXpath, nodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == 1
+ and: 'data nodes have expected xpaths'
+ dataNodes[0].getXpath() == "/test-tree/branch[@name='A']"
+ where: 'the following data was used'
+ scenario | nodeData | contentType
+ 'JSON' | '{"branch": [{"name": "A"}]}' | ContentType.JSON
+ 'XML' | '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch></test-tree>' | ContentType.XML
+ }
+
+ def 'Create data nodes using anchor, parent node xpath and invalid #scenario string'() {
+ given:'parent node xpath, invalid json string and schema set for given anchor and dataspace references test-tree model'
+ def parentXpath = '/test-tree'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ objectUnderTest.createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentXpath, invalidData, contentType)
+ then: 'expected number of data nodes are created'
+ def exceptionThrown = thrown(DataValidationException)
+ assert exceptionThrown.message.startsWith(expectedMessage)
+ where:
+ scenario | invalidData | contentType || expectedMessage
+ 'no data nodes' | '{"branch": []}' | ContentType.JSON || 'No Data Nodes'
+ 'invalid json' | '<test-tree><branch></branch></test-tree>' | ContentType.JSON || 'Data Validation Failed'
+ }
+
+ def 'Create data nodes using schema, xpath and #scenario string'() {
+ given:'xpath, json string and schema set for given anchor and dataspace references bookstore model'
+ def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ setupSchemaSetMocksForDelta(yangResourcesNameToContentMap)
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithYangResourceXpathAndNodeData(yangResourcesNameToContentMap, '/', nodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == 1
+ and: 'data nodes have expected xpath'
+ dataNodes[0].getXpath() == '/bookstore'
+ where: 'the following data was used'
+ scenario | nodeData | contentType
+ 'JSON' | '{"bookstore":{"bookstore-name":"Easons"}}' | ContentType.JSON
+ 'XML' | "<bookstore xmlns=\"org:onap:ccsdk:sample\"><bookstore-name>Easons</bookstore-name></bookstore>" | ContentType.XML
+ }
+
+ def 'Create data nodes using schema, xpath and invalid #scenario string'() {
+ given:'xpath, invalid json string and schema set for given anchor and dataspace references bookstore model'
+ def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ setupSchemaSetMocksForDelta(yangResourcesNameToContentMap)
+ when: 'attempt to create data nodes'
+ objectUnderTest.createDataNodesWithYangResourceXpathAndNodeData(yangResourcesNameToContentMap, '/', invalidData, contentType)
+ then: 'expected number of data nodes are created'
+ def exceptionThrown = thrown(DataValidationException)
+ assert exceptionThrown.message.startsWith(expectedMessage)
+ where:
+ scenario | invalidData | contentType || expectedMessage
+ 'no json nodes' | '{}' | ContentType.JSON || 'No Data Nodes'
+ 'no xml nodes' | '"<bookstore xmlns=\"org:onap:ccsdk:sample\"/>' | ContentType.XML || 'Data Validation Failed'
+ 'invalid json' | '{invalid' | ContentType.JSON || 'Data Validation Failed'
+ 'invalid xml' | '<invalid' | ContentType.XML || 'Data Validation Failed'
+ }
+
+ def setupSchemaSetMocks(String... yangResources) {
+ def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+ mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+ }
+
+ def setupSchemaSetMocksForDelta(Map<String, String> yangResourcesNameToContentMap) {
+ def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+ mockTimedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap) >> mockYangTextSchemaSourceSet
+ mockYangTextSchemaSourceSetCache.get(_, _) >> mockYangTextSchemaSourceSet
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
+ mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+ }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy
index db5b4f104e..f91570136c 100755
--- a/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2025 Nordix Foundation.
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,8 @@ class E2ENetworkSliceSpec extends Specification {
mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator,
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, dataNodeFactory, mockCpsValidator,
yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml
index 3b7cc6063a..49646731e2 100644
--- a/docker-compose/docker-compose.yml
+++ b/docker-compose/docker-compose.yml
@@ -94,7 +94,7 @@ services:
test: wget -q -O - http://localhost:8080/actuator/health/readiness | grep -q '{"status":"UP"}' || exit 1
interval: 10s
timeout: 10s
- retries: 3
+ retries: 10
start_period: 60s
nginx:
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
index 9b79af95ff..bb69f2f544 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
@@ -272,16 +272,26 @@ abstract class CpsIntegrationSpecBase extends Specification {
}
def registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles, offset) {
- registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, ModuleNameStrategy.UNIQUE)
+ registerSequenceOfCmHandles(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, ModuleNameStrategy.UNIQUE, { id -> "alt=${id}" })
}
- def registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, ModuleNameStrategy moduleNameStrategy ) {
+ def registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, ModuleNameStrategy moduleNameStrategy) {
+ registerSequenceOfCmHandles(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, moduleNameStrategy, { id -> "alt=${id}" })
+ }
+
+ def registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, ModuleNameStrategy moduleNameStrategy, Closure<String> alternateIdGenerator) {
+ registerSequenceOfCmHandles(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, moduleNameStrategy, alternateIdGenerator)
+ }
+
+ def registerSequenceOfCmHandles(dmiPlugin, moduleSetTag, numberOfCmHandles, offset, ModuleNameStrategy moduleNameStrategy, Closure<String> alternateIdGenerator) {
def cmHandles = []
def id = offset
def modulePrefix = moduleNameStrategy.OVERLAPPING.equals(moduleNameStrategy) ? 'same' : moduleSetTag
- def moduleReferences = (1..200).collect { "${modulePrefix}Module${it}" }
+ def moduleReferences = (1..200).collect { "${modulePrefix}Module${it}" }
+
(1..numberOfCmHandles).each {
- def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: "ch-${id}", moduleSetTag: moduleSetTag, alternateId: "alt=${id}")
+ def alternateId = alternateIdGenerator(id)
+ def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: "ch-${id}", moduleSetTag: moduleSetTag, alternateId: alternateId)
cmHandles.add(ncmpServiceCmHandle)
dmiDispatcher1.moduleNamesPerCmHandleId[ncmpServiceCmHandle.cmHandleId] = moduleReferences
dmiDispatcher2.moduleNamesPerCmHandleId[ncmpServiceCmHandle.cmHandleId] = moduleReferences
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/WriteDataJobPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/WriteDataJobPerfTest.groovy
new file mode 100644
index 0000000000..27ff155471
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/WriteDataJobPerfTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.ncmp
+
+import org.onap.cps.integration.ResourceMeter
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.datajobs.DataJobService
+import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata
+import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest
+import org.onap.cps.ncmp.api.datajobs.models.WriteOperation
+import org.springframework.beans.factory.annotation.Autowired
+
+/**
+ * This test does not depend on common performance test data. Hence it just extends the integration spec base.
+ */
+class WriteDataJobPerfTest extends CpsIntegrationSpecBase {
+
+ @Autowired
+ DataJobService dataJobService
+
+ def resourceMeter = new ResourceMeter()
+
+ def populateDataJobWriteRequests(int numberOfWriteOperations) {
+ def writeOperations = []
+ for (int i = 1; i <= numberOfWriteOperations; i++) {
+ def basePath = "/SubNetwork=Europe/SubNetwork=Ireland/MeContext=MyRadioNode${i}/ManagedElement=MyManagedElement${i}"
+ writeOperations.add(new WriteOperation("${basePath}/SomeChild=child-1", 'operation1', '1', null))
+ writeOperations.add(new WriteOperation("${basePath}/SomeChild=child-2", 'operation2', '2', null))
+ writeOperations.add(new WriteOperation(basePath, 'operation3', '3', null))
+ }
+ return new DataJobWriteRequest(writeOperations)
+ }
+
+ def 'Performance test for writeDataJob method'() {
+ given: 'register 1,000 cm handles (with alternative ids)'
+ registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, 'tagA', 1000, 1, ModuleNameStrategy.UNIQUE, { it -> "/SubNetwork=Europe/SubNetwork=Ireland/MeContext=MyRadioNode${it}/ManagedElement=MyManagedElement${it}" })
+ def authorization = 'my authorization header'
+ def numberOfWriteOperations = 1000
+ def dataJobWriteRequest = populateDataJobWriteRequests(numberOfWriteOperations)
+ def myDataJobMetadata = new DataJobMetadata('d1', '', '')
+ def dataJobId = 'my-data-job-id'
+ when: 'sending a write job to NCMP with dynamically generated write operations'
+ resourceMeter.start()
+ dataJobService.writeDataJob(authorization, dataJobId, myDataJobMetadata, dataJobWriteRequest)
+ resourceMeter.stop()
+ then: 'record the result. Not asserted, just recorded in See https://lf-onap.atlassian.net/browse/CPS-2691'
+ println "*** CPS-2691 Execution time: ${resourceMeter.totalTimeInSeconds} seconds"
+ cleanup: 'deregister test cm handles'
+ deregisterSequenceOfCmHandles(DMI1_URL, 1000, 1)
+ }
+}
diff --git a/k6-tests/make-logs.sh b/k6-tests/make-logs.sh
new file mode 100644
index 0000000000..60976247e5
--- /dev/null
+++ b/k6-tests/make-logs.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Copyright 2025 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.
+#
+
+SERVICE_NAME="cps-and-ncmp"
+TIMESTAMP=$(date +"%Y%m%d%H%M%S")
+LOG_DIR="${WORKSPACE:-.}/logs"
+TEMP_DIR="$LOG_DIR/temp_$TIMESTAMP"
+ZIP_FILE="$LOG_DIR/${SERVICE_NAME}_logs_$TIMESTAMP.zip"
+
+mkdir -p "$LOG_DIR"
+mkdir -p "$TEMP_DIR"
+
+# Store logs for cps-and-ncmp containers to temp directory
+CONTAINER_IDS=$(docker ps --filter "name=$SERVICE_NAME" --format "{{.ID}}")
+for CONTAINER_ID in $CONTAINER_IDS; do
+ CONTAINER_NAME=$(docker inspect --format="{{.Name}}" "$CONTAINER_ID" | sed 's/\///g')
+ LOG_FILE="$TEMP_DIR/${CONTAINER_NAME}_logs_$TIMESTAMP.log"
+ docker logs "$CONTAINER_ID" > "$LOG_FILE"
+done
+
+# Zip the logs
+zip -r "$ZIP_FILE" "$TEMP_DIR"
+echo "Logs saved to $ZIP_FILE inside workspace"
+
+# Clean temp files
+rm -r "$TEMP_DIR"
+
+# Delete logs older than 2 weeks
+find "$LOG_DIR" -name "${SERVICE_NAME}_logs_*.zip" -mtime +14 -delete
diff --git a/k6-tests/ncmp/common/cmhandle-crud.js b/k6-tests/ncmp/common/cmhandle-crud.js
index 285028f13c..3b6c3ff7b7 100644
--- a/k6-tests/ncmp/common/cmhandle-crud.js
+++ b/k6-tests/ncmp/common/cmhandle-crud.js
@@ -51,19 +51,30 @@ export function waitForAllCmHandlesToBeReady() {
function createCmHandlePayload(cmHandleIds) {
return {
"dmiPlugin": DMI_PLUGIN_URL,
- "createdCmHandles": cmHandleIds.map((cmHandleId, index) => ({
- "cmHandle": cmHandleId,
- "alternateId": cmHandleId.replace('ch-', 'Subnetwork=Europe,ManagedElement='),
- "moduleSetTag": MODULE_SET_TAGS[index % MODULE_SET_TAGS.length],
- "cmHandleProperties": {
- "id": "123"
- },
- "publicCmHandleProperties": {
- "Color": "yellow",
- "Size": "small",
- "Shape": "cube"
- }
- })),
+ "createdCmHandles": cmHandleIds.map((cmHandleId, index) => {
+ // Ensure unique networkSegment within range 1-10
+ let networkSegmentId = Math.floor(Math.random() * 10) + 1; // Random between 1-10
+ let moduleTag = MODULE_SET_TAGS[index % MODULE_SET_TAGS.length];
+
+ return {
+ "cmHandle": cmHandleId,
+ "alternateId": cmHandleId.replace('ch-', 'Region=NorthAmerica,Segment='),
+ "moduleSetTag": moduleTag,
+ "cmHandleProperties": {
+ "segmentId": index + 1,
+ "networkSegment": `Region=NorthAmerica,Segment=${networkSegmentId}`, // Unique within range 1-10
+ "deviceIdentifier": `Element=RadioBaseStation_5G_${index + 1000}`, // Unique per cmHandle
+ "hardwareVersion": `HW-${moduleTag}`, // Shares uniqueness with moduleSetTag
+ "softwareVersion": `Firmware_${moduleTag}`, // Shares uniqueness with moduleSetTag
+ "syncStatus": "ACTIVE",
+ "nodeCategory": "VirtualNode"
+ },
+ "publicCmHandleProperties": {
+ "systemId": index + 1,
+ "systemName": "ncmp"
+ }
+ };
+ }),
};
}
diff --git a/k6-tests/ncmp/common/passthrough-crud.js b/k6-tests/ncmp/common/passthrough-crud.js
index a3d48fd590..eed1ab5190 100644
--- a/k6-tests/ncmp/common/passthrough-crud.js
+++ b/k6-tests/ncmp/common/passthrough-crud.js
@@ -67,7 +67,7 @@ export function legacyBatchRead(cmHandleIds) {
}
function getRandomCmHandleReference(useAlternateId) {
- const prefix = useAlternateId ? 'Subnetwork=Europe,ManagedElement=' : 'ch-';
+ const prefix = useAlternateId ? 'Region=NorthAmerica,Segment=' : 'ch-';
return `${prefix}${randomIntBetween(1, TOTAL_CM_HANDLES)}`;
}
diff --git a/k6-tests/ncmp/common/search-base.js b/k6-tests/ncmp/common/search-base.js
index af2caf71ec..af7d153416 100644
--- a/k6-tests/ncmp/common/search-base.js
+++ b/k6-tests/ncmp/common/search-base.js
@@ -51,7 +51,7 @@ const SEARCH_PARAMETERS_PER_SCENARIO = {
"cmHandleQueryParameters": [
{
"conditionName": "hasAllProperties",
- "conditionParameters": [{"Color": "yellow"}]
+ "conditionParameters": [{"systemName": "ncmp"}]
}
]
},
diff --git a/k6-tests/ncmp/common/utils.js b/k6-tests/ncmp/common/utils.js
index ee3e9c7b4b..57ab2ea17f 100644
--- a/k6-tests/ncmp/common/utils.js
+++ b/k6-tests/ncmp/common/utils.js
@@ -27,7 +27,7 @@ export const DMI_PLUGIN_URL = testConfig.hosts.dmiStubUrl;
export const CONTAINER_UP_TIME_IN_SECONDS = testConfig.hosts.containerUpTimeInSeconds;
export const LEGACY_BATCH_TOPIC_NAME = 'legacy_batch_topic';
export const TOTAL_CM_HANDLES = 50000;
-export const REGISTRATION_BATCH_SIZE = 100;
+export const REGISTRATION_BATCH_SIZE = 2000;
export const READ_DATA_FOR_CM_HANDLE_DELAY_MS = 300; // must have same value as in docker-compose.yml
export const WRITE_DATA_FOR_CM_HANDLE_DELAY_MS = 670; // must have same value as in docker-compose.yml
export const CONTENT_TYPE_JSON_PARAM = {'Content-Type': 'application/json'};
diff --git a/k6-tests/setup.sh b/k6-tests/setup.sh
index c01a0f6c60..d990475522 100755
--- a/k6-tests/setup.sh
+++ b/k6-tests/setup.sh
@@ -26,11 +26,11 @@ docker-compose \
--profile dmi-stub \
up --quiet-pull --detach --wait || exit 1
- if [[ "$testProfile" == "kpi" ]]; then
- ACTUATOR_PORT=8883
- elif [[ "$testProfile" == "endurance" ]]; then
- ACTUATOR_PORT=8884
- fi
+if [[ "$testProfile" == "kpi" ]]; then
+ ACTUATOR_PORT=8883
+elif [[ "$testProfile" == "endurance" ]]; then
+ ACTUATOR_PORT=8884
+fi
echo "Build information:"
curl --silent --show-error http://localhost:$ACTUATOR_PORT/actuator/info
diff --git a/k6-tests/teardown.sh b/k6-tests/teardown.sh
index 10db7ac7e0..7804a73286 100755
--- a/k6-tests/teardown.sh
+++ b/k6-tests/teardown.sh
@@ -18,11 +18,9 @@
echo '================================== docker info =========================='
docker ps -a
-echo '================================== CPS-NCMP Logs ========================'
-for CONTAINER_ID in $(docker ps --filter "name=cps-and-ncmp" --format "{{.ID}}"); do
- echo "CPS-NCMP Logs for container: $CONTAINER_ID"
- docker logs "$CONTAINER_ID"
-done
+# Zip and store logs for the containers
+chmod +x make-logs.sh
+./make-logs.sh
testProfile=$1
docker_compose_shutdown_cmd="docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub --project-name $testProfile down --volumes"
diff --git a/test-tools/generate-metrics-report.sh b/test-tools/generate-metrics-report.sh
index 7d94e5b49f..4d99adfdff 100755
--- a/test-tools/generate-metrics-report.sh
+++ b/test-tools/generate-metrics-report.sh
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright 2023 Nordix Foundation.
+# Copyright 2023-2025 Nordix Foundation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -75,13 +75,13 @@ function generate_report() {
grep --invert-match "^#" $TEMP_DIR/metrics-raw.txt | sort | sed 's/,[}]/}\t/' >$TEMP_DIR/metrics-all.txt
# Extract useful metrics.
- grep -E "^cps_|^spring_data_" $TEMP_DIR/metrics-all.txt >$TEMP_DIR/metrics-cps.txt
+ grep -E "^cps_|^spring_data_|^http_server_|^http_client_|^tasks_scheduled_execution_|^spring_kafka_template_|^spring_kafka_listener_" $TEMP_DIR/metrics-all.txt >$TEMP_DIR/metrics-cps.txt
# Extract into columns.
- grep "_count" $TEMP_DIR/metrics-cps.txt | sed 's/_count//' | cut -f 1 >$TEMP_DIR/column1.txt
- grep "_count" $TEMP_DIR/metrics-cps.txt | cut -f 2 >$TEMP_DIR/column2.txt
- grep "_sum" $TEMP_DIR/metrics-cps.txt | cut -f 2 >$TEMP_DIR/column3.txt
- grep "_max" $TEMP_DIR/metrics-cps.txt | cut -f 2 >$TEMP_DIR/column4.txt
+ grep "_count" $TEMP_DIR/metrics-cps.txt | sed 's/_count//' | cut -d ' ' -f 1 >$TEMP_DIR/column1.txt
+ grep "_count" $TEMP_DIR/metrics-cps.txt | cut -d ' ' -f 2 >$TEMP_DIR/column2.txt
+ grep "_sum" $TEMP_DIR/metrics-cps.txt | cut -d ' ' -f 2 >$TEMP_DIR/column3.txt
+ grep "_max" $TEMP_DIR/metrics-cps.txt | cut -d ' ' -f 2 >$TEMP_DIR/column4.txt
# Combine columns into report.
paste $TEMP_DIR/column{1,2,3,4}.txt >$TEMP_DIR/report.txt