aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java3
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java5
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java4
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java84
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java45
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy128
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java12
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java56
-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.java28
-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/ParameterizedCmHandleQueryService.java10
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java11
-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/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java26
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy27
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy10
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy4
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy16
-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
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java43
-rw-r--r--cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java96
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy231
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy229
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy16
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsFacade.java96
-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/api/parameters/FetchDescendantsOption.java32
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java252
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java97
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java31
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java107
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/DataMapper.java133
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java26
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy27
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy1
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy44
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy114
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy11
-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.groovy59
-rw-r--r--docker-compose/docker-compose.yml2
-rw-r--r--docs/cps-path.rst22
-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/cps/QueryPerfTest.groovy28
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy55
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/WriteDataJobPerfTest.groovy67
-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
57 files changed, 1793 insertions, 953 deletions
diff --git a/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java
index c1d65758c7..28ff7c307c 100644
--- a/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java
+++ b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2024 Nordix Foundation
+ * Copyright (C) 2024-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.
@@ -35,6 +35,7 @@ public class ArchitectureTestBase {
"java..",
"lombok..",
"org.apache..",
+ "org.aspectj..",
"org.mapstruct..",
"org.opendaylight..",
"org.slf4j..",
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
index 317f6b70e1..d7b38d1a46 100755
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2021-2024 Nordix Foundation
+ * Modifications Copyright (C) 2021-2025 Nordix Foundation
* Modifications Copyright (C) 2021 highstreet technologies GmbH
* Modifications Copyright (C) 2021-2022 Bell Canada
* ================================================================================
@@ -57,6 +57,7 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState;
import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties;
import org.onap.cps.ncmp.rest.util.CmHandleStateMapper;
+import org.onap.cps.ncmp.rest.util.CountCmHandleSearchExecution;
import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper;
import org.onap.cps.ncmp.rest.util.DeprecationHelper;
import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper;
@@ -256,6 +257,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
*/
@Override
@SuppressWarnings("deprecation") // mapOldConditionProperties method will be removed in Release 12
+ @CountCmHandleSearchExecution(methodName = "searchCmHandles", interfaceName = "CPS-E-05")
public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles(
final CmHandleQueryParameters cmHandleQueryParameters) {
final CmHandleQueryApiParameters cmHandleQueryApiParameters =
@@ -276,6 +278,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
* @return collection of cm handle ids
*/
@Override
+ @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-E-05")
public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters,
final Boolean outputAlternateId) {
final CmHandleQueryApiParameters cmHandleQueryApiParameters =
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
index 0e27ba9355..e412107753 100755
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021-2022 Bell Canada
- * Modifications Copyright (C) 2022-2024 Nordix Foundation
+ * Modifications Copyright (C) 2022-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.
@@ -37,6 +37,7 @@ import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
import org.onap.cps.ncmp.rest.model.CmHandlerRegistrationErrorResponse;
import org.onap.cps.ncmp.rest.model.DmiPluginRegistrationErrorResponse;
import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration;
+import org.onap.cps.ncmp.rest.util.CountCmHandleSearchExecution;
import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -60,6 +61,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
* @return list of cm handle IDs
*/
@Override
+ @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-NCMP-I-01")
public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters,
final Boolean outputAlternateId) {
final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java
new file mode 100644
index 0000000000..ecd248d89f
--- /dev/null
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java
@@ -0,0 +1,84 @@
+/*
+ * ============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.ncmp.rest.util;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import jakarta.validation.Valid;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
+import org.onap.cps.ncmp.rest.model.ConditionProperties;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class CmHandleSearchExecutionCounter {
+
+ private static final String NO_CONDITION = "NONE";
+
+ private final MeterRegistry meterRegistry;
+
+ /**
+ * Counts the number of invocations of the methods annotated with @CountCmHandleSearchExecution based on the search
+ * conditions dynamically added. If search is executed without condition then it would be tagged as NONE, otherwise
+ * the conditions are concatenated with _ as separator.
+ *
+ * @param joinPoint join point
+ * @param countCmHandleSearchExecution count the cm handle search conditions
+ */
+ @Before("@annotation(countCmHandleSearchExecution)")
+ public void cmHandleSearchExecutionCounter(final JoinPoint joinPoint,
+ final CountCmHandleSearchExecution countCmHandleSearchExecution) {
+ final Object[] args = joinPoint.getArgs();
+
+ if (args.length == 0 || !(args[0] instanceof CmHandleQueryParameters cmHandleQueryParameters)) {
+ log.warn("Method {} is missing required CmHandleQueryParameters argument", joinPoint.getSignature());
+ return;
+ }
+
+ final String conditionTag = Optional.ofNullable(cmHandleQueryParameters.getCmHandleQueryParameters())
+ .filter(conditionTypes -> !conditionTypes.isEmpty())
+ .map(CmHandleSearchExecutionCounter::conditionTag)
+ .orElse(NO_CONDITION);
+
+ Counter.builder("cm_handle_search_invocations")
+ .tag("method", countCmHandleSearchExecution.methodName())
+ .tag("cps-interface", countCmHandleSearchExecution.interfaceName())
+ .tag("conditions", conditionTag)
+ .description("Number of invocations of search methods based on condition types")
+ .register(meterRegistry)
+ .increment();
+ }
+
+ private static String conditionTag(final List<@Valid ConditionProperties> conditionTypes) {
+ return conditionTypes.stream().map(ConditionProperties::getConditionName).sorted()
+ .collect(Collectors.joining("_"));
+ }
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java
new file mode 100644
index 0000000000..27a0c4a87a
--- /dev/null
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java
@@ -0,0 +1,45 @@
+/*
+ * ============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.ncmp.rest.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CountCmHandleSearchExecution {
+
+ /**
+ * Capture the method name for which the number of invocations needs to be tracked.
+ *
+ * @return the search method name
+ */
+ String methodName();
+
+ /**
+ * Capture the CPS and NCMP interface name of the called method.
+ *
+ * @return the CPS and NCMP interface name
+ */
+ String interfaceName();
+}
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy
new file mode 100644
index 0000000000..bdadfc8689
--- /dev/null
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy
@@ -0,0 +1,128 @@
+/*
+ * ============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.ncmp.rest.util
+
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry
+import org.aspectj.lang.JoinPoint
+import org.aspectj.lang.Signature
+import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters
+import org.onap.cps.ncmp.rest.model.ConditionProperties
+import spock.lang.Specification
+
+class CmHandleSearchExecutionCounterSpec extends Specification {
+
+ def meterRegistry = new SimpleMeterRegistry()
+ def mockJoinPoint = Mock(JoinPoint)
+ def mockCountCmHandleSearchExecutionAnnotation = Mock(CountCmHandleSearchExecution)
+ def mockSignature = Mock(Signature)
+
+ def objectUnderTest = new CmHandleSearchExecutionCounter(meterRegistry)
+
+ def setup() {
+ mockCountCmHandleSearchExecutionAnnotation.methodName() >> 'testMethod'
+ mockCountCmHandleSearchExecutionAnnotation.interfaceName() >> 'testInterface'
+ mockSignature.toString() >> 'testSignature'
+ mockJoinPoint.getSignature() >> mockSignature
+ }
+
+ def 'should track search with conditions'() {
+ given: 'CmHandleQueryParameters with conditions'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ def condition1 = new ConditionProperties(conditionName: 'condition1')
+ def condition2 = new ConditionProperties(conditionName: 'condition2')
+ cmHandleQueryParameters.addCmHandleQueryParametersItem(condition1).addCmHandleQueryParametersItem(condition2)
+ and: 'joinPoint returns the parameters'
+ mockJoinPoint.getArgs() >> [cmHandleQueryParameters]
+ when: 'the annotated method is called'
+ objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation)
+ then: 'the counter should be registered'
+ def counter = findCounter('cm_handle_search_invocations', [
+ 'method' : 'testMethod',
+ 'cps-interface': 'testInterface',
+ 'conditions' : 'condition1_condition2'
+ ])
+ and: 'is incremented once'
+ assert counter.count() == 1
+ }
+
+ def 'should track search with no conditions as NONE'() {
+ given: 'empty CmHandleQueryParameters'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ and: 'joinPoint returns the parameters'
+ mockJoinPoint.getArgs() >> [cmHandleQueryParameters]
+ when: 'the annotated method is called'
+ objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation)
+ then: 'the counter should be registered with NONE tag'
+ def counter = findCounter('cm_handle_search_invocations', [
+ method : 'testMethod',
+ 'cps-interface': 'testInterface',
+ conditions : 'NONE'
+ ])
+ and: 'is incremented once'
+ assert counter.count() == 1
+ }
+
+ def 'should not create counter when args are empty'() {
+ given: 'joinPoint with empty args'
+ mockJoinPoint.getArgs() >> []
+ when: 'the aspect method is called'
+ objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation)
+ then: 'no counter should be registered'
+ assert meterRegistry.find('cm_handle_search_invocations').counters().isEmpty()
+ }
+
+ def 'should not create counter when first arg is not CmHandleQueryParameters'() {
+ given: 'joinPoint with non-CmHandleQueryParameters arg'
+ mockJoinPoint.getArgs() >> ['not a CmHandleQueryParameters']
+ when: 'the aspect method is called'
+ objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation)
+ then: 'no counter should be registered'
+ assert meterRegistry.find('cm_handle_search_invocations').counters().isEmpty()
+ }
+
+ def 'should sort condition names alphabetically'() {
+ given: 'CmHandleQueryParameters with unsorted conditions'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ def condition1 = new ConditionProperties(conditionName: 'zCondition')
+ def condition2 = new ConditionProperties(conditionName: 'aCondition')
+ cmHandleQueryParameters.addCmHandleQueryParametersItem(condition1).addCmHandleQueryParametersItem(condition2)
+ and: 'joinPoint returns our parameters'
+ mockJoinPoint.getArgs() >> [cmHandleQueryParameters]
+ when: 'the aspect method is called'
+ objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation)
+ then: 'the counter should be registered with alphabetically sorted tags'
+ def counter = findCounter('cm_handle_search_invocations', [
+ 'method' : 'testMethod',
+ 'cps-interface': 'testInterface',
+ 'conditions' : 'aCondition_zCondition'
+ ])
+ and: 'counter is incremented once'
+ assert counter.count() == 1
+ }
+
+ def findCounter(name, tags) {
+ def counterSearch = meterRegistry.find(name)
+ tags.each { key, value ->
+ counterSearch = counterSearch.tag(key, value)
+ }
+ return counterSearch.counter()
+ }
+} \ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java
index 2d33234478..1b5dd2f853 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandler.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2024 Nordix Foundation
+ * Copyright (C) 2022-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.
@@ -47,7 +47,8 @@ public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandle
*/
public Collection<DataNode> executeRequest(final String cmHandleId, final String resourceIdentifier,
final boolean includeDescendants) {
- final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
+ final FetchDescendantsOption fetchDescendantsOption
+ = FetchDescendantsOption.getFetchDescendantsOption(includeDescendants);
return networkCmProxyQueryService.queryResourceDataOperational(cmHandleId, resourceIdentifier,
fetchDescendantsOption);
}
@@ -59,7 +60,8 @@ public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandle
final String requestId,
final boolean includeDescendants,
final String authorization) {
- final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
+ final FetchDescendantsOption fetchDescendantsOption
+ = FetchDescendantsOption.getFetchDescendantsOption(includeDescendants);
final DataNode dataNode = cpsDataService.getDataNodes(cmResourceAddress.getDatastoreName(),
cmResourceAddress.resolveCmHandleReferenceToId(),
@@ -68,8 +70,4 @@ public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandle
return Mono.justOrEmpty(dataNode);
}
- private static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) {
- return includeDescendants ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
- : FetchDescendantsOption.OMIT_DESCENDANTS;
- }
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java
index 429a3790d4..f200aa2ad7 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminer.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2024 Nordix Foundation
+ * Copyright (C) 2024-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.
@@ -21,8 +21,8 @@
package org.onap.cps.ncmp.impl.datajobs;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@@ -31,7 +31,9 @@ import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest;
import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation;
import org.onap.cps.ncmp.api.datajobs.models.ProducerKey;
import org.onap.cps.ncmp.api.datajobs.models.WriteOperation;
-import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.impl.dmi.DmiServiceNameResolver;
+import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService;
import org.onap.cps.ncmp.impl.models.RequiredDmiService;
import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher;
import org.springframework.stereotype.Service;
@@ -42,6 +44,7 @@ import org.springframework.stereotype.Service;
public class WriteRequestExaminer {
private final AlternateIdMatcher alternateIdMatcher;
+ private final ParameterizedCmHandleQueryService parameterizedCmHandleQueryService;
private static final String PATH_SEPARATOR = "/";
/**
@@ -52,25 +55,35 @@ public class WriteRequestExaminer {
* @return {@code Map} map of Dmi Write Operations per Producer Key
*/
public Map<ProducerKey, List<DmiWriteOperation>> splitDmiWriteOperationsFromRequest(
- final String dataJobId,
- final DataJobWriteRequest dataJobWriteRequest) {
+ final String dataJobId, final DataJobWriteRequest dataJobWriteRequest) {
final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey = new HashMap<>();
+ final Map<String, NcmpServiceCmHandle> cmHandlePerAlternateId = getAllNcmpServiceCmHandlesWithoutProperties();
for (final WriteOperation writeOperation : dataJobWriteRequest.data()) {
- examineWriteOperation(dataJobId, dmiWriteOperationsPerProducerKey, writeOperation);
+ examineWriteOperation(dataJobId, dmiWriteOperationsPerProducerKey, writeOperation, cmHandlePerAlternateId);
}
return dmiWriteOperationsPerProducerKey;
}
+ private Map<String, NcmpServiceCmHandle> getAllNcmpServiceCmHandlesWithoutProperties() {
+ final Map<String, NcmpServiceCmHandle> ncmpServiceCmHandles = new HashMap<>();
+ for (final NcmpServiceCmHandle ncmpServiceCmHandle
+ : parameterizedCmHandleQueryService.getAllCmHandlesWithoutProperties()) {
+ ncmpServiceCmHandles.put(ncmpServiceCmHandle.getAlternateId(), ncmpServiceCmHandle);
+ }
+ return ncmpServiceCmHandles;
+ }
+
private void examineWriteOperation(final String dataJobId,
final Map<ProducerKey, List<DmiWriteOperation>> dmiWriteOperationsPerProducerKey,
- final WriteOperation writeOperation) {
+ final WriteOperation writeOperation,
+ final Map<String, NcmpServiceCmHandle> cmHandlePerAlternateId) {
log.debug("data job id for write operation is: {}", dataJobId);
- final YangModelCmHandle yangModelCmHandle = alternateIdMatcher
- .getYangModelCmHandleByLongestMatchingAlternateId(writeOperation.path(), PATH_SEPARATOR);
+ final NcmpServiceCmHandle ncmpServiceCmHandle = alternateIdMatcher
+ .getCmHandleByLongestMatchingAlternateId(writeOperation.path(), PATH_SEPARATOR, cmHandlePerAlternateId);
- final DmiWriteOperation dmiWriteOperation = createDmiWriteOperation(writeOperation, yangModelCmHandle);
+ final DmiWriteOperation dmiWriteOperation = createDmiWriteOperation(writeOperation, ncmpServiceCmHandle);
- final ProducerKey producerKey = createProducerKey(yangModelCmHandle);
+ final ProducerKey producerKey = createProducerKey(ncmpServiceCmHandle);
final List<DmiWriteOperation> dmiWriteOperations;
if (dmiWriteOperationsPerProducerKey.containsKey(producerKey)) {
dmiWriteOperations = dmiWriteOperationsPerProducerKey.get(producerKey);
@@ -81,27 +94,20 @@ public class WriteRequestExaminer {
dmiWriteOperations.add(dmiWriteOperation);
}
- private ProducerKey createProducerKey(final YangModelCmHandle yangModelCmHandle) {
- return new ProducerKey(yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA),
- yangModelCmHandle.getDataProducerIdentifier());
+ private ProducerKey createProducerKey(final NcmpServiceCmHandle ncmpServiceCmHandle) {
+ final String dmiDataServiceName =
+ DmiServiceNameResolver.resolveDmiServiceName(RequiredDmiService.DATA, ncmpServiceCmHandle);
+ return new ProducerKey(dmiDataServiceName, ncmpServiceCmHandle.getDataProducerIdentifier());
}
private DmiWriteOperation createDmiWriteOperation(final WriteOperation writeOperation,
- final YangModelCmHandle yangModelCmHandle) {
+ final NcmpServiceCmHandle ncmpServiceCmHandle) {
return new DmiWriteOperation(
writeOperation.path(),
writeOperation.op(),
- yangModelCmHandle.getModuleSetTag(),
+ ncmpServiceCmHandle.getModuleSetTag(),
writeOperation.value(),
writeOperation.operationId(),
- getPrivatePropertiesFromDataNode(yangModelCmHandle));
+ Collections.emptyMap()); // TODO: Private properties will be removed as part of CPS-2693.
}
-
- private Map<String, String> getPrivatePropertiesFromDataNode(final YangModelCmHandle yangModelCmHandle) {
- final Map<String, String> cmHandleDmiProperties = new LinkedHashMap<>();
- yangModelCmHandle.getDmiProperties()
- .forEach(dmiProperty -> cmHandleDmiProperties.put(dmiProperty.getName(), dmiProperty.getValue()));
- return cmHandleDmiProperties;
- }
-
}
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..74e862691a 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
@@ -26,6 +26,7 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NA
import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+import com.hazelcast.map.IMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -54,6 +55,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;
@@ -61,7 +63,7 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService {
private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
@Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_CM_HANDLE)
- private final Map<String, TrustLevel> trustLevelPerCmHandleId;
+ private final IMap<String, TrustLevel> trustLevelPerCmHandleId;
private final CpsValidator cpsValidator;
@@ -99,8 +101,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
@@ -144,14 +153,15 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService {
final TrustLevel dmiTrustLevel = mapEntry.getValue();
final Collection<String> candidateCmHandleIds = getCmHandleReferencesByDmiPluginIdentifier(
dmiPluginIdentifier, false);
- for (final String candidateCmHandleId : candidateCmHandleIds) {
- final TrustLevel candidateCmHandleTrustLevel = trustLevelPerCmHandleId.get(candidateCmHandleId);
- final TrustLevel effectiveTrustlevel =
- candidateCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel);
- if (targetTrustLevel.equals(effectiveTrustlevel)) {
- selectedCmHandleReferences.add(candidateCmHandleId);
+ final Set<String> candidateCmHandleIdsSet = new HashSet<>(candidateCmHandleIds);
+ final Map<String, TrustLevel> trustLevelPerCmHandleIdInBatch =
+ trustLevelPerCmHandleId.getAll(candidateCmHandleIdsSet);
+ trustLevelPerCmHandleIdInBatch.forEach((cmHandleId, trustLevel) -> {
+ final TrustLevel effectiveTrustLevel = trustLevel.getEffectiveTrustLevel(dmiTrustLevel);
+ if (targetTrustLevel.equals(effectiveTrustLevel)) {
+ selectedCmHandleReferences.add(cmHandleId);
}
- }
+ });
}
if (outputAlternateId) {
return getAlternateIdsByCmHandleIds(selectedCmHandleReferences);
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/ParameterizedCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java
index 3db4920d3e..fc8884433c 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java
@@ -75,4 +75,14 @@ public interface ParameterizedCmHandleQueryService {
* @return collection of cm handles
*/
Collection<NcmpServiceCmHandle> getAllCmHandles();
+
+ /**
+ * Retrieves all {@code NcmpServiceCmHandle} instances without their associated properties.
+ * This method fetches the relevant data nodes from the inventory persistence layer and
+ * converts them into {@code NcmpServiceCmHandle} objects. Only the handles are returned,
+ * without any additional properties.
+ *
+ * @return a collection of {@code NcmpServiceCmHandle} instances without properties.
+ */
+ Collection<NcmpServiceCmHandle> getAllCmHandlesWithoutProperties();
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java
index 4c1032efad..c44234d28c 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java
@@ -97,7 +97,16 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan
@Override
public Collection<NcmpServiceCmHandle> getAllCmHandles() {
- final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT).iterator().next();
+ return toNcmpServiceCmHandles(inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT));
+ }
+
+ @Override
+ public Collection<NcmpServiceCmHandle> getAllCmHandlesWithoutProperties() {
+ return toNcmpServiceCmHandles(inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY));
+ }
+
+ private Collection<NcmpServiceCmHandle> toNcmpServiceCmHandles(final Collection<DataNode> dataNodes) {
+ final DataNode dataNode = dataNodes.iterator().next();
return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
}
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/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java
index 750a5050f2..b8e4e7feda 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java
@@ -20,12 +20,12 @@
package org.onap.cps.ncmp.impl.utils;
+import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
-import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException;
import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
import org.onap.cps.utils.CpsValidator;
import org.springframework.stereotype.Service;
@@ -37,24 +37,26 @@ public class AlternateIdMatcher {
private final CpsValidator cpsValidator;
/**
- * Get yang model cm handle that matches longest alternate id by removing elements
+ * Get cm handle that matches longest alternate id by removing elements
* (as defined by the separator string) from right to left.
* If alternate id contains a hash then all elements after that hash are ignored.
*
- * @param alternateId alternate ID
- * @param separator a string that separates each element from the next.
- * @return yang model cm handle
+ * @param alternateId alternate ID
+ * @param separator a string that separates each element from the next.
+ * @param cmHandlePerAlternateId all CM-handles by alternate ID
+ * @return ncmp service cm handle
*/
- public YangModelCmHandle getYangModelCmHandleByLongestMatchingAlternateId(final String alternateId,
- final String separator) {
+ public NcmpServiceCmHandle getCmHandleByLongestMatchingAlternateId(
+ final String alternateId, final String separator,
+ final Map<String, NcmpServiceCmHandle> cmHandlePerAlternateId) {
final String[] splitPath = alternateId.split("#", 2);
String bestMatch = splitPath[0];
while (StringUtils.isNotEmpty(bestMatch)) {
- try {
- return inventoryPersistence.getYangModelCmHandleByAlternateId(bestMatch);
- } catch (final CmHandleNotFoundException ignored) {
- bestMatch = getParentPath(bestMatch, separator);
+ final NcmpServiceCmHandle ncmpServiceCmHandle = cmHandlePerAlternateId.get(bestMatch);
+ if (ncmpServiceCmHandle != null) {
+ return ncmpServiceCmHandle;
}
+ bestMatch = getParentPath(bestMatch, separator);
}
throw new NoAlternateIdMatchFoundException(alternateId);
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy
index 6aa84d1c7f..d051927b3d 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/WriteRequestExaminerSpec.groovy
@@ -23,24 +23,27 @@ package org.onap.cps.ncmp.impl.datajobs
import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest
import org.onap.cps.ncmp.api.datajobs.models.WriteOperation
import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
-import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService
import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
import spock.lang.Specification
class WriteRequestExaminerSpec extends Specification {
def mockAlternateIdMatcher = Mock(AlternateIdMatcher)
- def objectUnderTest = new WriteRequestExaminer(mockAlternateIdMatcher)
+ def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService)
+ def objectUnderTest = new WriteRequestExaminer(mockAlternateIdMatcher, mockParameterizedCmHandleQueryService)
def setup() {
- def ch1 = new YangModelCmHandle(id: 'ch1', dmiServiceName: 'dmiA', dataProducerIdentifier: 'p1', dmiProperties: [])
- def ch2 = new YangModelCmHandle(id: 'ch2', dmiServiceName: 'dmiA', dataProducerIdentifier: 'p1', dmiProperties: [])
- def ch3 = new YangModelCmHandle(id: 'ch3', dmiServiceName: 'dmiA', dataProducerIdentifier: 'p2', dmiProperties: [])
- def ch4 = new YangModelCmHandle(id: 'ch4', dmiServiceName: 'dmiB', dataProducerIdentifier: 'p1', dmiProperties: [])
- mockAlternateIdMatcher.getYangModelCmHandleByLongestMatchingAlternateId('fdn1', '/') >> ch1
- mockAlternateIdMatcher.getYangModelCmHandleByLongestMatchingAlternateId('fdn2', '/') >> ch2
- mockAlternateIdMatcher.getYangModelCmHandleByLongestMatchingAlternateId('fdn3', '/') >> ch3
- mockAlternateIdMatcher.getYangModelCmHandleByLongestMatchingAlternateId('fdn4', '/') >> ch4
+ def ch1 = new NcmpServiceCmHandle(cmHandleId: 'ch1', dmiServiceName: 'dmiA', moduleSetTag: 'someModuleSetTag', alternateId: 'fdn1', dataProducerIdentifier: 'p1')
+ def ch2 = new NcmpServiceCmHandle(cmHandleId: 'ch2', dmiServiceName: 'dmiA', moduleSetTag: 'someModuleSetTag', alternateId: 'fdn2', dataProducerIdentifier: 'p1')
+ def ch3 = new NcmpServiceCmHandle(cmHandleId: 'ch3', dmiServiceName: 'dmiA', moduleSetTag: 'someModuleSetTag', alternateId: 'fdn3', dataProducerIdentifier: 'p2')
+ def ch4 = new NcmpServiceCmHandle(cmHandleId: 'ch4', dmiServiceName: 'dmiB', moduleSetTag: 'someModuleSetTag', alternateId: 'fdn4', dataProducerIdentifier: 'p1')
+ def cmHandlePerAlternateId = ['fdn1': ch1, 'fdn2': ch2, 'fdn3': ch3, 'fdn4': ch4]
+ mockAlternateIdMatcher.getCmHandleByLongestMatchingAlternateId('fdn1', '/', cmHandlePerAlternateId) >> ch1
+ mockAlternateIdMatcher.getCmHandleByLongestMatchingAlternateId('fdn2', '/', cmHandlePerAlternateId) >> ch2
+ mockAlternateIdMatcher.getCmHandleByLongestMatchingAlternateId('fdn3', '/', cmHandlePerAlternateId) >> ch3
+ mockAlternateIdMatcher.getCmHandleByLongestMatchingAlternateId('fdn4', '/', cmHandlePerAlternateId) >> ch4
+ mockParameterizedCmHandleQueryService.getAllCmHandlesWithoutProperties() >> [ch1, ch2, ch3, ch4]
}
def 'Create a map of dmi write requests per producer key with #scenario.'() {
@@ -83,9 +86,9 @@ class WriteRequestExaminerSpec extends Specification {
def 'Validate the creation of a ProducerKey with correct dmiservicename.'() {
given: 'yangModelCmHandles with service name: "#dmiServiceName" and data service name: "#dataServiceName"'
- def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dataServiceName, '', new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1'), '', '', 'dpi1')
+ def ncmpServiceCmHandle = new NcmpServiceCmHandle(dmiServiceName: dmiServiceName, dmiDataServiceName: dataServiceName, dataProducerIdentifier: 'dpi1')
when: 'the ProducerKey is created'
- def result = objectUnderTest.createProducerKey(yangModelCmHandle).toString()
+ def result = objectUnderTest.createProducerKey(ncmpServiceCmHandle).toString()
then: 'we get the ProducerKey with the correct service name'
assert result == expectedProducerKey
where: 'the following services are registered'
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..12e4d6b85d 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)
@@ -241,7 +241,7 @@ class CmHandleQueryServiceImplSpec extends Specification {
mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']/@alternate-id', _) >> [pnfDemo.getLeaves().get('alternate-id'), pnfDemo4.getLeaves().get('alternate-id')]
mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']/@alternate-id', _) >> [pnfDemo2.getLeaves().get('alternate-id'), pnfDemo4.getLeaves().get('alternate-id')]
mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'PNFDemo\']/@alternate-id', _) >> [pnfDemo.getLeaves().get('alternate-id')]
- mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'PNFDemo2\' or @id=\'PNFDemo\' or @id=\'PNFDemo4\']/@alternate-id', _) >> [pnfDemo2.getLeaves().get('alternate-id'), pnfDemo.getLeaves().get('alternate-id'), pnfDemo4.getLeaves().get('alternate-id')]
+ mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'PNFDemo2\' or @id=\'PNFDemo4\' or @id=\'PNFDemo\']/@alternate-id', _) >> [pnfDemo2.getLeaves().get('alternate-id'), pnfDemo.getLeaves().get('alternate-id'), pnfDemo4.getLeaves().get('alternate-id')]
mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '//public-properties[@name=\'Contact\' and @value=\'newemailforstore@bookstore.com\']/ancestor::cm-handles/@id',_) >> [pnfDemo.getLeaves().get('id'), pnfDemo2.getLeaves().get('id'), pnfDemo4.getLeaves().get('id')]
mockCpsQueryService.queryDataLeaf(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '//public-properties[@name=\'Contact\' and @value=\'newemailforstore@bookstore.com\']/ancestor::cm-handles/@alternate-id',_) >> [pnfDemo.getLeaves().get('alternate-id'), pnfDemo2.getLeaves().get('alternate-id'), pnfDemo4.getLeaves().get('alternate-id')]
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-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy
index b59dd1a55f..098e88a644 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy
@@ -20,7 +20,7 @@
package org.onap.cps.ncmp.impl.utils
-import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException
import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
@@ -37,16 +37,12 @@ class AlternateIdMatcherSpec extends Specification {
def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence, new CpsValidatorImpl())
- def setup() {
- given: 'cm handle in the registry with alternate id /a/b'
- mockInventoryPersistence.getYangModelCmHandleByAlternateId('/a/b') >> new YangModelCmHandle()
- and: 'no other cm handle'
- mockInventoryPersistence.getYangModelCmHandleByAlternateId(_) >> { throw new CmHandleNotFoundException('') }
- }
-
def 'Finding longest alternate id matches.'() {
+ given: 'a cm handle with alternate id /a/b in the cached map of all cm handles'
+ def ch1 = new NcmpServiceCmHandle(cmHandleId: 'ch1', alternateId: '/a/b')
+ def cmHandlePerAlternateId = ['/a/b': ch1]
expect: 'querying for alternate id a matching result found'
- assert objectUnderTest.getYangModelCmHandleByLongestMatchingAlternateId(targetAlternateId, '/') != null
+ assert objectUnderTest.getCmHandleByLongestMatchingAlternateId(targetAlternateId, '/', cmHandlePerAlternateId) != null
where: 'the following parameters are used'
scenario | targetAlternateId
'exact match' | '/a/b'
@@ -61,7 +57,7 @@ class AlternateIdMatcherSpec extends Specification {
def 'Attempt to find longest alternate id match without any matches.'() {
when: 'attempt to find alternateId'
- objectUnderTest.getYangModelCmHandleByLongestMatchingAlternateId(targetAlternateId, '/')
+ objectUnderTest.getCmHandleByLongestMatchingAlternateId(targetAlternateId, '/', [:])
then: 'no alternate id match found exception thrown'
def thrown = thrown(NoAlternateIdMatchFoundException)
and: 'the exception has the relevant details from the error response'
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-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index be552ecc6a..b6a2e42a14 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -2,7 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2021-2024 Nordix Foundation
+ * Modifications Copyright (C) 2021-2025 Nordix Foundation
* Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
@@ -30,24 +30,19 @@ import io.micrometer.core.annotation.Timed;
import jakarta.validation.ValidationException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
-import org.onap.cps.api.CpsAnchorService;
import org.onap.cps.api.CpsDataService;
-import org.onap.cps.api.model.Anchor;
-import org.onap.cps.api.model.DataNode;
+import org.onap.cps.api.CpsFacade;
import org.onap.cps.api.model.DeltaReport;
import org.onap.cps.api.parameters.FetchDescendantsOption;
import org.onap.cps.rest.api.CpsDataApi;
import org.onap.cps.utils.ContentType;
-import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
-import org.onap.cps.utils.PrefixResolver;
import org.onap.cps.utils.XmlFileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -64,10 +59,9 @@ public class DataRestController implements CpsDataApi {
private static final String ISO_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
private static final DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_FORMAT);
+ private final CpsFacade cpsFacade;
private final CpsDataService cpsDataService;
- private final CpsAnchorService cpsAnchorService;
private final JsonObjectMapper jsonObjectMapper;
- private final PrefixResolver prefixResolver;
@Override
public ResponseEntity<String> createNode(final String apiVersion,
@@ -116,24 +110,20 @@ public class DataRestController implements CpsDataApi {
}
@Override
- @Timed(value = "cps.data.controller.datanode.get.v1",
- description = "Time taken to get data node")
+ @Timed(value = "cps.data.controller.datanode.get.v1", description = "Time taken to get data node")
public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName,
final String anchorName,
final String xpath,
final Boolean includeDescendants) {
- final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
- ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
- final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
- fetchDescendantsOption).iterator().next();
- final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
+ final FetchDescendantsOption fetchDescendantsOption =
+ FetchDescendantsOption.getFetchDescendantsOption(includeDescendants);
+ final Map<String, Object> dataNodeAsMap =
+ cpsFacade.getFirstDataNodeByAnchor(dataspaceName, anchorName, xpath, fetchDescendantsOption);
+ return new ResponseEntity<>(dataNodeAsMap, HttpStatus.OK);
}
@Override
- @Timed(value = "cps.data.controller.datanode.get.v2",
- description = "Time taken to get data node")
+ @Timed(value = "cps.data.controller.datanode.get.v2", description = "Time taken to get data node")
public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName,
final String xpath,
final String fetchDescendantsOptionAsString,
@@ -141,16 +131,9 @@ public class DataRestController implements CpsDataApi {
final ContentType contentType = ContentType.fromString(contentTypeInHeader);
final FetchDescendantsOption fetchDescendantsOption =
FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
- final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
- fetchDescendantsOption);
- final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
- final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- for (final DataNode dataNode: dataNodes) {
- final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
- dataMaps.add(dataMap);
- }
- return buildResponseEntity(dataMaps, contentType);
+ final List<Map<String, Object>> dataNodesAsMaps =
+ cpsFacade.getDataNodesByAnchor(dataspaceName, anchorName, xpath, fetchDescendantsOption);
+ return buildResponseEntity(dataNodesAsMaps, contentType);
}
@Override
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
index f8833094cf..11713ad5e7 100644
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * Copyright (C) 2021-2025 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada.
* Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
* ================================================================================
@@ -23,23 +23,15 @@
package org.onap.cps.rest.controller;
import io.micrometer.core.annotation.Timed;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
-import org.onap.cps.api.CpsAnchorService;
-import org.onap.cps.api.CpsQueryService;
-import org.onap.cps.api.model.Anchor;
-import org.onap.cps.api.model.DataNode;
+import org.onap.cps.api.CpsFacade;
import org.onap.cps.api.parameters.FetchDescendantsOption;
import org.onap.cps.api.parameters.PaginationOption;
import org.onap.cps.rest.api.CpsQueryApi;
import org.onap.cps.utils.ContentType;
-import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
-import org.onap.cps.utils.PrefixResolver;
import org.onap.cps.utils.XmlFileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -51,27 +43,24 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
public class QueryRestController implements CpsQueryApi {
- private final CpsQueryService cpsQueryService;
- private final CpsAnchorService cpsAnchorService;
+ private final CpsFacade cpsFacade;
private final JsonObjectMapper jsonObjectMapper;
- private final PrefixResolver prefixResolver;
@Override
- @Timed(value = "cps.data.controller.datanode.query.v1",
- description = "Time taken to query data nodes")
+ @Timed(value = "cps.data.controller.datanode.query.v1", description = "Time taken to query data nodes")
public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String dataspaceName,
final String anchorName,
final String cpsPath,
final Boolean includeDescendants) {
- final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
- ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
- return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath,
- fetchDescendantsOption, ContentType.JSON);
+ final FetchDescendantsOption fetchDescendantsOption =
+ FetchDescendantsOption.getFetchDescendantsOption(includeDescendants);
+ final List<Map<String, Object>> dataNodesAsMaps
+ = cpsFacade.executeAnchorQuery(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
+ return buildResponseEntity(dataNodesAsMaps, ContentType.JSON);
}
@Override
- @Timed(value = "cps.data.controller.datanode.query.v2",
- description = "Time taken to query data nodes")
+ @Timed(value = "cps.data.controller.datanode.query.v2", description = "Time taken to query data nodes")
public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPathV2(final String dataspaceName,
final String anchorName,
final String cpsPath,
@@ -80,8 +69,9 @@ public class QueryRestController implements CpsQueryApi {
final ContentType contentType = ContentType.fromString(contentTypeInHeader);
final FetchDescendantsOption fetchDescendantsOption =
FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
- return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath,
- fetchDescendantsOption, contentType);
+ final List<Map<String, Object>> dataNodesAsMaps
+ = cpsFacade.executeAnchorQuery(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
+ return buildResponseEntity(dataNodesAsMaps, contentType);
}
@Override
@@ -96,65 +86,21 @@ public class QueryRestController implements CpsQueryApi {
FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
final PaginationOption paginationOption = (pageIndex == null || pageSize == null)
? PaginationOption.NO_PAGINATION : new PaginationOption(pageIndex, pageSize);
- final Collection<DataNode> dataNodes = cpsQueryService.queryDataNodesAcrossAnchors(dataspaceName,
- cpsPath, fetchDescendantsOption, paginationOption);
- final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size());
- String prefix = null;
- final Map<String, List<DataNode>> dataNodesPerAnchor = groupDataNodesPerAnchor(dataNodes);
- for (final Map.Entry<String, List<DataNode>> dataNodesPerAnchorEntry : dataNodesPerAnchor.entrySet()) {
- final String anchorName = dataNodesPerAnchorEntry.getKey();
- if (prefix == null) {
- final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- prefix = prefixResolver.getPrefix(anchor, dataNodesPerAnchorEntry.getValue().get(0).getXpath());
- }
- final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifierAndAnchor(
- dataNodesPerAnchorEntry.getValue(), anchorName, prefix);
- dataNodesAsListOfMaps.add(dataMap);
- }
- final Integer totalPages = getTotalPages(dataspaceName, cpsPath, paginationOption);
- return ResponseEntity.ok().header("total-pages",
- totalPages.toString()).body(jsonObjectMapper.asJsonString(dataNodesAsListOfMaps));
- }
+ final List<Map<String, Object>> dataNodesAsMaps
+ = cpsFacade.executeDataspaceQuery(dataspaceName, cpsPath, fetchDescendantsOption, paginationOption);
- private Integer getTotalPages(final String dataspaceName, final String cpsPath,
- final PaginationOption paginationOption) {
- if (paginationOption == PaginationOption.NO_PAGINATION) {
- return 1;
- }
- final int totalAnchors = cpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath);
- return totalAnchors <= paginationOption.getPageSize() ? 1
- : (int) Math.ceil((double) totalAnchors / paginationOption.getPageSize());
- }
-
- private static Map<String, List<DataNode>> groupDataNodesPerAnchor(final Collection<DataNode> dataNodes) {
- return dataNodes.stream().collect(Collectors.groupingBy(DataNode::getAnchorName));
- }
-
- private ResponseEntity<Object> executeNodesByDataspaceQueryAndCreateResponse(final String dataspaceName,
- final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption,
- final ContentType contentType) {
- final Collection<DataNode> dataNodes =
- cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
- final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size());
- final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- String prefix = null;
- for (final DataNode dataNode : dataNodes) {
- if (prefix == null) {
- prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- }
- final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
- dataNodesAsListOfMaps.add(dataMap);
- }
- return buildResponseEntity(dataNodesAsListOfMaps, contentType);
+ final int totalPages = cpsFacade.countAnchorsInDataspaceQuery(dataspaceName, cpsPath, paginationOption);
+ return ResponseEntity.ok().header("total-pages", String.valueOf(totalPages))
+ .body(jsonObjectMapper.asJsonString(dataNodesAsMaps));
}
- private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataNodesAsListOfMaps,
+ private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataNodesAsMaps,
final ContentType contentType) {
final String responseData;
if (ContentType.XML.equals(contentType)) {
- responseData = XmlFileUtils.convertDataMapsToXml(dataNodesAsListOfMaps);
+ responseData = XmlFileUtils.convertDataMapsToXml(dataNodesAsMaps);
} else {
- responseData = jsonObjectMapper.asJsonString(dataNodesAsListOfMaps);
+ responseData = jsonObjectMapper.asJsonString(dataNodesAsMaps);
}
return new ResponseEntity<>(responseData, HttpStatus.OK);
}
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index f2f962422f..e4cd8c4be6 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * Copyright (C) 2021-2025 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
@@ -25,17 +25,12 @@
package org.onap.cps.rest.controller
import com.fasterxml.jackson.databind.ObjectMapper
-import groovy.json.JsonSlurper
-import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDataService
-import org.onap.cps.api.parameters.FetchDescendantsOption
-import org.onap.cps.api.model.DataNode
-import org.onap.cps.impl.DataNodeBuilder
+import org.onap.cps.api.CpsFacade
import org.onap.cps.impl.DeltaReportBuilder
import org.onap.cps.utils.ContentType
import org.onap.cps.utils.DateTimeUtility
import org.onap.cps.utils.JsonObjectMapper
-import org.onap.cps.utils.PrefixResolver
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
@@ -44,7 +39,6 @@ import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.mock.web.MockMultipartFile
import org.springframework.test.web.servlet.MockMvc
-import org.springframework.web.multipart.MultipartFile
import spock.lang.Shared
import spock.lang.Specification
@@ -61,17 +55,14 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
class DataRestControllerSpec extends Specification {
@SpringBean
- CpsDataService mockCpsDataService = Mock()
+ CpsFacade mockCpsFacade = Mock()
@SpringBean
- CpsAnchorService mockCpsAnchorService = Mock()
+ CpsDataService mockCpsDataService = Mock()
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
- @SpringBean
- PrefixResolver prefixResolver = Mock()
-
@Autowired
MockMvc mvc
@@ -97,20 +88,7 @@ class DataRestControllerSpec extends Specification {
def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
@Shared
- static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/parent-1')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
-
- @Shared
- static DataNode dataNodeWithLeavesNoChildren2 = new DataNodeBuilder().withXpath('/parent-2')
- .withLeaves([leaf: 'value']).build()
-
- @Shared
- static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
- .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
-
- @Shared
- static MultipartFile multipartYangFile = new MockMultipartFile("file", 'filename.yang', "text/plain", 'content'.getBytes())
-
+ def multipartYangFile = new MockMultipartFile("file", 'filename.yang', "text/plain", 'content'.getBytes())
def setup() {
dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
@@ -130,7 +108,7 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a created response is returned'
response.status == HttpStatus.CREATED.value()
- then: 'the java API was called with the correct parameters'
+ then: 'the cps data service was called with the correct parameters'
1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, noTimestamp, expectedContentType)
where: 'following xpath parameters are are used'
scenario | parentNodeXpath | contentType | expectedContentType | requestBody | expectedData
@@ -140,7 +118,7 @@ class DataRestControllerSpec extends Specification {
'XML content: xpath parameter point root' | '/' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData
}
- def 'Create a node with observed-timestamp'() {
+ def 'Create a node with observed-timestamp.'() {
given: 'endpoint to create a node'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'post is invoked with datanode endpoint and json'
@@ -154,7 +132,7 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a created response is returned'
response.status == expectedHttpStatus.value()
- then: 'the java API was called with the correct parameters'
+ then: 'the cps data service was called with the correct parameters'
expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
where:
@@ -164,7 +142,7 @@ class DataRestControllerSpec extends Specification {
'with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
}
- def 'Validate data using create a node API'() {
+ def 'Validate data using create a node API.'() {
given: 'an endpoint to create a node'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
def parentNodeXpath = '/'
@@ -181,11 +159,11 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a 200 OK response is returned'
response.status == HttpStatus.OK.value()
- then: 'the service was called with correct parameters'
+ then: 'the cps data service was called with correct parameters'
1 * mockCpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, requestBodyJson, ContentType.JSON)
}
- def 'Create a child node #scenario'() {
+ def 'Create a child node #scenario.'() {
given: 'endpoint to create a node'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
and: 'parent node xpath'
@@ -201,7 +179,7 @@ class DataRestControllerSpec extends Specification {
mvc.perform(postRequestBuilder).andReturn().response
then: 'a created response is returned'
response.status == HttpStatus.CREATED.value()
- then: 'the java API was called with the correct parameters'
+ then: 'the cps data service was called with the correct parameters'
1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedData,
DateTimeUtility.toOffsetDateTime(observedTimestamp), expectedContentType)
where:
@@ -251,10 +229,10 @@ class DataRestControllerSpec extends Specification {
def response = mvc.perform(postRequestBuilder).andReturn().response
then: 'a created response is returned'
response.status == expectedHttpStatus.value()
- then: 'the java API was called with the correct parameters'
+ then: 'the cps data service was called with the correct parameters when needed'
expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
- where:
+ where: 'the following parameters are used'
scenario | observedTimestamp | contentType | requestBody || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType
'Content type JSON with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
'Content type JSON without observed-timestamp' | null | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
@@ -280,34 +258,14 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a 200 OK response is returned'
response.status == HttpStatus.OK.value()
- then: 'the service was called with correct parameters'
+ then: 'the cps data service was called with correct parameters'
1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
}
- def 'Get data node with leaves'() {
- given: 'the service returns data node leaves'
- def xpath = 'parent-1'
- def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren]
- when: 'get request is performed through REST API'
- def response =
- mvc.perform(get(endpoint).param('xpath', xpath))
- .andReturn().response
- then: 'a success response is returned'
- response.status == HttpStatus.OK.value()
- then: 'the response contains the the datanode in json format'
- response.getContentAsString() == '{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}'
- and: 'response contains expected leaf and value'
- response.contentAsString.contains('"leaf":"value"')
- and: 'response contains expected leaf-list and values'
- response.contentAsString.contains('"leafList":["leaveListElement1","leaveListElement2"]')
- }
-
- def 'Get data node with #scenario.'() {
+ def 'Get data nodes [V1] with #scenario.'() {
given: 'the service returns data node with #scenario'
- def xpath = 'some xPath'
+ def xpath = 'my/path'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
when: 'get request is performed through REST API'
def response =
mvc.perform(
@@ -315,121 +273,42 @@ class DataRestControllerSpec extends Specification {
.param('xpath', xpath)
.param('include-descendants', includeDescendantsOption))
.andReturn().response
+ then: 'the cps facade is called with the correct parameters'
+ 1 * mockCpsFacade.getFirstDataNodeByAnchor(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [mocked:'result']
then: 'a success response is returned'
response.status == HttpStatus.OK.value()
- and: 'the response contains the root node identifier: #expectedRootidentifier'
- response.contentAsString.contains(expectedRootidentifier)
- and: 'the response contains child is #expectChildInResponse'
- response.contentAsString.contains('"child"') == expectChildInResponse
- where:
- scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
- 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1'
- 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'parent-1'
- 'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true | 'parent'
- }
-
- def 'Get all the data trees as json array with root node xPath using V2'() {
- given: 'the service returns all data node leaves'
- def xpath = '/'
- def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2]
- when: 'V2 of get request is performed through REST API'
- def response =
- mvc.perform(get(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .param('xpath', xpath))
- .andReturn().response
- then: 'a success response is returned'
- response.status == HttpStatus.OK.value()
- and: 'the response contains the datanode in json array format'
- response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' +
- '{"parent-2":{"leaf":"value"}}]'
- and: 'the json array contains expected number of data trees'
- def numberOfDataTrees = new JsonSlurper().parseText(response.getContentAsString()).iterator().size()
- assert numberOfDataTrees == 2
- }
-
- def 'Get all the data trees using V2 without Content-Type defaults to json'() {
- given: 'the service returns all data node leaves'
- def xpath = '/'
- def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2]
- when: 'V2 of get request is performed through REST API without specifying content-type header'
- def response =
- mvc.perform(get(endpoint)
- .param('xpath', xpath))
- .andReturn().response
- then: 'a success response is returned'
- response.status == HttpStatus.OK.value()
- and: 'the response contains the datanode in json array format'
- response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' +
- '{"parent-2":{"leaf":"value"}}]'
- }
-
- def 'Get all the data trees as XML with root node xPath using V2'() {
- given: 'the service returns all data node leaves'
- def xpath = '/'
- def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren]
- when: 'V2 of get request is performed through REST API with XML content type'
- def response =
- mvc.perform(get(endpoint).contentType(MediaType.APPLICATION_XML).param('xpath', xpath))
- .andReturn().response
- then: 'a success response is returned'
- response.status == HttpStatus.OK.value()
- and: 'the response contains the datanode in XML format'
- response.getContentAsString() == '<parent-1><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></parent-1>'
+ and: 'the response contains the facade result in json format'
+ response.getContentAsString() == '{"mocked":"result"}'
+ where: 'the following parameters are used'
+ scenario | includeDescendantsOption || expectedCpsDataServiceOption
+ 'no descendants (default) ' | '' || OMIT_DESCENDANTS
+ 'with descendants' | 'true' || INCLUDE_ALL_DESCENDANTS
}
- def 'Get data node with #scenario using V2.'() {
+ def 'Get data node with #scenario using V2. output type #scenario.'() {
given: 'the service returns data nodes with #scenario'
def xpath = 'some xPath'
def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
when: 'V2 of get request is performed through REST API'
def response =
- mvc.perform(
- get(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .param('xpath', xpath)
- .param('descendants', includeDescendantsOption))
- .andReturn().response
- then: 'a success response is returned'
- response.status == HttpStatus.OK.value()
- and: 'the response contains the root node identifier: #expectedRootidentifier'
- response.contentAsString.contains(expectedRootidentifier)
- and: 'the response contains child is #expectChildInResponse'
- response.contentAsString.contains('"child"') == expectChildInResponse
- where:
- scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
- 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1'
- 'no descendant explicitly' | dataNodeWithLeavesNoChildren | '0' || OMIT_DESCENDANTS | false | 'parent-1'
- 'with descendants' | dataNodeWithChild | '-1' || INCLUDE_ALL_DESCENDANTS | true | 'parent'
- }
-
- def 'Get data node using v2 api'() {
- given: 'the service returns data node'
- def xpath = 'some xPath'
- def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
- mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, { descendantsOption -> {
- assert descendantsOption.depth == 2}} as FetchDescendantsOption) >> [dataNodeWithChild]
- when: 'get request is performed through REST API'
- def response =
- mvc.perform(
- get(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
+ mvc.perform(get(endpoint)
+ .contentType(contentType)
.param('xpath', xpath)
- .param('descendants', '2'))
+ .param('descendants', 'all'))
.andReturn().response
- then: 'a success response is returned'
+ then: 'the cps service facade is called with the correct parameters and returns some data'
+ 1 * mockCpsFacade.getDataNodesByAnchor(dataspaceName, anchorName, xpath, INCLUDE_ALL_DESCENDANTS) >> [[mocked:'result1'], [mocked:'result2']]
+ and: 'a success response is returned'
assert response.status == HttpStatus.OK.value()
- and: 'the response contains the root node identifier'
- assert response.contentAsString.contains('parent')
- and: 'the response contains child is true'
- assert response.contentAsString.contains('"child"')
+ and: 'the response is in the expected format'
+ assert response.contentAsString == expectedResult
+ where: 'the following content types are used'
+ scenario | contentType || expectedResult
+ 'XML' | MediaType.APPLICATION_XML || '<mocked>result1</mocked><mocked>result2</mocked>'
+ 'JSON' | MediaType.APPLICATION_JSON || '[{"mocked":"result1"},{"mocked":"result2"}]'
}
- def 'Get delta between two anchors'() {
+ def 'Get delta between two anchors.'() {
given: 'the service returns a list containing delta reports'
def deltaReports = new DeltaReportBuilder().actionReplace().withXpath('some xpath').withSourceData('some key': 'some value').withTargetData('some key': 'some value').build()
def xpath = 'some xpath'
@@ -468,7 +347,7 @@ class DataRestControllerSpec extends Specification {
assert response.contentAsString.contains("[{\"action\":\"create\",\"xpath\":\"some xpath\"}]")
}
- def 'Get delta between anchor and JSON payload without multipart file'() {
+ def 'Get delta between anchor and JSON payload without multipart file.'() {
given: 'sample delta report, xpath, and json payload'
def deltaReports = new DeltaReportBuilder().actionRemove().withXpath('some xpath').build()
def xpath = 'some xpath'
@@ -499,7 +378,7 @@ class DataRestControllerSpec extends Specification {
.content(requestBody)
.param('xpath', inputXpath)
).andReturn().response
- then: 'the service method is invoked with expected parameters'
+ then: 'the cps data service method is invoked with expected parameters'
1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedData, null, expectedContentType)
and: 'response status indicates success'
response.status == HttpStatus.OK.value()
@@ -513,7 +392,7 @@ class DataRestControllerSpec extends Specification {
'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML
}
- def 'Update data node leaves with observedTimestamp'() {
+ def 'Update data node leaves with observedTimestamp.'() {
given: 'endpoint to update a node leaves '
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'patch request is performed'
@@ -525,7 +404,7 @@ class DataRestControllerSpec extends Specification {
.param('xpath', '/')
.param('observed-timestamp', observedTimestamp)
).andReturn().response
- then: 'the service method is invoked with expected parameters'
+ then: 'the cps data service method is invoked with expected parameters'
expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
and: 'response status indicates success'
@@ -536,7 +415,7 @@ class DataRestControllerSpec extends Specification {
'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
}
- def 'Validate data using Update a node API'() {
+ def 'Validate data using Update a node API.'() {
given: 'endpoint to update a node leaves'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
and: 'dryRunEnabled flag is set to true'
@@ -552,7 +431,7 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a 200 OK response is returned'
response.status == HttpStatus.OK.value()
- then: 'the service was called with correct parameters'
+ then: 'the cps data service was called with correct parameters'
1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
}
@@ -567,7 +446,7 @@ class DataRestControllerSpec extends Specification {
.content(requestBody)
.param('xpath', inputXpath))
.andReturn().response
- then: 'the service method is invoked with expected parameters'
+ then: 'the cps data service method is invoked with expected parameters'
1 * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, xpathServiceParameter, expectedData, noTimestamp, expectedContentType)
and: 'response status indicates success'
response.status == HttpStatus.OK.value()
@@ -581,7 +460,7 @@ class DataRestControllerSpec extends Specification {
'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML
}
- def 'Validate data using Replace data node API'() {
+ def 'Validate data using Replace data node API.'() {
given: 'endpoint to replace node'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
and: 'dryRunEnabled flag is set to true'
@@ -597,7 +476,7 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a 200 OK response is returned'
response.status == HttpStatus.OK.value()
- then: 'the service was called with correct parameters'
+ then: 'the cps data service was called with correct parameters'
1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
}
@@ -613,7 +492,7 @@ class DataRestControllerSpec extends Specification {
.param('xpath', '')
.param('observed-timestamp', observedTimestamp))
.andReturn().response
- then: 'the service method is invoked with expected parameters'
+ then: 'the cps data service method is invoked with expected parameters'
expectedApiCount * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', expectedJsonData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
and: 'response status indicates success'
@@ -635,7 +514,7 @@ class DataRestControllerSpec extends Specification {
def response = mvc.perform(putRequestBuilder).andReturn().response
then: 'a success response is returned'
response.status == expectedHttpStatus.value()
- and: 'the java API was called with the correct parameters'
+ and: 'the cps data service was called with the correct parameters'
expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
where:
@@ -656,7 +535,7 @@ class DataRestControllerSpec extends Specification {
def response = mvc.perform(putRequestBuilder).andReturn().response
then: 'a success response is returned'
response.status == expectedHttpStatus.value()
- and: 'the java API was called with the correct parameters'
+ and: 'the cps data service was called with the correct parameters'
expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedXmlData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.XML)
where:
@@ -666,7 +545,7 @@ class DataRestControllerSpec extends Specification {
'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
}
- def 'Validate data using Replace list content API'() {
+ def 'Validate data using Replace list content API.'() {
given: 'endpoint to replace list-nodes'
def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes"
and: 'dryRunEnabled flag is set to true'
@@ -682,7 +561,7 @@ class DataRestControllerSpec extends Specification {
).andReturn().response
then: 'a 200 OK response is returned'
response.status == HttpStatus.OK.value()
- then: 'the service was called with correct parameters'
+ then: 'the cps data service was called with correct parameters'
1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
}
@@ -695,7 +574,7 @@ class DataRestControllerSpec extends Specification {
def response = mvc.perform(deleteRequestBuilder).andReturn().response
then: 'a success response is returned'
response.status == expectedHttpStatus.value()
- and: 'the java API was called with the correct parameters'
+ and: 'the cps data service was called with the correct parameters'
expectedApiCount * mockCpsDataService.deleteListOrListElement(dataspaceName, anchorName, 'list element xpath',
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
where:
@@ -717,7 +596,7 @@ class DataRestControllerSpec extends Specification {
def response = mvc.perform(deleteDataNodeRequest).andReturn().response
then: 'a successful response is returned'
response.status == expectedHttpStatus.value()
- and: 'the api is called with the correct parameters'
+ and: 'the cps data service is called with the correct parameters'
expectedApiCount * mockCpsDataService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
where:
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
index 2b5c471287..5f6de2ec4c 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * 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.
@@ -24,12 +24,9 @@
package org.onap.cps.rest.controller
import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.api.CpsAnchorService
-import org.onap.cps.api.CpsQueryService
+import org.onap.cps.api.CpsFacade
import org.onap.cps.api.parameters.PaginationOption
-import org.onap.cps.impl.DataNodeBuilder
import org.onap.cps.utils.JsonObjectMapper
-import org.onap.cps.utils.PrefixResolver
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
@@ -39,216 +36,98 @@ import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Specification
-import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@WebMvcTest(QueryRestController)
class QueryRestControllerSpec extends Specification {
@SpringBean
- CpsQueryService mockCpsQueryService = Mock()
-
- @SpringBean
- CpsAnchorService mockCpsAnchorService = Mock()
+ CpsFacade mockCpsFacade = Mock()
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
- @SpringBean
- PrefixResolver prefixResolver = Mock()
-
@Autowired
MockMvc mvc
@Value('${rest.api.cps-base-path}')
def basePath
- def dataspaceName = 'my_dataspace'
- def anchorName = 'my_anchor'
- def cpsPath = 'some cps-path'
- def dataNodeEndpointV2
-
- def setup() {
- dataNodeEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query"
- }
+ def dataNodeAsMap = ['prefixedPath':[path:[leaf:'value']]]
- def 'Query data node by cps path for the given dataspace and anchor with #scenario.'() {
- given: 'service method returns a list containing a data node'
- def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
- mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, expectedCpsDataServiceOption) >> [dataNode1, dataNode1]
- and: 'the query endpoint'
- def dataNodeEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query"
+ def 'Query data node (v1) by cps path for the given dataspace and anchor with #scenario.'() {
+ given: 'the query endpoint'
+ def dataNodeEndpoint = "$basePath/v1/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
when: 'query data nodes API is invoked'
- def response =
- mvc.perform(
- get(dataNodeEndpoint)
- .param('cps-path', cpsPath)
- .param('include-descendants', includeDescendantsOption))
- .andReturn().response
+ def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param('include-descendants', includeDescendantsOption))
+ .andReturn().response
+ then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
+ 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path', expectedCpsDataServiceOption) >> [dataNodeAsMap]
then: 'the response contains the the datanode in json format'
- response.status == HttpStatus.OK.value()
- response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
+ assert response.status == HttpStatus.OK.value()
+ assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
where: 'the following options for include descendants are provided in the request'
scenario | includeDescendantsOption || expectedCpsDataServiceOption
'no descendants by default' | '' || OMIT_DESCENDANTS
- 'no descendant explicitly' | 'false' || OMIT_DESCENDANTS
'descendants' | 'true' || INCLUDE_ALL_DESCENDANTS
}
- def 'Query data node v2 API by cps path for the given dataspace and anchor with #scenario and media type JSON'() {
- given: 'service method returns a list containing a data node'
- def dataNode = new DataNodeBuilder().withXpath('/xpath')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
- mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption ->
- assert descendantsOption.depth == expectedDepth
- }) >> [dataNode, dataNode]
+ def 'Query data node (v2) by cps path for given dataspace and anchor with #scenario'() {
+ given: 'the query endpoint'
+ def dataNodeEndpointV2 = "$basePath/v2/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
when: 'query data nodes API is invoked'
- def response =
- mvc.perform(
- get(dataNodeEndpointV2)
- .contentType(MediaType.APPLICATION_JSON)
- .param('cps-path', cpsPath)
- .param('descendants', includeDescendantsOptionString))
+ def response = mvc.perform(get(dataNodeEndpointV2).contentType(contentType).param('cps-path', 'my/path') .param('descendants', includeDescendantsOptionString))
.andReturn().response
- then: 'the response contains the datanode in the expected JSON format'
+ then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
+ 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path',
+ { descendantsOption -> assert descendantsOption.depth == expectedDepth }) >> [dataNodeAsMap]
+ and: 'the response contains the datanode in the expected format'
assert response.status == HttpStatus.OK.value()
- assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
+ assert response.getContentAsString() == expectedOutput
where: 'the following options for include descendants are provided in the request'
- scenario | includeDescendantsOptionString || expectedDepth
- 'direct children' | 'direct' || 1
- 'descendants' | '2' || 2
+ scenario | includeDescendantsOptionString | contentType || expectedDepth || expectedOutput
+ 'direct children JSON' | 'direct' | MediaType.APPLICATION_JSON || 1 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
+ 'descendants JSON' | '2' | MediaType.APPLICATION_JSON || 2 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
+ 'descendants XML' | '2' | MediaType.APPLICATION_XML || 2 || '<prefixedPath><path><leaf>value</leaf></path></prefixedPath>'
}
- def 'Query data node v2 API by cps path for the given dataspace and anchor with #scenario and media type XML'() {
- given: 'service method returns a list containing a data node'
- def dataNode = new DataNodeBuilder().withXpath('/xpath')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
- mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption ->
- assert descendantsOption.depth == expectedDepth
- }) >> [dataNode, dataNode]
+ def 'Query data node by cps path for given dataspace across all anchors'() {
+ given: 'the query endpoint'
+ def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query"
+ and: 'the cps service facade will say there are 123 pages '
+ mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', new PaginationOption(2,5) ) >> 123
when: 'query data nodes API is invoked'
- def response =
- mvc.perform(
- get(dataNodeEndpointV2)
- .contentType(MediaType.APPLICATION_XML)
- .param('cps-path', cpsPath)
- .param('descendants', includeDescendantsOptionString))
- .andReturn().response
- then: 'the response contains the datanode in the expected XML format'
- assert response.status == HttpStatus.OK.value()
- assert response.getContentAsString().contains('<xpath><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></xpath>')
- where: 'the following options for include descendants are provided in the request'
- scenario | includeDescendantsOptionString || expectedDepth
- 'direct children' | 'direct' || 1
- 'descendants' | '2' || 2
- }
-
- def 'Query data node by cps path for the given dataspace across all anchors with #scenario.'() {
- given: 'service method returns a list containing a data node from different anchors'
- def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
- def dataNode2 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor_2')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build()
- and: 'second data node for the same anchor'
- def dataNode3 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor_2')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement5', 'leaveListElement6']]).build()
- and: 'the query endpoint'
- def dataspaceName = 'my_dataspace'
- def cpsPath = 'some/cps/path'
- def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query"
- mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath,
- expectedCpsDataServiceOption, PaginationOption.NO_PAGINATION) >> [dataNode1, dataNode2, dataNode3]
- mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> 2
- when: 'query data nodes API is invoked'
- def response =
- mvc.perform(
- get(dataNodeEndpoint)
- .param('cps-path', cpsPath)
- .param('descendants', includeDescendantsOptionString))
+ def response = mvc.perform(
+ get(dataNodeEndpoint).param('cps-path', 'my/path').param('pageIndex', String.valueOf(2)).param('pageSize', String.valueOf(5)))
.andReturn().response
- then: 'the response contains the the datanode in json format'
- response.status == HttpStatus.OK.value()
- response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
- response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}')
- response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement5","leaveListElement6"]}}')
- where: 'the following options for include descendants are provided in the request'
- scenario | includeDescendantsOptionString || expectedCpsDataServiceOption
- 'no descendants by default' | '' || OMIT_DESCENDANTS
- 'no descendant explicitly' | 'none' || OMIT_DESCENDANTS
- 'descendants' | 'all' || INCLUDE_ALL_DESCENDANTS
- 'direct children' | 'direct' || DIRECT_CHILDREN_ONLY
- }
-
- def 'Query data node by cps path for the given dataspace across all anchors with pagination #scenario.'() {
- given: 'service method returns a list containing a data node from different anchors'
- def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
- def dataNode2 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor_2')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build()
- and: 'the query endpoint'
- def dataspaceName = 'my_dataspace'
- def cpsPath = 'some/cps/path'
- def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query"
- mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath,
- INCLUDE_ALL_DESCENDANTS, new PaginationOption(pageIndex,pageSize)) >> [dataNode1, dataNode2]
- mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> totalAnchors
- when: 'query data nodes API is invoked'
- def response =
- mvc.perform(
- get(dataNodeEndpoint)
- .param('cps-path', cpsPath)
- .param('descendants', "all")
- .param('pageIndex', String.valueOf(pageIndex))
- .param('pageSize', String.valueOf(pageSize)))
- .andReturn().response
- then: 'the response contains the the datanode in json format'
+ then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
+ 1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, new PaginationOption(2,5)) >> [dataNodeAsMap]
+ then: 'the response is OK and contains the the datanode in json format'
assert response.status == HttpStatus.OK.value()
- assert Integer.valueOf(response.getHeaderValue("total-pages")) == expectedTotalPageSize
- assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
- assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}')
- where: 'the following options for include descendants are provided in the request'
- scenario | pageIndex | pageSize | totalAnchors || expectedTotalPageSize
- '1st page with all anchors' | 1 | 3 | 3 || 1
- '1st page with less anchors' | 1 | 2 | 3 || 2
+ assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
+ and: 'the header indicates the correct number of pages'
+ assert response.getHeaderValue('total-pages') == '123'
}
- def 'Query data node across all anchors with pagination option with #scenario.'() {
- given: 'service method returns a list containing a data node from different anchors'
- def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
- def dataNode2 = new DataNodeBuilder().withXpath('/xpath')
- .withAnchor('my_anchor_2')
- .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build()
- and: 'the query endpoint'
- def dataspaceName = 'my_dataspace'
- def cpsPath = 'some/cps/path'
- def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query"
- mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath,
- INCLUDE_ALL_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNode1, dataNode2]
- mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> 2
+ def 'Query data node across all anchors with pagination option with #scenario i.e. no pagination.'() {
+ given: 'the query endpoint'
+ def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query"
+ and: 'the cps service facade will say there is 1 page '
+ mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', NO_PAGINATION ) >> 1
when: 'query data nodes API is invoked'
- def response =
- mvc.perform(
- get(dataNodeEndpoint)
- .param('cps-path', cpsPath)
- .param('descendants', "all")
- .param(parameterName, "1"))
- .andReturn().response
- then: 'the response contains the the datanode in json format'
+ def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param(parameterName, '1'))
+ .andReturn().response
+ then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
+ 1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNodeAsMap]
+ then: 'the response is OK and contains the datanode in json format'
assert response.status == HttpStatus.OK.value()
- assert Integer.valueOf(response.getHeaderValue("total-pages")) == 1
- assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
- assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}')
- where:
+ assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
+ and: 'the header indicates the correct number of pages'
+ assert response.getHeaderValue('total-pages') == '1'
+ where: 'only the following rest parameter is used'
scenario | parameterName
'only page size' | 'pageSize'
'only page index' | 'pageIndex'
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
index 4e1d27cda2..0cbdffbdc4 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2020 Pantheon.tech
- * Modifications Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2021-2025 Nordix Foundation
* Modifications Copyright (C) 2021 Bell Canada.
* Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
@@ -26,23 +26,24 @@ package org.onap.cps.rest.exceptions
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.json.JsonSlurper
-import org.onap.cps.api.CpsDataspaceService
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsDataspaceService
+import org.onap.cps.api.CpsFacade
import org.onap.cps.api.CpsModuleService
import org.onap.cps.api.CpsNotificationService
import org.onap.cps.api.CpsQueryService
-import org.onap.cps.rest.controller.CpsRestInputMapper
import org.onap.cps.api.exceptions.AlreadyDefinedException
import org.onap.cps.api.exceptions.CpsException
import org.onap.cps.api.exceptions.CpsPathException
import org.onap.cps.api.exceptions.DataInUseException
import org.onap.cps.api.exceptions.DataNodeNotFoundException
import org.onap.cps.api.exceptions.DataValidationException
+import org.onap.cps.api.exceptions.DataspaceInUseException
import org.onap.cps.api.exceptions.ModelValidationException
import org.onap.cps.api.exceptions.NotFoundInDataspaceException
import org.onap.cps.api.exceptions.SchemaSetInUseException
-import org.onap.cps.api.exceptions.DataspaceInUseException
+import org.onap.cps.rest.controller.CpsRestInputMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.spockframework.spring.SpringBean
@@ -65,6 +66,9 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
class CpsRestExceptionHandlerSpec extends Specification {
@SpringBean
+ CpsFacade mockCpsFacade = Stub()
+
+ @SpringBean
CpsDataspaceService mockCpsAdminService = Stub()
@SpringBean
@@ -86,10 +90,10 @@ class CpsRestExceptionHandlerSpec extends Specification {
CpsRestInputMapper cpsRestInputMapper = Stub()
@SpringBean
- PrefixResolver prefixResolver = Mock()
+ PrefixResolver prefixResolver = Stub()
@SpringBean
- CpsNotificationService mockCpsNotificationService = Mock()
+ CpsNotificationService mockCpsNotificationService = Stub()
@Autowired
MockMvc mvc
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java b/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java
new file mode 100644
index 0000000000..8933f02cb4
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java
@@ -0,0 +1,96 @@
+/*
+ * ============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.api;
+
+import java.util.List;
+import java.util.Map;
+import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.api.parameters.PaginationOption;
+
+public interface CpsFacade {
+
+ /**
+ * Get the first data node for a given dataspace, anchor and xpath.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param xpath the xpath
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data node and its descendants
+ */
+ Map<String, Object> getFirstDataNodeByAnchor(String dataspaceName,
+ String anchorName,
+ String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
+
+ /**
+ * Get data nodes for a given dataspace, anchor and xpath.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param xpath the xpath
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data nodes and their descendants
+ */
+ List<Map<String, Object>> getDataNodesByAnchor(String dataspaceName,
+ String anchorName,
+ String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
+
+ /**
+ * Query the given anchor using a cps path expression.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param cpsPath the xpath i.e. query
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data nodes and their descendants
+ */
+ List<Map<String, Object>> executeAnchorQuery(String dataspaceName,
+ String anchorName,
+ String cpsPath,
+ FetchDescendantsOption fetchDescendantsOption);
+
+ /**
+ * Query the given dataspace (all anchors) using a cps path expression.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param cpsPath the xpath i.e. query
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data nodes and their descendants
+ */
+ List<Map<String, Object>> executeDataspaceQuery(String dataspaceName,
+ String cpsPath,
+ FetchDescendantsOption fetchDescendantsOption,
+ PaginationOption paginationOption);
+
+ /**
+ * Query how many anchors wil be returned for the given dataspace and a cps path query.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param cpsPath the xpath i.e. query
+ * @param paginationOption the options for pagination
+ * @return the number of anchors involved in the output
+ */
+ int countAnchorsInDataspaceQuery(String dataspaceName,
+ String cpsPath,
+ PaginationOption paginationOption);
+}
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/api/parameters/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/api/parameters/FetchDescendantsOption.java
index 46022ba46b..05fa366239 100644
--- a/cps-service/src/main/java/org/onap/cps/api/parameters/FetchDescendantsOption.java
+++ b/cps-service/src/main/java/org/onap/cps/api/parameters/FetchDescendantsOption.java
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Pantheon.tech
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-2025 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,7 @@ package org.onap.cps.api.parameters;
import com.google.common.base.Strings;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.onap.cps.api.exceptions.DataValidationException;
@@ -44,6 +45,12 @@ public class FetchDescendantsOption {
private static final Pattern FETCH_DESCENDANTS_OPTION_PATTERN =
Pattern.compile("^$|^all$|^none$|^direct$|^[0-9]+$|^-1$|^1$");
+ /**
+ * Get depth.
+ *
+ * @return depth: -1 for all descendants, 0 for no descendants, or positive value for fixed level of descendants
+ */
+ @Getter
private final int depth;
private final String optionName;
@@ -76,15 +83,7 @@ public class FetchDescendantsOption {
}
/**
- * Get depth.
- * @return depth: -1 for all descendants, 0 for no descendants, or positive value for fixed level of descendants
- */
- public int getDepth() {
- return depth;
- }
-
- /**
- * get fetch descendants option for given descendant.
+ * Convert fetch descendants option from string to enum with depth.
*
* @param fetchDescendantsOptionAsString fetch descendants option string
* @return fetch descendants option for given descendant
@@ -99,11 +98,22 @@ public class FetchDescendantsOption {
} else if ("1".equals(fetchDescendantsOptionAsString) || "direct".equals(fetchDescendantsOptionAsString)) {
return FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
} else {
- final Integer depth = Integer.valueOf(fetchDescendantsOptionAsString);
+ final int depth = Integer.parseInt(fetchDescendantsOptionAsString);
return new FetchDescendantsOption(depth);
}
}
+ /**
+ * Convert include all-descendants boolean parameter to FetchDescendantsOption enum.
+ *
+ * @param includedDescendantsOptionAsBoolean fetch descendants option as Boolean
+ * @return fetch descendants option for given descendant
+ */
+ public static FetchDescendantsOption getFetchDescendantsOption(final Boolean includedDescendantsOptionAsBoolean) {
+ return Boolean.TRUE.equals(includedDescendantsOptionAsBoolean)
+ ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
+ }
+
@Override
public String toString() {
return optionName;
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..a93bf9ac82 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
@@ -1,9 +1,9 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * Copyright (C) 2021-2025 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,13 +24,16 @@
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 static org.onap.cps.utils.ContentType.JSON;
+
import io.micrometer.core.annotation.Timed;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -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;
@@ -50,11 +53,9 @@ import org.onap.cps.events.model.Data.Operation;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.utils.ContentType;
import org.onap.cps.utils.CpsValidator;
-import org.onap.cps.utils.DataMapUtils;
+import org.onap.cps.utils.DataMapper;
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,36 +63,33 @@ 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;
private final CpsDeltaService cpsDeltaService;
+ private final DataMapper dataMapper;
private final JsonObjectMapper jsonObjectMapper;
- private final PrefixResolver prefixResolver;
@Override
public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
final OffsetDateTime observedTimestamp) {
- saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
+ saveData(dataspaceName, anchorName, nodeData, observedTimestamp, JSON);
}
@Override
- @Timed(value = "cps.data.service.datanode.root.save",
- description = "Time taken to save a root data node")
+ @Timed(value = "cps.data.service.datanode.root.save", description = "Time taken to save a root data node")
public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
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);
}
@@ -99,34 +97,32 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String nodeData, final OffsetDateTime observedTimestamp) {
- saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
+ saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, JSON);
}
@Override
- @Timed(value = "cps.data.service.datanode.child.save",
- description = "Time taken to save a child data node")
+ @Timed(value = "cps.data.service.datanode.child.save", description = "Time taken to save a child data node")
public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String nodeData, 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.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
}
@Override
- @Timed(value = "cps.data.service.list.element.save",
- description = "Time taken to save list elements")
+ @Timed(value = "cps.data.service.list.element.save", description = "Time taken to save list elements")
public void saveListElements(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String nodeData,
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,
@@ -136,8 +132,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.get",
- description = "Time taken to get data nodes for an xpath")
+ @Timed(value = "cps.data.service.datanode.get", description = "Time taken to get data nodes for an xpath")
public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
final String xpath,
final FetchDescendantsOption fetchDescendantsOption) {
@@ -146,8 +141,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.batch.get",
- description = "Time taken to get a batch of data nodes")
+ @Timed(value = "cps.data.service.datanode.batch.get", description = "Time taken to get a batch of data nodes")
public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
final Collection<String> xpaths,
final FetchDescendantsOption fetchDescendantsOption) {
@@ -163,8 +157,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 +174,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,
+ JSON);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(anchor, dataNodeUpdate);
}
@@ -210,8 +205,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.get.delta",
- description = "Time taken to get delta between anchors")
+ @Timed(value = "cps.data.service.get.delta", description = "Time taken to get delta between anchors")
public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
final String sourceAnchorName,
final String targetAnchorName, final String xpath,
@@ -225,9 +219,9 @@ public class CpsDataServiceImpl implements CpsDataService {
return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
}
+ @Override
@Timed(value = "cps.data.service.get.deltaBetweenAnchorAndPayload",
description = "Time taken to get delta between anchor and a payload")
- @Override
public List<DeltaReport> getDeltaByDataspaceAnchorAndPayload(final String dataspaceName,
final String sourceAnchorName, final String xpath,
final Map<String, String> yangResourceContentPerName,
@@ -256,8 +250,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,31 +260,30 @@ 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));
}
@Override
- @Timed(value = "cps.data.service.list.update",
- description = "Time taken to update a list")
+ @Timed(value = "cps.data.service.list.update", description = "Time taken to update a list")
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
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);
}
@Override
- @Timed(value = "cps.data.service.list.batch.update",
- description = "Time taken to update a batch of lists")
+ @Timed(value = "cps.data.service.list.batch.update", description = "Time taken to update a batch of lists")
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -300,8 +293,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.delete",
- description = "Time taken to delete a datanode")
+ @Timed(value = "cps.data.service.datanode.delete", description = "Time taken to delete a datanode")
public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -311,8 +303,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.batch.delete",
- description = "Time taken to delete a batch of datanodes")
+ @Timed(value = "cps.data.service.datanode.batch.delete", description = "Time taken to delete a batch of datanodes")
public void deleteDataNodes(final String dataspaceName, final String anchorName,
final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -348,8 +339,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.list.delete",
- description = "Time taken to delete a list or list element")
+ @Timed(value = "cps.data.service.list.delete", description = "Time taken to delete a list or list element")
public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -362,156 +352,34 @@ 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);
}
- private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor,
+ private Collection<DataNode> rebuildSourceDataNodes(final String xpath,
+ final Anchor sourceAnchor,
final Collection<DataNode> sourceDataNodes) {
-
final Collection<DataNode> sourceDataNodesRebuilt = new ArrayList<>();
if (sourceDataNodes != null) {
- final String sourceDataNodesAsJson = getDataNodesAsJson(sourceAnchor, sourceDataNodes);
- sourceDataNodesRebuilt.addAll(
- buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
+ final Map<String, Object> sourceDataNodesAsMap = dataMapper.toFlatDataMap(sourceAnchor, sourceDataNodes);
+ final String sourceDataNodesAsJson = jsonObjectMapper.asJsonString(sourceDataNodesAsMap);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorXpathAndNodeData(sourceAnchor, xpath, sourceDataNodesAsJson, JSON);
+ sourceDataNodesRebuilt.addAll(dataNodes);
}
return sourceDataNodesRebuilt;
}
- private Collection<DataNode> buildTargetDataNodes(final Anchor sourceAnchor, final String xpath,
+ private Collection<DataNode> buildTargetDataNodes(final Anchor sourceAnchor,
+ final String xpath,
final Map<String, String> yangResourceContentPerName,
final String targetData) {
if (yangResourceContentPerName.isEmpty()) {
- return buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, targetData, ContentType.JSON);
- } else {
- return buildDataNodesWithYangResourceAndXpath(yangResourceContentPerName, xpath,
- targetData, ContentType.JSON);
+ return dataNodeFactory.createDataNodesWithAnchorXpathAndNodeData(sourceAnchor, xpath, targetData, JSON);
}
- }
-
- private String getDataNodesAsJson(final Anchor anchor, final Collection<DataNode> dataNodes) {
-
- final List<Map<String, Object>> prefixToDataNodes = prefixResolver(anchor, dataNodes);
- final Map<String, Object> targetDataAsJsonObject = getNodeDataAsJsonString(prefixToDataNodes);
- return jsonObjectMapper.asJsonString(targetDataAsJsonObject);
- }
-
- private Map<String, Object> getNodeDataAsJsonString(final List<Map<String, Object>> prefixToDataNodes) {
- final Map<String, Object> nodeDataAsJson = new HashMap<>();
- for (final Map<String, Object> prefixToDataNode : prefixToDataNodes) {
- nodeDataAsJson.putAll(prefixToDataNode);
- }
- return nodeDataAsJson;
- }
-
- private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) {
- final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size());
- for (final DataNode dataNode: dataNodes) {
- final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
- prefixToDataNodes.add(prefixToDataNode);
- }
- 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);
+ return dataNodeFactory
+ .createDataNodesWithYangResourceXpathAndNodeData(yangResourceContentPerName, xpath, targetData, JSON);
}
private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
@@ -523,8 +391,10 @@ public class CpsDataServiceImpl implements CpsDataService {
}
}
- private void sendDataUpdatedEvent(final Anchor anchor, final String xpath,
- final Operation operation, final OffsetDateTime observedTimestamp) {
+ private void sendDataUpdatedEvent(final Anchor anchor,
+ final String xpath,
+ final Operation operation,
+ final OffsetDateTime observedTimestamp) {
try {
cpsDataUpdateEventsService.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp);
} catch (final Exception exception) {
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java
new file mode 100644
index 0000000000..4ac0d5d8e8
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java
@@ -0,0 +1,97 @@
+/*
+ * ============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.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsFacade;
+import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.api.parameters.PaginationOption;
+import org.onap.cps.utils.DataMapper;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class CpsFacadeImpl implements CpsFacade {
+
+ private final CpsDataService cpsDataService;
+ private final CpsQueryService cpsQueryService;
+ private final DataMapper dataMapper;
+
+ @Override
+ public Map<String, Object> getFirstDataNodeByAnchor(final String dataspaceName,
+ final String anchorName,
+ final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption).iterator().next();
+ return dataMapper.toDataMap(dataspaceName, anchorName, dataNode);
+ }
+
+ @Override
+ public List<Map<String, Object>> getDataNodesByAnchor(final String dataspaceName,
+ final String anchorName,
+ final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption);
+ return dataMapper.toDataMaps(dataspaceName, anchorName, dataNodes);
+ }
+
+ @Override
+ public List<Map<String, Object>> executeAnchorQuery(final String dataspaceName,
+ final String anchorName,
+ final String cpsPath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<DataNode> dataNodes =
+ cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
+ return dataMapper.toDataMaps(dataspaceName, anchorName, dataNodes);
+ }
+
+ @Override
+ public List<Map<String, Object>> executeDataspaceQuery(final String dataspaceName,
+ final String cpsPath,
+ final FetchDescendantsOption fetchDescendantsOption,
+ final PaginationOption paginationOption) {
+ final Collection<DataNode> dataNodes = cpsQueryService.queryDataNodesAcrossAnchors(dataspaceName,
+ cpsPath, fetchDescendantsOption, paginationOption);
+ return dataMapper.toDataMaps(dataspaceName, dataNodes);
+ }
+
+ @Override
+ public int countAnchorsInDataspaceQuery(final String dataspaceName,
+ final String cpsPath,
+ final PaginationOption paginationOption) {
+ if (paginationOption == PaginationOption.NO_PAGINATION) {
+ return 1;
+ }
+ final int totalAnchors = cpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath);
+ return totalAnchors <= paginationOption.getPageSize() ? 1
+ : (int) Math.ceil((double) totalAnchors / paginationOption.getPageSize());
+ }
+
+}
+
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java
index 09ef637965..dc293b26e2 100644
--- a/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2025 TechMahindra Ltd.
+ * Modifications 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.
@@ -22,7 +23,6 @@ package org.onap.cps.impl;
import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -36,8 +36,7 @@ import org.onap.cps.api.model.DataNode;
import org.onap.cps.cpspath.parser.CpsPathUtil;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.utils.ContentType;
-import org.onap.cps.utils.DataMapUtils;
-import org.onap.cps.utils.PrefixResolver;
+import org.onap.cps.utils.DataMapper;
import org.onap.cps.utils.YangParser;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.springframework.stereotype.Service;
@@ -53,10 +52,10 @@ public class CpsNotificationServiceImpl implements CpsNotificationService {
private final YangParser yangParser;
- private final PrefixResolver prefixResolver;
+ private final DataMapper dataMapper;
private static final String ADMIN_DATASPACE = "CPS-Admin";
- private static final String ANCHOR_NAME = "cps-notification-subscriptions";
+ private static final String CPS_SUBSCRIPTION_ANCHOR_NAME = "cps-notification-subscriptions";
private static final String DATASPACE_SUBSCRIPTION_XPATH_FORMAT = "/dataspaces/dataspace[@name='%s']";
private static final String ANCHORS_SUBSCRIPTION_XPATH_FORMAT = "/dataspaces/dataspace[@name='%s']/anchors";
private static final String ANCHOR_SUBSCRIPTION_XPATH_FORMAT =
@@ -65,29 +64,22 @@ public class CpsNotificationServiceImpl implements CpsNotificationService {
@Override
public void createNotificationSubscription(final String notificationSubscriptionAsJson, final String xpath) {
- final Anchor anchor = cpsAnchorService.getAnchor(ADMIN_DATASPACE, ANCHOR_NAME);
+ final Anchor anchor = cpsAnchorService.getAnchor(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME);
final Collection<DataNode> dataNodes =
buildDataNodesWithParentNodeXpath(anchor, xpath, notificationSubscriptionAsJson);
- cpsDataPersistenceService.addListElements(ADMIN_DATASPACE, ANCHOR_NAME, xpath, dataNodes);
+ cpsDataPersistenceService.addListElements(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath, dataNodes);
}
@Override
public void deleteNotificationSubscription(final String xpath) {
- cpsDataPersistenceService.deleteDataNode(ADMIN_DATASPACE, ANCHOR_NAME, xpath);
+ cpsDataPersistenceService.deleteDataNode(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath);
}
@Override
public List<Map<String, Object>> getNotificationSubscription(final String xpath) {
- final Collection<DataNode> dataNodes =
- cpsDataPersistenceService.getDataNodes(ADMIN_DATASPACE, ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
- final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
- final Anchor anchor = cpsAnchorService.getAnchor(ADMIN_DATASPACE, ANCHOR_NAME);
- for (final DataNode dataNode: dataNodes) {
- final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
- dataMaps.add(dataMap);
- }
- return dataMaps;
+ final Collection<DataNode> dataNodes = cpsDataPersistenceService
+ .getDataNodes(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
+ return dataMapper.toDataMaps(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, dataNodes);
}
@Override
@@ -103,7 +95,8 @@ public class CpsNotificationServiceImpl implements CpsNotificationService {
private boolean isNotificationEnabledForXpath(final String xpath) {
try {
- cpsDataPersistenceService.getDataNodes(ADMIN_DATASPACE, ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
+ cpsDataPersistenceService
+ .getDataNodes(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
} catch (final DataNodeNotFoundException e) {
return false;
}
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/main/java/org/onap/cps/utils/DataMapper.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java
new file mode 100644
index 0000000000..6e7eff9132
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java
@@ -0,0 +1,133 @@
+/*
+ * ============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.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsAnchorService;
+import org.onap.cps.api.model.Anchor;
+import org.onap.cps.api.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DataMapper {
+
+ private final CpsAnchorService cpsAnchorService;
+ private final PrefixResolver prefixResolver;
+
+ /**
+ * Convert a data node to a data map.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param dataNode the data node to convert
+ * @return the data node represented as a map of key value pairs
+ */
+ public Map<String, Object> toDataMap(final String dataspaceName, final String anchorName, final DataNode dataNode) {
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ return DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+ }
+
+ /**
+ * Convert a collection of data nodes to a list of data maps.
+ *
+ * @param dataspaceName the name dataspace name
+ * @param anchorName the name of the anchor
+ * @param dataNodes the data nodes to convert
+ * @return a list of maps representing the data nodes
+ */
+ public List<Map<String, Object>> toDataMaps(final String dataspaceName, final String anchorName,
+ final Collection<DataNode> dataNodes) {
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ return toDataMaps(anchor, dataNodes);
+ }
+
+ /**
+ * Convert a collection of data nodes to a list of data maps.
+ *
+ * @param anchor the anchor
+ * @param dataNodes the data nodes to convert
+ * @return a list of maps representing the data nodes
+ */
+ public List<Map<String, Object>> toDataMaps(final Anchor anchor, final Collection<DataNode> dataNodes) {
+ final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+ for (final DataNode dataNode : dataNodes) {
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+ dataMaps.add(dataMap);
+ }
+ return dataMaps;
+ }
+
+ /**
+ * Convert a collection of data nodes (belonging to multiple anchors) to a list of data maps.
+ *
+ * @param dataspaceName the name dataspace name
+ * @param dataNodes the data nodes to convert
+ * @return a list of maps representing the data nodes
+ */
+ public List<Map<String, Object>> toDataMaps(final String dataspaceName, final Collection<DataNode> dataNodes) {
+ final List<Map<String, Object>> dataNodesAsMaps = new ArrayList<>(dataNodes.size());
+ final Map<String, List<DataNode>> dataNodesPerAnchor = groupDataNodesPerAnchor(dataNodes);
+ for (final Map.Entry<String, List<DataNode>> dataNodesPerAnchorEntry : dataNodesPerAnchor.entrySet()) {
+ final String anchorName = dataNodesPerAnchorEntry.getKey();
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ final DataNode dataNode = dataNodesPerAnchorEntry.getValue().get(0);
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ final Map<String, Object> dataNodeAsMap = DataMapUtils.toDataMapWithIdentifierAndAnchor(
+ dataNodesPerAnchorEntry.getValue(), anchorName, prefix);
+ dataNodesAsMaps.add(dataNodeAsMap);
+ }
+ return dataNodesAsMaps;
+ }
+
+ /**
+ * Convert a collection of data nodes to a data map.
+ *
+ * @param anchor the anchor
+ * @param dataNodes the data nodes to convert
+ * @return a map representing the data nodes
+ */
+ public Map<String, Object> toFlatDataMap(final Anchor anchor, final Collection<DataNode> dataNodes) {
+ final List<Map<String, Object>> dataNodesAsMaps = toDataMaps(anchor, dataNodes);
+ return flattenDataNodesMaps(dataNodesAsMaps);
+ }
+
+ private Map<String, Object> flattenDataNodesMaps(final List<Map<String, Object>> dataNodesAsMaps) {
+ final Map<String, Object> dataNodesAsFlatMap = new HashMap<>();
+ for (final Map<String, Object> dataNodeAsMap : dataNodesAsMaps) {
+ dataNodesAsFlatMap.putAll(dataNodeAsMap);
+ }
+ return dataNodesAsFlatMap;
+ }
+
+ private static Map<String, List<DataNode>> groupDataNodesPerAnchor(final Collection<DataNode> dataNodes) {
+ return dataNodes.stream().collect(Collectors.groupingBy(DataNode::getAnchorName));
+ }
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
index bd348a25d1..e59029f916 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2024 Nordix Foundation.
+ * Copyright (C) 2022-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.
@@ -47,25 +47,29 @@ public class PrefixResolver {
* @return the prefix of the module the top level element of given xpath
*/
public String getPrefix(final Anchor anchor, final String xpath) {
+ return getPrefix(anchor.getDataspaceName(), anchor.getSchemaSetName(), xpath);
+ }
+
+ private String getPrefix(final String dataspaceName, final String schemaSetName, final String xpath) {
final CpsPathQuery cpsPathQuery = CpsPathUtil.getCpsPathQuery(xpath);
if (cpsPathQuery.getCpsPathPrefixType() != CpsPathPrefixType.ABSOLUTE) {
return "";
}
- final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0);
+ final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0);
final YangTextSchemaSourceSet yangTextSchemaSourceSet =
- yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), anchor.getSchemaSetName());
+ yangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName);
final SchemaContext schemaContext = yangTextSchemaSourceSet.getSchemaContext();
return schemaContext.getChildNodes().stream()
- .filter(DataNodeContainer.class::isInstance)
- .map(SchemaNode::getQName)
- .filter(qname -> qname.getLocalName().equals(topLevelContainerName))
- .findFirst()
- .map(QName::getModule)
- .flatMap(schemaContext::findModule)
- .map(Module::getPrefix)
- .orElse("");
+ .filter(DataNodeContainer.class::isInstance)
+ .map(SchemaNode::getQName)
+ .filter(qname -> qname.getLocalName().equals(topLevelContainerName))
+ .findFirst()
+ .map(QName::getModule)
+ .flatMap(schemaContext::findModule)
+ .map(Module::getPrefix)
+ .orElse("");
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy
index 126e5b197b..508178b419 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-2025 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,10 @@ package org.onap.cps.api.parameters
import org.onap.cps.api.exceptions.DataValidationException
import spock.lang.Specification
+import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
+import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
+
class FetchDescendantsOptionSpec extends Specification {
def 'Has next descendant for fetch descendant option: #scenario'() {
@@ -105,11 +109,22 @@ class FetchDescendantsOptionSpec extends Specification {
expect: 'each fetch descendant option has the correct String value'
assert fetchDescendantsOption.toString() == expectedStringValue
where: 'the following option is used'
- fetchDescendantsOption || expectedStringValue
- FetchDescendantsOption.OMIT_DESCENDANTS || 'OmitDescendants'
- FetchDescendantsOption.DIRECT_CHILDREN_ONLY || 'DirectChildrenOnly'
- FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS || 'IncludeAllDescendants'
- new FetchDescendantsOption(2) || 'Depth=2'
+ fetchDescendantsOption || expectedStringValue
+ OMIT_DESCENDANTS || 'OmitDescendants'
+ DIRECT_CHILDREN_ONLY || 'DirectChildrenOnly'
+ INCLUDE_ALL_DESCENDANTS || 'IncludeAllDescendants'
+ new FetchDescendantsOption(2) || 'Depth=2'
+ }
+
+ def 'Convert include-descendants boolean to fetch descendants option with : #includeDescendants'() {
+ when: 'convert boolean #includeDescendants'
+ def result = FetchDescendantsOption.getFetchDescendantsOption(includeDescendants)
+ then: 'result is the expected option'
+ assert result == expectedFetchDescendantsOption
+ where: 'following parameters are used'
+ includeDescendants || expectedFetchDescendantsOption
+ true || INCLUDE_ALL_DESCENDANTS
+ false || OMIT_DESCENDANTS
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy
index d78c8bb47f..a21a17fabd 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy
@@ -20,7 +20,6 @@
package org.onap.cps.impl
-
import org.onap.cps.utils.CpsValidator
import org.onap.cps.spi.CpsAdminPersistenceService
import org.onap.cps.spi.CpsDataPersistenceService
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..967bcc0aa0 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
@@ -1,9 +1,9 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * Copyright (C) 2021-2025 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.
@@ -30,17 +30,18 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
-import org.onap.cps.events.CpsDataUpdateEventsService
-import org.onap.cps.utils.CpsValidator
-import org.onap.cps.spi.CpsDataPersistenceService
-import org.onap.cps.api.parameters.FetchDescendantsOption
import org.onap.cps.api.exceptions.ConcurrencyException
import org.onap.cps.api.exceptions.DataNodeNotFoundExceptionBatch
import org.onap.cps.api.exceptions.DataValidationException
import org.onap.cps.api.exceptions.SessionManagerException
import org.onap.cps.api.exceptions.SessionTimeoutException
import org.onap.cps.api.model.Anchor
+import org.onap.cps.api.parameters.FetchDescendantsOption
+import org.onap.cps.events.CpsDataUpdateEventsService
+import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.CpsValidator
+import org.onap.cps.utils.DataMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.onap.cps.utils.YangParser
@@ -68,9 +69,11 @@ class CpsDataServiceImplSpec extends Specification {
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockPrefixResolver = Mock(PrefixResolver)
+ def dataMapper = new DataMapper(mockCpsAnchorService, mockPrefixResolver)
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService,
- mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
+ dataNodeFactory, mockCpsValidator, yangParser, mockCpsDeltaService, dataMapper, jsonObjectMapper)
def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
def loggingListAppender
@@ -107,8 +110,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 +135,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 +143,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 +164,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 +174,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 +192,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 +206,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/CpsFacadeImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy
new file mode 100644
index 0000000000..c754970518
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy
@@ -0,0 +1,114 @@
+/*
+ * ============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.impl
+
+import org.onap.cps.api.CpsAnchorService
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsQueryService
+import org.onap.cps.api.model.DataNode
+import org.onap.cps.api.parameters.PaginationOption
+import org.onap.cps.utils.DataMapper
+import org.onap.cps.utils.PrefixResolver
+import spock.lang.Specification
+
+import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION
+
+class CpsFacadeImplSpec extends Specification {
+
+ def mockCpsDataService = Mock(CpsDataService)
+ def mockCpsQueryService = Mock(CpsQueryService)
+ def mockCpsAnchorService = Mock(CpsAnchorService)
+ def mockPrefixResolver = Mock(PrefixResolver)
+ def dataMapper = new DataMapper(mockCpsAnchorService, mockPrefixResolver)
+
+ def myFetchDescendantsOption = OMIT_DESCENDANTS
+ def myPaginationOption = NO_PAGINATION
+
+ def objectUnderTest = new CpsFacadeImpl(mockCpsDataService, mockCpsQueryService , dataMapper)
+
+ def dataNode1 = new DataNode(xpath:'/path1', anchorName: 'my anchor')
+ def dataNode2 = new DataNode(xpath:'/path2', anchorName: 'my anchor')
+ def dataNode3 = new DataNode(xpath:'/path3', anchorName: 'other anchor')
+
+ def setup() {
+ mockCpsDataService.getDataNodes('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption) >> [ dataNode1, dataNode2]
+ mockPrefixResolver.getPrefix(_, '/path1') >> 'prefix1'
+ mockPrefixResolver.getPrefix(_, '/path2') >> 'prefix2'
+ mockPrefixResolver.getPrefix(_, '/path3') >> 'prefix3'
+ }
+
+ def 'Get one data node.'() {
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.getFirstDataNodeByAnchor('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption)
+ then: 'only the first node (from the data service result) is returned'
+ assert result.size() == 1
+ assert result.keySet()[0] == 'prefix1:path1'
+ }
+
+ def 'Get multiple data nodes.'() {
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.getDataNodesByAnchor('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption)
+ then: 'all nodes (from the data service result) are returned'
+ assert result.size() == 2
+ assert result[0].keySet()[0] == 'prefix1:path1'
+ assert result[1].keySet()[0] == 'prefix2:path2'
+ }
+
+ def 'Execute anchor query.'() {
+ given: 'the cps query service returns two data nodes'
+ mockCpsQueryService.queryDataNodes('my dataspace', 'my anchor', 'my cps path', myFetchDescendantsOption) >> [ dataNode1, dataNode2]
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.executeAnchorQuery('my dataspace', 'my anchor', 'my cps path', myFetchDescendantsOption)
+ then: 'all nodes (from the query service result) are returned'
+ assert result.size() == 2
+ assert result[0].keySet()[0] == 'prefix1:path1'
+ assert result[1].keySet()[0] == 'prefix2:path2'
+ }
+
+ def 'Execute dataspace query.'() {
+ given: 'the cps query service returns two data nodes (on two different anchors)'
+ mockCpsQueryService.queryDataNodesAcrossAnchors('my dataspace', 'my cps path', myFetchDescendantsOption, myPaginationOption) >> [ dataNode1, dataNode2, dataNode3 ]
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.executeDataspaceQuery('my dataspace', 'my cps path', myFetchDescendantsOption, myPaginationOption)
+ then: 'all nodes (from the query service result) are returned, grouped by anchor'
+ assert result.size() == 2
+ assert result[0].toString() == '{anchorName=my anchor, dataNodes=[{prefix1:path1={}}, {prefix1:path2={}}]}'
+ assert result[1].toString() == '{anchorName=other anchor, dataNodes=[{prefix3:path3={}}]}'
+ }
+
+ def 'How many pages (anchors) could be in the output with #scenario.'() {
+ given: 'the query service says there are 10 anchors for the given query'
+ mockCpsQueryService.countAnchorsForDataspaceAndCpsPath('my dataspace', 'my cps path') >> 10
+ expect: 'the correct number of pages is returned'
+ assert objectUnderTest.countAnchorsInDataspaceQuery('my dataspace', 'my cps path', paginationOption) == expectedNumberOfPages
+ where: 'the following pagination options are used'
+ scenario | paginationOption || expectedNumberOfPages
+ 'no pagination' | NO_PAGINATION || 1
+ '1 anchor per page' | new PaginationOption(1,1) || 10
+ '1 anchor per page, start at 2' | new PaginationOption(2,1) || 10
+ '2 anchors per page' | new PaginationOption(1,2) || 5
+ '3 anchors per page' | new PaginationOption(1,3) || 4
+ '10 anchors per page' | new PaginationOption(1,10) || 1
+ '100 anchors per page' | new PaginationOption(1,100) || 1
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy
index b7f06456c9..ab7853c8e6 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2025 TechMahindra Ltd.
+ * Modifications 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.
@@ -21,22 +22,22 @@
package org.onap.cps.impl
import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.exceptions.DataNodeNotFoundException
import org.onap.cps.api.exceptions.DataValidationException
import org.onap.cps.api.model.Anchor
-import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.api.parameters.FetchDescendantsOption
import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.utils.DataMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.onap.cps.utils.YangParser
-import org.onap.cps.TestUtils
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.springframework.test.context.ContextConfiguration
-
import spock.lang.Specification
@ContextConfiguration(classes = [ObjectMapper, JsonObjectMapper])
@@ -53,9 +54,9 @@ class CpsNotificationServiceImplSpec extends Specification {
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
- def mockPrefixResolver = Mock(PrefixResolver)
+ def dataMapper = new DataMapper(mockCpsAnchorService, Mock(PrefixResolver))
- def objectUnderTest = new CpsNotificationServiceImpl(mockCpsAnchorService, mockCpsDataPersistenceService, yangParser, mockPrefixResolver)
+ def objectUnderTest = new CpsNotificationServiceImpl(mockCpsAnchorService, mockCpsDataPersistenceService, yangParser, dataMapper)
def 'add notification subscription for list of dataspaces'() {
given: 'details for notification subscription and subscription root node xpath'
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..893cce6687 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.
@@ -27,12 +27,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
+import org.onap.cps.api.model.Anchor
import org.onap.cps.events.CpsDataUpdateEventsService
-import org.onap.cps.utils.CpsValidator
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
-import org.onap.cps.api.model.Anchor
import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.CpsValidator
+import org.onap.cps.utils.DataMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.onap.cps.utils.YangParser
@@ -42,23 +43,22 @@ import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import spock.lang.Specification
class E2ENetworkSliceSpec extends Specification {
- def mockModuleStoreService = Mock(CpsModulePersistenceService)
- def mockDataStoreService = Mock(CpsDataPersistenceService)
+ def mockCpsModulePersistenceService = Mock(CpsModulePersistenceService)
+ def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
def mockCpsAnchorService = Mock(CpsAnchorService)
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockCpsValidator = Mock(CpsValidator)
def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()
def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, timedYangTextSchemaSourceSetBuilder)
def mockCpsDeltaService = Mock(CpsDeltaService)
+ def dataMapper = new DataMapper(mockCpsAnchorService, Mock(PrefixResolver))
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
- def mockPrefixResolver = Mock(PrefixResolver)
- def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,
- mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
+ def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockCpsModulePersistenceService, mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator,
- yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService, dataNodeFactory, mockCpsValidator, yangParser, mockCpsDeltaService, dataMapper, jsonObjectMapper)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
def schemaSetName = 'someSchemaSet'
@@ -74,7 +74,7 @@ class E2ENetworkSliceSpec extends Specification {
when: 'Create schema set method is invoked'
cpsModuleServiceImpl.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockModuleStoreService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
+ 1 * mockCpsModulePersistenceService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
}
def 'E2E Coverage Area-Tracking Area & TA-Cell mapping model can be parsed by CPS.'() {
@@ -84,7 +84,7 @@ class E2ENetworkSliceSpec extends Specification {
when: 'Create schema set method is invoked'
cpsModuleServiceImpl.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockModuleStoreService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
+ 1 * mockCpsModulePersistenceService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
}
def 'E2E Coverage Area-Tracking Area & TA-Cell mapping data can be parsed by CPS.'() {
@@ -100,31 +100,28 @@ class E2ENetworkSliceSpec extends Specification {
new Anchor().builder().name(anchorName).schemaSetName(schemaSetName).dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >>
YangTextSchemaSourceSetBuilder.of(yangResourceContentPerName)
- mockModuleStoreService.getYangSchemaResources(dataspaceName, schemaSetName) >> schemaContext
+ mockCpsModulePersistenceService.getYangSchemaResources(dataspaceName, schemaSetName) >> schemaContext
when: 'saveData method is invoked'
cpsDataServiceImpl.saveData(dataspaceName, anchorName, jsonData, noTimestamp)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockDataStoreService.storeDataNodes('someDataspace', 'someAnchor', _) >>
+ 1 * mockCpsDataPersistenceService.storeDataNodes('someDataspace', 'someAnchor', _) >>
{ args -> dataNodeStored = args[2]}
def child = dataNodeStored[0].childDataNodes[0]
assert child.childDataNodes.size() == 1
and: 'list of Tracking Area for a Coverage Area are stored with correct xpath and child nodes '
def listOfTAForCoverageArea = child.childDataNodes[0]
- listOfTAForCoverageArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/' +
- 'coverage-area[@coverageArea=\'Washington\']'
- listOfTAForCoverageArea.childDataNodes[0].leaves.get('nRTAC') == 234
+ listOfTAForCoverageArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/coverage-area[@coverageArea=\'Washington\']'
+ assert listOfTAForCoverageArea.childDataNodes[0].leaves.get('nRTAC') == 234
and: 'list of cells in a tracking area are stored with correct xpath and child nodes '
def listOfCellsInTrackingArea = listOfTAForCoverageArea.childDataNodes[0]
- listOfCellsInTrackingArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/' +
- 'coverage-area[@coverageArea=\'Washington\']/coverageAreaTAList[@nRTAC=\'234\']'
+ listOfCellsInTrackingArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/coverage-area[@coverageArea=\'Washington\']/coverageAreaTAList[@nRTAC=\'234\']'
listOfCellsInTrackingArea.childDataNodes[0].leaves.get('cellLocalId') == 15709
}
def 'E2E Coverage Area-Tracking Area & TA-Cell mapping data can be parsed for RAN inventory.'() {
def dataNodeStored
given: 'valid yang resource as name-to-content map'
- def yangResourceContentPerName = TestUtils.getYangResourcesAsMap(
- 'e2e/basic/cps-ran-inventory@2021-01-28.yang')
+ def yangResourceContentPerName = TestUtils.getYangResourcesAsMap('e2e/basic/cps-ran-inventory@2021-01-28.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceContentPerName).getSchemaContext()
and : 'a valid json is provided for the model'
def jsonData = TestUtils.getResourceFileContent('e2e/basic/cps-ran-inventory-data.json')
@@ -132,12 +129,11 @@ class E2ENetworkSliceSpec extends Specification {
mockCpsAnchorService.getAnchor('someDataspace', 'someAnchor') >>
new Anchor().builder().name('someAnchor').schemaSetName('someSchemaSet').dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get('someDataspace', 'someSchemaSet') >> YangTextSchemaSourceSetBuilder.of(yangResourceContentPerName)
- mockModuleStoreService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> schemaContext
+ mockCpsModulePersistenceService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> schemaContext
when: 'saveData method is invoked'
cpsDataServiceImpl.saveData('someDataspace', 'someAnchor', jsonData, noTimestamp)
then: 'parameters are validated and processing is delegated to persistence service'
- 1 * mockDataStoreService.storeDataNodes('someDataspace', 'someAnchor', _) >>
- { args -> dataNodeStored = args[2]}
+ 1 * mockCpsDataPersistenceService.storeDataNodes('someDataspace', 'someAnchor', _) >> { args -> dataNodeStored = args[2]}
and: 'the size of the tree is correct'
def cpsRanInventory = TestUtils.getFlattenMapByXpath(dataNodeStored[0])
assert cpsRanInventory.size() == 4
@@ -146,17 +142,16 @@ class E2ENetworkSliceSpec extends Specification {
def ranSlices = cpsRanInventory.get('/ran-inventory/ran-slices[@rannfnssiid=\'14559ead-f4fe-4c1c-a94c-8015fad3ea35\']')
def sliceProfilesList = cpsRanInventory.get('/ran-inventory/ran-slices[@rannfnssiid=\'14559ead-f4fe-4c1c-a94c-8015fad3ea35\']/sliceProfilesList[@sliceProfileId=\'f33a9dd8-ae51-4acf-8073-c9390c25f6f1\']')
def pLMNIdList = cpsRanInventory.get('/ran-inventory/ran-slices[@rannfnssiid=\'14559ead-f4fe-4c1c-a94c-8015fad3ea35\']/sliceProfilesList[@sliceProfileId=\'f33a9dd8-ae51-4acf-8073-c9390c25f6f1\']/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']')
- ranInventory.getChildDataNodes().size() == 1
- ranInventory.getChildDataNodes().find( {it.xpath == ranSlices.xpath})
+ assert ranInventory.getChildDataNodes().size() == 1
+ assert ranInventory.getChildDataNodes().find( {it.xpath == ranSlices.xpath})
and: 'ranSlices contains the correct child node'
- ranSlices.getChildDataNodes().size() == 1
- ranSlices.getChildDataNodes().find( {it.xpath == sliceProfilesList.xpath})
+ assert ranSlices.getChildDataNodes().size() == 1
+ assert ranSlices.getChildDataNodes().find( {it.xpath == sliceProfilesList.xpath})
and: 'sliceProfilesList contains the correct child node'
- sliceProfilesList.getChildDataNodes().size() == 1
- sliceProfilesList.getChildDataNodes().find( {it.xpath == pLMNIdList.xpath})
+ assert sliceProfilesList.getChildDataNodes().size() == 1
+ assert sliceProfilesList.getChildDataNodes().find( {it.xpath == pLMNIdList.xpath})
and: 'pLMNIdList contains no children'
- pLMNIdList.getChildDataNodes().size() == 0
-
+ assert pLMNIdList.getChildDataNodes().size() == 0
}
def 'E2E RAN Schema Model.'(){
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/docs/cps-path.rst b/docs/cps-path.rst
index eb203d8918..cfaad3ca57 100644
--- a/docs/cps-path.rst
+++ b/docs/cps-path.rst
@@ -1,6 +1,6 @@
.. This work is licensed under a Creative Commons Attribution 4.0 International License.
.. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021-2023 Nordix Foundation
+.. Copyright (C) 2021-2025 Nordix Foundation
.. Modifications Copyright (C) 2023 TechMahindra Ltd
.. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
@@ -183,7 +183,7 @@ General Notes
Query Syntax
============
-``( <absolute-path> | <descendant-path> ) [ <leaf-conditions> ] [ <text()-condition> ] [ <contains()-condition> ] [ <ancestor-axis> ]``
+``( <absolute-path> | <descendant-path> ) [ <leaf-conditions> ] [ <text()-condition> ] [ <contains()-condition> ] [ <ancestor-axis> ] [ <attribute-axis> ]``
Each CPS path expression need to start with an 'absolute' or 'descendant' xpath.
@@ -310,3 +310,21 @@ The ancestor axis can be added to any CPS path query but has to be the last part
**Limitations**
- Ancestor list elements can only be addressed using the list key leaf.
- List elements with compound keys are not supported.
+
+attribute-axis
+--------------
+
+The attribute axis can be added to a CPS path query at the end. It will return only distinct values of a specified leaf.
+
+**Syntax**: ``<cps-path> ( '/@' <leaf-name> )?``
+ - ``cps-path``: Any CPS path query.
+ - ``leaf-name``: The name of the leaf (attribute) for which values should be returned.
+
+**Examples**
+ - ``//categories/@name``
+ - ``//categories[@code='1']/books/@price``
+ - ``//books/ancestor::bookstore/@bookstore-name``
+
+**Notes**
+ - The output is a list of attribute-value pairs. For example, ``[{"name":"Kids"},{"name":"SciFi"}]``
+ - Only unique values will be returned. For example, if 3 books have a price of 5, then 5 will be returned only once.
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/cps/QueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
index acc95cab8d..53e39ed9c0 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation
+ * Copyright (C) 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.
@@ -41,7 +41,7 @@ class QueryPerfTest extends CpsPerfTestBase {
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the expected number of nodes is returned'
assert countDataNodesInTree(result) == expectedNumberOfDataNodes
- and: 'all data is read within #durationLimit ms and memory used is within limit'
+ and: 'all data is read within #durationLimit seconds and memory used is within limit'
recordAndAssertResourceUsage("Query 1 anchor ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpsPath || durationLimit | memoryLimit | expectedNumberOfDataNodes
@@ -60,7 +60,7 @@ class QueryPerfTest extends CpsPerfTestBase {
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the expected number of nodes is returned'
assert countDataNodesInTree(result) == expectedNumberOfDataNodes
- and: 'all data is read within #durationLimit ms and memory used is within limit'
+ and: 'all data is read within #durationLimit seconds and memory used is within limit'
recordAndAssertResourceUsage("Query across anchors ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpspath || durationLimit | memoryLimit | expectedNumberOfDataNodes
@@ -78,7 +78,7 @@ class QueryPerfTest extends CpsPerfTestBase {
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the expected number of nodes is returned'
assert countDataNodesInTree(result) == expectedNumberOfDataNodes
- and: 'all data is read within #durationLimit ms and memory used is within limit'
+ and: 'all data is read within #durationLimit seconds and memory used is within limit'
recordAndAssertResourceUsage("Query with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
@@ -95,7 +95,7 @@ class QueryPerfTest extends CpsPerfTestBase {
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the expected number of nodes is returned'
assert countDataNodesInTree(result) == expectedNumberOfDataNodes
- and: 'all data is read within #durationLimit ms and memory used is within limit'
+ and: 'all data is read within #durationLimit seconds and memory used is within limit'
recordAndAssertResourceUsage("Query ancestors with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes
@@ -103,4 +103,22 @@ class QueryPerfTest extends CpsPerfTestBase {
'direct descendants' | DIRECT_CHILDREN_ONLY || 0.11 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR
'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.34 | 400 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
}
+
+ def 'Query data leaf with #scenario.'() {
+ when: 'query data leaf is called'
+ resourceMeter.start()
+ def result = objectUnderTest.queryDataLeaf(CPS_PERFORMANCE_TEST_DATASPACE, 'openroadm1', cpsPath, String)
+ resourceMeter.stop()
+ def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
+ then: 'the expected number of results is returned'
+ assert result.size() == expectedNumberOfValues
+ and: 'all data is read within #durationLimit seconds and memory used is within limit'
+ recordAndAssertResourceUsage("Query data leaf ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
+ where: 'the following parameters are used'
+ scenario | cpsPath || durationLimit | memoryLimit | expectedNumberOfValues
+ 'unique leaf value' | '/openroadm-devices/openroadm-device/@device-id' || 0.10 | 8 | OPENROADM_DEVICES_PER_ANCHOR
+ 'common leaf value' | '/openroadm-devices/openroadm-device/@ne-state' || 0.05 | 1 | 1
+ 'non-existing data leaf' | '/openroadm-devices/openroadm-device/@non-existing' || 0.05 | 1 | 0
+ }
+
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy
deleted file mode 100644
index cd2fc6ed7e..0000000000
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * ============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.ncmp
-
-import org.onap.cps.integration.ResourceMeter
-import org.onap.cps.integration.performance.base.NcmpPerfTestBase
-import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
-
-import java.util.stream.Collectors
-
-import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
-
-class CmHandleQueryByAlternateIdPerfTest extends NcmpPerfTestBase {
-
- AlternateIdMatcher objectUnderTest
- ResourceMeter resourceMeter = new ResourceMeter()
-
- def setup() { objectUnderTest = alternateIdMatcher }
-
- def 'Query cm handle by longest match alternate id'() {
- when: 'an alternate id as cps path query'
- resourceMeter.start()
- def cpsPath = "/a/b/c/d-5/e/f/g/h/i"
- def dataNodes = objectUnderTest.getYangModelCmHandleByLongestMatchingAlternateId(cpsPath, '/')
- and: 'the ids of the result are extracted and converted to xpath'
- def cpsXpaths = dataNodes.stream().map(dataNode -> "/dmi-registry/cm-handles[@id='${dataNode.leaves.id}']".toString() ).collect(Collectors.toSet())
- and: 'a single get is executed to get all the parent objects and their descendants'
- cpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsXpaths, OMIT_DESCENDANTS)
- resourceMeter.stop()
- def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
- print 'Total time in seconds to query ch handle by alternate id: ' + durationInSeconds
- then: 'the required operations are performed within required time and memory limit'
- recordAndAssertResourceUsage('Look up cm-handle by longest match alternate-id', 1, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
- }
-}
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..c71426032d
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/WriteDataJobPerfTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * ============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
+import spock.lang.Ignore
+
+/**
+ * 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)
+ }
+
+ @Ignore // CPS-2691
+ def 'Performance test for writeDataJob method'() {
+ given: 'register 10_000 cm handles (with alternative ids)'
+ registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, 'tagA', 10_000, 1, ModuleNameStrategy.UNIQUE, { it -> "/SubNetwork=Europe/SubNetwork=Ireland/MeContext=MyRadioNode${it}/ManagedElement=MyManagedElement${it}" })
+ def dataJobWriteRequest = populateDataJobWriteRequests(10_000)
+ when: 'sending a write job to NCMP with dynamically generated write operations'
+ resourceMeter.start()
+ dataJobService.writeDataJob('', '', new DataJobMetadata('d1', '', ''), 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, 10_000, 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