summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-dependencies/pom.xml4
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java6
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java10
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java30
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java12
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy243
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerSpec.groovy10
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java27
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java18
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java11
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java71
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java17
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java34
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmResourceAddress.java25
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy27
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy47
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy5
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy3
-rw-r--r--cps-parent/pom.xml2
-rw-r--r--docker-compose/docker-compose.yml2
-rw-r--r--docker-compose/postgres-init.sql1
-rw-r--r--docs/deployment.rst7
-rw-r--r--integration-test/pom.xml14
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy19
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy18
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy46
-rw-r--r--integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java86
27 files changed, 506 insertions, 289 deletions
diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml
index 69ea85917f..68f36fb839 100644
--- a/cps-dependencies/pom.xml
+++ b/cps-dependencies/pom.xml
@@ -2,7 +2,7 @@
<!--
============LICENSE_START=======================================================
Copyright (c) 2021 Linux Foundation.
- Modifications Copyright (C) 2020-2023 Nordix Foundation
+ Modifications Copyright (C) 2020-2024 Nordix Foundation
Modifications Copyright (C) 2022 Bell Canada.
================================================================================
Licensed under the Apache License, Version 2.0 (the "License");
@@ -130,7 +130,7 @@
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
- <version>4.2.0</version>
+ <version>4.2.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
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 66c159105a..93cbccf1a8 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
@@ -44,6 +44,7 @@ import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler;
@@ -107,8 +108,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
final Boolean includeDescendants,
final String authorization) {
final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = getNcmpDatastoreRequestHandler(datastoreName);
- return ncmpDatastoreRequestHandler.executeRequest(datastoreName, cmHandle, resourceIdentifier,
- optionsParamInQuery, topicParamInQuery, includeDescendants, authorization);
+ final CmResourceAddress cmResourceAddress = new CmResourceAddress(datastoreName, cmHandle, resourceIdentifier);
+ return ncmpDatastoreRequestHandler.executeRequest(cmResourceAddress, optionsParamInQuery, topicParamInQuery,
+ includeDescendants, authorization);
}
@Override
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java
index 430c0996f9..e6d6faf983 100644
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-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.
@@ -23,6 +23,7 @@ package org.onap.cps.ncmp.rest.controller.handlers;
import java.util.function.Supplier;
import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
import org.onap.cps.ncmp.api.NetworkCmProxyQueryService;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
import org.onap.cps.spi.FetchDescendantsOption;
import org.springframework.http.ResponseEntity;
@@ -68,9 +69,7 @@ public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandle
}
@Override
- protected Supplier<Object> getTaskSupplierForGetRequest(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ protected Supplier<Object> getTaskSupplierForGetRequest(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final String requestId,
@@ -79,8 +78,7 @@ public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandle
final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendants);
- return () -> networkCmProxyDataService.getResourceDataForCmHandle(datastoreName, cmHandleId, resourceIdentifier,
- fetchDescendantsOption);
+ return () -> networkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, fetchDescendantsOption);
}
private Supplier<Object> getTaskSupplierForQueryRequest(final String cmHandleId,
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java
index 65410d3a36..1ae16820a1 100644
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java
@@ -25,6 +25,7 @@ import java.util.UUID;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
import org.onap.cps.ncmp.rest.util.TopicValidator;
import org.springframework.beans.factory.annotation.Value;
@@ -50,18 +51,14 @@ public abstract class NcmpDatastoreRequestHandler {
/**
* Executes synchronous/asynchronous get request for given cm handle.
*
- * @param datastoreName the name of the datastore
- * @param cmHandleId the cm handle
- * @param resourceIdentifier the resource identifier
+ * @param cmResourceAddress the name of the datastore, cm handle and resource identifier
* @param optionsParamInQuery the options param in query
* @param topicParamInQuery the topic param in query
* @param includeDescendants whether include descendants
* @param authorization contents of Authorization header, or null if not present
* @return the response entity
*/
- public ResponseEntity<Object> executeRequest(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ public ResponseEntity<Object> executeRequest(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final boolean includeDescendants,
@@ -69,16 +66,16 @@ public abstract class NcmpDatastoreRequestHandler {
final boolean asyncResponseRequested = topicParamInQuery != null;
if (asyncResponseRequested && notificationFeatureEnabled) {
- return executeAsyncTaskAndGetResponseEntity(datastoreName, cmHandleId, resourceIdentifier,
- optionsParamInQuery, topicParamInQuery, includeDescendants, authorization);
+ return executeAsyncTaskAndGetResponseEntity(cmResourceAddress, optionsParamInQuery, topicParamInQuery,
+ includeDescendants, authorization);
}
if (asyncResponseRequested) {
log.warn("Asynchronous request is unavailable as notification feature is currently disabled, "
+ "will use synchronous operation.");
}
- final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(datastoreName, cmHandleId,
- resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID, includeDescendants, authorization);
+ final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(cmResourceAddress, optionsParamInQuery,
+ NO_TOPIC, NO_REQUEST_ID, includeDescendants, authorization);
return executeTaskSync(taskSupplier);
}
@@ -96,23 +93,18 @@ public abstract class NcmpDatastoreRequestHandler {
return ResponseEntity.ok(taskSupplier.get());
}
- private ResponseEntity<Object> executeAsyncTaskAndGetResponseEntity(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ private ResponseEntity<Object> executeAsyncTaskAndGetResponseEntity(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final boolean includeDescendants,
final String authorization) {
final String requestId = UUID.randomUUID().toString();
- final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(datastoreName, cmHandleId,
- resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants,
- authorization);
+ final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(cmResourceAddress,
+ optionsParamInQuery, topicParamInQuery, requestId, includeDescendants, authorization);
return executeTaskAsync(topicParamInQuery, requestId, taskSupplier);
}
- protected abstract Supplier<Object> getTaskSupplierForGetRequest(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ protected abstract Supplier<Object> getTaskSupplierForGetRequest(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final String requestId,
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
index 430b749eff..75112caf14 100644
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-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.
@@ -30,6 +30,7 @@ import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException;
import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
import org.onap.cps.ncmp.api.impl.operations.OperationType;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.api.models.DataOperationRequest;
import org.onap.cps.ncmp.rest.exceptions.OperationNotSupportedException;
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
@@ -77,18 +78,15 @@ public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestH
}
@Override
- protected Supplier<Object> getTaskSupplierForGetRequest(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ protected Supplier<Object> getTaskSupplierForGetRequest(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final String requestId,
final boolean includeDescendants,
final String authorization) {
- return () -> networkCmProxyDataService.getResourceDataForCmHandle(
- datastoreName, cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId,
- authorization);
+ return () -> networkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, optionsParamInQuery,
+ topicParamInQuery, requestId, authorization);
}
private ResponseEntity<Object> getRequestIdAndSendDataOperationRequestToDmiService(
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
index 616492d4e2..a5b1f05ee1 100644
--- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
@@ -39,6 +39,7 @@ import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.models.CmResourceAddress
import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
@@ -136,15 +137,15 @@ class NetworkCmProxyControllerSpec extends Specification {
def NO_TOPIC = null
def NO_REQUEST_ID = null
def NO_AUTH_HEADER = null
- def TIMOUT_FOR_TEST = 1234
+ def TIMEOUT_FOR_TEST = 1234
def logger = Spy(ListAppender<ILoggingEvent>)
def setup() {
ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
- ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
+ ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMEOUT_FOR_TEST
ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
- ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
+ ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMEOUT_FOR_TEST
setupLogger()
}
@@ -154,31 +155,28 @@ class NetworkCmProxyControllerSpec extends Specification {
def 'Get Resource Data from pass-through operational.'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)"
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&options=(a=1,b=2)"
+ and: 'the expected cm resource address'
+ def expectedCmResourceAddress = new CmResourceAddress(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child')
when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- ).andReturn().response
- then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
- 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
- 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
+ then: 'the NCMP data service is called with correct parameters'
+ 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress, '(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
and: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
}
def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
- "?resourceIdentifier=parent/child${additionalUrlParam}"
+ def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational?resourceIdentifier=parent/child${additionalUrlParam}"
+ and: 'the expected cm resource address'
+ def expectedCmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'h123', 'parent/child')
when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
- then: 'task executor is called appropriate number of times'
- 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle('ncmp-datastore:operational', 'h123', 'parent/child', expectedIncludeDescendants)
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
+ then: 'the NCMP data service is called with correct parameters'
+ 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress, expectedIncludeDescendants)
and: 'response status is OK'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
where: 'the following parameters are used'
scenario | additionalUrlParam || expectedIncludeDescendants
'no additional param' | '' || OMIT_DESCENDANTS
@@ -192,15 +190,11 @@ class NetworkCmProxyControllerSpec extends Specification {
def 'Execute (async) data operation to read data from dmi service.'() {
given: 'data operation url'
def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
- def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", datastore.datastoreName))
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest('read', datastore.datastoreName))
when: 'post data operation request is performed'
- def response = mvc.perform(
- post(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- .content(dataOperationRequestJsonData)
- ).andReturn().response
+ def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)).andReturn().response
then: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
and: 'async request id is generated'
assert response.contentAsString.contains('requestId')
then: 'the request is handled asynchronously'
@@ -212,80 +206,57 @@ class NetworkCmProxyControllerSpec extends Specification {
def 'Execute (async) data operation with some validation error.'() {
given: 'data operation url'
def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
- def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
- getDataOperationRequest('read', 'invalid datastore'))
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest('read', 'invalid datastore'))
when: 'post data resource request is performed'
- def response = mvc.perform(
- post(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- .content(dataOperationRequestJsonData)
- ).andReturn().response
+ def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)).andReturn().response
then: 'response status is BAD_REQUEST'
- response.status == HttpStatus.BAD_REQUEST.value()
+ assert response.status == HttpStatus.BAD_REQUEST.value()
}
def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
given: 'data operation url'
def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
- def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
- getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
when: 'post data resource request is performed'
- def response = mvc.perform(
- post(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- .content(dataOperationRequestJsonData)
+ def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)
).andReturn().response
then: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
and: 'async request id is unavailable'
assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
}
def 'Query Resource Data from operational.'() {
given: 'the query resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
- "?cps-path=/cps/path"
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query?cps-path=/cps/path"
when: 'the query data resource request is performed'
- def response = mvc.perform(
- get(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- ).andReturn().response
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
- 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
- '/cps/path',
- FetchDescendantsOption.OMIT_DESCENDANTS)
+ 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle','/cps/path',FetchDescendantsOption.OMIT_DESCENDANTS)
and: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
}
def 'Query Resource Data with unsupported datastore'() {
given: 'the query resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query" +
- "?cps-path=/cps/path"
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query?cps-path=/cps/path"
when: 'the query data resource request is performed'
- def response = mvc.perform(
- get(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- ).andReturn().response
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
- response.status == 400
+ assert response.status == 400
and: 'the error message is that the datastore is not supported'
- response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
+ assert response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
}
def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
- "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=$resourceIdentifier&options=(a=1,b=2)"
and: 'ncmp service returns json object'
- mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
- resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >> '{valid-json}'
+ def expectedCmResourceAddress = new CmResourceAddress(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', resourceIdentifier)
+ mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >> '{valid-json}'
when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- ).andReturn().response
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
then: 'response status is Ok'
response.status == HttpStatus.OK.value()
and: 'response contains valid object body'
@@ -302,34 +273,24 @@ class NetworkCmProxyControllerSpec extends Specification {
def 'Update resource data from pass-through running.'() {
given: 'update resource data url'
- def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
- "?resourceIdentifier=parent/child"
+ def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
when: 'update data resource request is performed'
- def response = mvc.perform(
- put(updateUrl)
- .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
- ).andReturn().response
+ def response = mvc.perform(put(updateUrl).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
then: 'ncmp service method to update resource is called'
- 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
- 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+ 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle','parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
and: 'the response status is OK'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
}
def 'Create Resource Data from pass-through running with #scenario.'() {
given: 'resource data url'
- def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
- "?resourceIdentifier=parent/child"
+ def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
when: 'create resource request is performed'
- def response = mvc.perform(
- post(url)
- .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
- ).andReturn().response
+ def response = mvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
then: 'ncmp service method to create resource called'
- 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
- 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+ 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
and: 'resource is created'
- response.status == HttpStatus.CREATED.value()
+ assert response.status == HttpStatus.CREATED.value()
}
def 'Get module references for the given dataspace and cm handle.'() {
@@ -338,12 +299,11 @@ class NetworkCmProxyControllerSpec extends Specification {
when: 'get module resource request is performed'
def response = mvc.perform(get(getUrl)).andReturn().response
then: 'ncmp service method to get yang resource module references is called'
- mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
- >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
+ mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle') >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
and: 'response contains an array with the module name and revision'
response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
and: 'response returns an OK http code'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
}
def 'Retrieve cm handles.'() {
@@ -364,13 +324,11 @@ class NetworkCmProxyControllerSpec extends Specification {
and: 'map for trust level per cmHandle has value for only one cm handle'
trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
when: 'the searches api is invoked'
- def response = mvc.perform(post(searchesEndpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(jsonString)).andReturn().response
+ def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString)).andReturn().response
then: 'response status returns OK'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
and: 'the expected response content is returned'
- response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"NONE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":null,"moduleSetTag":"someModuleSetTag","alternateId":"someAlternateId","dataProducerIdentifier":"someDataProducerIdentifier"}]'
+ assert response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"NONE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":null,"moduleSetTag":"someModuleSetTag","alternateId":"someAlternateId","dataProducerIdentifier":"someDataProducerIdentifier"}]'
}
def 'Get complete Cm Handle details by Cm Handle id.'() {
@@ -396,7 +354,7 @@ class NetworkCmProxyControllerSpec extends Specification {
and: 'the response contains the cm handle state'
assertContainsState(response)
and: 'the content does not contain dmi properties'
- !response.contentAsString.contains("some DMI property")
+ assert !response.contentAsString.contains("some DMI property")
}
def 'Get Cm Handle public properties by Cm Handle id.'() {
@@ -405,13 +363,11 @@ class NetworkCmProxyControllerSpec extends Specification {
and: 'some cm handle public properties'
def publicProperties = ['public prop': 'some public property']
and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
- 1 * mockNetworkCmProxyDataService
- .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
+ 1 * mockNetworkCmProxyDataService.getCmHandlePublicProperties('some-cm-handle') >> publicProperties
when: 'the cm handle properties api is invoked'
- def response = mvc.perform(
- get(cmHandlePropertiesEndpoint)).andReturn().response
+ def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
then: 'the correct response is returned'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
and: 'the response contains the public properties'
assertContainsPublicProperties(response)
}
@@ -422,15 +378,13 @@ class NetworkCmProxyControllerSpec extends Specification {
and: 'some cm handle composite state'
def compositeState = compositeStateTestObject()
and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
- 1 * mockNetworkCmProxyDataService
- .getCmHandleCompositeState('some-cm-handle') >> compositeState
+ 1 * mockNetworkCmProxyDataService.getCmHandleCompositeState('some-cm-handle') >> compositeState
when: 'the cm handle state api is invoked'
- def response = mvc.perform(
- get(cmHandlePropertiesEndpoint)).andReturn().response
+ def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
then: 'the correct response is returned'
response.status == HttpStatus.OK.value()
and: 'the response contains the cm handle state'
- assertContainsState(response)
+ assert assertContainsState(response)
}
def 'Call execute cm handle searches with unrecognized condition name.'() {
@@ -449,12 +403,9 @@ class NetworkCmProxyControllerSpec extends Specification {
trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
trustLevelPerCmHandle.put('ch-2', TrustLevel.NONE)
when: 'the searches api is invoked'
- def response = mvc.perform(
- post(searchesEndpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(jsonString)).andReturn().response
+ def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString)).andReturn().response
then: 'an empty cm handle identifier is returned'
- response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"COMPLETE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":"NONE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null}]'
+ assert response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"COMPLETE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":"NONE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null}]'
}
def 'Query for cm handles matching query parameters'() {
@@ -463,68 +414,47 @@ class NetworkCmProxyControllerSpec extends Specification {
and: 'the service method is invoked with module names and returns cm handle ids'
1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2']
when: 'the searches api is invoked'
- def response = mvc.perform(
- post(searchesEndpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content('{}')).andReturn().response
+ def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content('{}')).andReturn().response
then: 'cm handle ids are returned'
- response.contentAsString == '["ch-1","ch-2"]'
+ assert response.contentAsString == '["ch-1","ch-2"]'
}
def 'Query for cm handles with invalid request payload'() {
when: 'the searches api is invoked'
def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
def invalidInputData = '{invalidJson}'
- def response = mvc.perform(
- post(searchesEndpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(invalidInputData)).andReturn().response
+ def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(invalidInputData)).andReturn().response
then: 'BAD_REQUEST is returned'
- response.getStatus() == 400
+ assert response.getStatus() == 400
}
def 'Patch resource data in pass-through running datastore.'() {
given: 'patch resource data url'
- def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
- "?resourceIdentifier=parent/child"
+ def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
when: 'patch data resource request is performed'
- def response = mvc.perform(
- patch(url)
- .contentType(MediaType.APPLICATION_JSON)
- .accept(MediaType.APPLICATION_JSON).content(requestBody)
- ).andReturn().response
+ def response = mvc.perform(patch(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(requestBody)).andReturn().response
then: 'ncmp service method to update resource is called'
- 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
- 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+ 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
and: 'the response status is OK'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
}
def 'Delete resource data in pass-through running datastore.'() {
given: 'delete resource data url'
- def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
- "?resourceIdentifier=parent/child"
+ def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
when: 'delete data resource request is performed'
- def response = mvc.perform(
- delete(url)
- .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
+ def response = mvc.perform(delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
then: 'the ncmp service method to delete resource is called (with null as body)'
- 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
- 'parent/child', DELETE, null, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
+ 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
and: 'the response is No Content'
- response.status == HttpStatus.NO_CONTENT.value()
+ assert response.status == HttpStatus.NO_CONTENT.value()
}
def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- .accept(MediaType.APPLICATION_JSON_VALUE)
- ).andReturn().response
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON_VALUE)).andReturn().response
then: 'async request id is generated'
assert response.contentAsString.contains("requestId")
where: 'the following parameters are used'
@@ -535,17 +465,14 @@ class NetworkCmProxyControllerSpec extends Specification {
def 'Getting module definitions for a module'() {
when: 'get module definition request is performed with module name'
- def response = mvc.perform(
- get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName"))
- .andReturn().response
+ def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName")).andReturn().response
then: 'ncmp service method is invoked with correct parameters'
mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
- >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
- 'module sampleModuleName{ sample module content }')]
+ >> [new ModuleDefinition('sampleModuleName', '2021-10-03','module sampleModuleName{ sample module content }')]
and: 'response contains an array with the module name, revision and content'
response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
and: 'response returns an OK http code'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
}
def 'Getting module definitions filtering on #scenario'() {
@@ -590,17 +517,15 @@ class NetworkCmProxyControllerSpec extends Specification {
def 'Get Resource Data from operational with or without descendants'() {
given: 'resource data url with descendants #enabled'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
- "?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
+ and: 'the expected target'
+ def expectedCmResourceAddress = new CmResourceAddress(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child')
when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl)
- .contentType(MediaType.APPLICATION_JSON)
- ).andReturn().response
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
- 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
+ 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(expectedCmResourceAddress, descendantsOption)
and: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ assert response.status == HttpStatus.OK.value()
where: 'the following parameters are used'
booleanValue | descendantsOption
false | OMIT_DESCENDANTS
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerSpec.groovy
index ddeac519c3..bdd0e716d8 100644
--- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerSpec.groovy
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerSpec.groovy
@@ -25,6 +25,7 @@ import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException
import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException
import org.onap.cps.ncmp.api.models.DataOperationDefinition
import org.onap.cps.ncmp.api.models.DataOperationRequest
+import org.onap.cps.ncmp.api.models.CmResourceAddress
import org.onap.cps.ncmp.rest.exceptions.OperationNotSupportedException
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
import spock.lang.Specification
@@ -48,12 +49,13 @@ class NcmpDatastoreRequestHandlerSpec extends Specification {
objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled
and: 'a flag to track the network service call'
def networkServiceMethodCalled = false
+ and: 'a CM resource address'
+ def cmResourceAddress = new CmResourceAddress('ds', 'ch1', 'resource1')
and: 'the (mocked) service will use the flag to indicate if it is called'
- mockNetworkCmProxyDataService.getResourceDataForCmHandle('ds', 'ch1', 'resource1', 'options', _, _, NO_AUTH_HEADER) >> {
- networkServiceMethodCalled = true
- }
+ mockNetworkCmProxyDataService.getResourceDataForCmHandle(cmResourceAddress, 'options', _, _, NO_AUTH_HEADER) >>
+ { networkServiceMethodCalled = true }
when: 'get request is executed with topic = #topic'
- objectUnderTest.executeRequest('ds', 'ch1', 'resource1', 'options', topic, false, NO_AUTH_HEADER)
+ objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER)
then: 'the task is executed in an async fashion or not'
expectedCalls * spiedCpsNcmpTaskExecutor.executeTask(*_)
and: 'the service request is invoked'
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
index 4230140d26..20545d711d 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
@@ -29,6 +29,7 @@ import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
import org.onap.cps.ncmp.api.impl.operations.OperationType;
import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.api.models.DataOperationRequest;
import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
@@ -53,18 +54,14 @@ public interface NetworkCmProxyDataService {
/**
* Get resource data for given data store using dmi.
*
- * @param datastoreName datastore name
- * @param cmHandleId cm handle identifier
- * @param resourceIdentifier resource identifier
+ * @param cmResourceAddress target datastore, cm handle and resource identifier
* @param optionsParamInQuery options query
* @param topicParamInQuery topic name for (triggering) async responses
* @param requestId unique requestId for async request
* @param authorization contents of Authorization header, or null if not present
* @return {@code Object} resource data
*/
- Object getResourceDataForCmHandle(String datastoreName,
- String cmHandleId,
- String resourceIdentifier,
+ Object getResourceDataForCmHandle(CmResourceAddress cmResourceAddress,
String optionsParamInQuery,
String topicParamInQuery,
String requestId,
@@ -73,15 +70,11 @@ public interface NetworkCmProxyDataService {
/**
* Get resource data for operational.
*
- * @param datastoreName datastore name
- * @param cmHandleId cm handle identifier
- * @param resourceIdentifier resource identifier
+ * @param cmResourceAddress target datastore, cm handle and resource identifier
* @Link FetchDescendantsOption fetch descendants option
* @return {@code Object} resource data
*/
- Object getResourceDataForCmHandle(String datastoreName,
- String cmHandleId,
- String resourceIdentifier,
+ Object getResourceDataForCmHandle(CmResourceAddress cmResourceAddress,
FetchDescendantsOption fetchDescendantsOption);
/**
@@ -110,11 +103,11 @@ public interface NetworkCmProxyDataService {
* @return {@code Object} return data
*/
Object writeResourceDataPassThroughRunningForCmHandle(String cmHandleId,
- String resourceIdentifier,
- OperationType operationType,
- String requestBody,
- String contentType,
- String authorization);
+ String resourceIdentifier,
+ OperationType operationType,
+ String requestBody,
+ String contentType,
+ String authorization);
/**
* Retrieve module references for the given cm handle.
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
index 6ab6eab653..c15df9c869 100755
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
@@ -72,6 +72,7 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.api.models.DataOperationRequest;
import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
@@ -127,15 +128,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
@Override
- public Object getResourceDataForCmHandle(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final String requestId,
final String authorization) {
- final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(datastoreName, cmHandleId,
- resourceIdentifier,
+ final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmResourceAddress,
optionsParamInQuery,
topicParamInQuery,
requestId,
@@ -144,12 +142,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
}
@Override
- public Object getResourceDataForCmHandle(final String datastoreName,
- final String cmHandleId,
- final String resourceIdentifier,
+ public Object getResourceDataForCmHandle(final CmResourceAddress cmResourceAddress,
final FetchDescendantsOption fetchDescendantsOption) {
- return cpsDataService.getDataNodes(datastoreName, cmHandleId, resourceIdentifier,
- fetchDescendantsOption).iterator().next();
+ return cpsDataService.getDataNodes(cmResourceAddress.datastoreName(),
+ cmResourceAddress.cmHandleId(),
+ cmResourceAddress.resourceIdentifier(),
+ fetchDescendantsOption).iterator().next();
}
@Override
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java
index 38f3db98de..6b02adb654 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java
@@ -57,4 +57,15 @@ public interface CmNotificationSubscriptionPersistenceService {
*/
Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
final String cmHandleId, final String xpath);
+
+ /**
+ * Add or update cm notification subscription.
+ *
+ * @param datastoreType valid datastore type
+ * @param cmHandle cmhandle id
+ * @param xpath valid xpath
+ * @param newSubscriptionId subscription Id to be added
+ */
+ void addOrUpdateCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandle,
+ final String xpath, final String newSubscriptionId);
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java
index 6e4997a4dd..5eca5e8c57 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java
@@ -20,15 +20,26 @@
package org.onap.cps.ncmp.api.impl.events.cmsubscription.service;
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
+
+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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.CpsDataService;
import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
-import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.utils.ContentType;
+import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.stereotype.Service;
@Slf4j
@@ -37,14 +48,16 @@ import org.springframework.stereotype.Service;
public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService {
private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
- private static final String IS_ONGOING_CM_SUBSCRIPTION_CPS_PATH_QUERY = """
+ private static final String CM_SUBSCRIPTION_CPS_PATH_QUERY = """
/datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']/filters/filter[@xpath='%s']
""".trim();
private static final String SUBSCRIPTION_IDS_CPS_PATH_QUERY = """
//filter/subscriptionIds[text()='%s']
""".trim();
+ private final JsonObjectMapper jsonObjectMapper;
private final CpsQueryService cpsQueryService;
+ private final CpsDataService cpsDataService;
@Override
public boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
@@ -56,7 +69,7 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
public boolean isUniqueSubscriptionId(final String subscriptionId) {
return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
SUBSCRIPTION_IDS_CPS_PATH_QUERY.formatted(subscriptionId),
- FetchDescendantsOption.OMIT_DESCENDANTS).isEmpty();
+ OMIT_DESCENDANTS).isEmpty();
}
@Override
@@ -64,17 +77,65 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
final String cmHandleId, final String xpath) {
final String isOngoingCmSubscriptionCpsPathQuery =
- IS_ONGOING_CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreType.getDatastoreName(), cmHandleId,
+ CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreType.getDatastoreName(), cmHandleId,
escapeQuotesByDoublingThem(xpath));
final Collection<DataNode> existingNodes =
cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME,
- isOngoingCmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS);
+ isOngoingCmSubscriptionCpsPathQuery, OMIT_DESCENDANTS);
if (existingNodes.isEmpty()) {
return Collections.emptyList();
}
return (List<String>) existingNodes.iterator().next().getLeaves().get("subscriptionIds");
}
+ @Override
+ public void addOrUpdateCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
+ final String xpath, final String newSubscriptionId) {
+ if (isOngoingCmNotificationSubscription(datastoreType, cmHandleId, xpath)) {
+ final DataNode existingFilterNode =
+ cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+ CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreType.getDatastoreName(), cmHandleId,
+ escapeQuotesByDoublingThem(xpath)),
+ OMIT_DESCENDANTS).iterator().next();
+ final Collection<String> existingSubscriptionIds = getOngoingCmNotificationSubscriptionIds(datastoreType,
+ cmHandleId, xpath);
+ if (!existingSubscriptionIds.contains(newSubscriptionId)) {
+ updateListOfSubscribers(existingSubscriptionIds, newSubscriptionId, existingFilterNode);
+ }
+ } else {
+ addNewSubscriptionViaDatastore(datastoreType, cmHandleId, xpath, newSubscriptionId);
+ }
+ }
+
+ private void addNewSubscriptionViaDatastore(final DatastoreType datastoreType, final String cmHandleId,
+ final String xpath, final String newSubscriptionId) {
+ final String parentXpathFormat = "/datastores/datastore[@name='%s']/cm-handles";
+ String parentXpath = "";
+ if (datastoreType == PASSTHROUGH_RUNNING) {
+ parentXpath = parentXpathFormat.formatted("ncmp-datastore:passthrough-running");
+ } else {
+ parentXpath = parentXpathFormat.formatted("ncmp-datastore:passthrough-operational");
+ }
+
+ final String updatedJson = String.format("{\"cm-handle\":[{\"id\":\"%s\",\"filters\":{\"filter\":"
+ + "[{\"xpath\":\"%s\",\"subscriptionIds\":[\"%s\"]}]}}]}", cmHandleId, xpath, newSubscriptionId);
+ cpsDataService.saveData(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, updatedJson,
+ OffsetDateTime.now(), ContentType.JSON);
+ }
+
+ private void updateListOfSubscribers(final Collection<String> existingSubscriptionIds,
+ final String newSubscriptionId, final DataNode existingFilterNode) {
+ final String parentXpath = CpsPathUtil.getNormalizedParentXpath(existingFilterNode.getXpath());
+ final List<String> updatedSubscribers = new ArrayList<>(existingSubscriptionIds);
+ updatedSubscribers.add(newSubscriptionId);
+ final Map<String, Serializable> updatedLeaves = new HashMap<>();
+ updatedLeaves.put("xpath", existingFilterNode.getLeaves().get("xpath"));
+ updatedLeaves.put("subscriptionIds", (Serializable) updatedSubscribers);
+ final String updatedJson = "{\"filter\":[" + jsonObjectMapper.asJsonString(updatedLeaves) + "]}";
+ cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME, parentXpath, updatedJson,
+ OffsetDateTime.now());
+ }
+
private static String escapeQuotesByDoublingThem(final String inputXpath) {
return inputXpath.replace("'", "''");
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
index 2a4bceca23..a9ec1241bc 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
@@ -42,6 +42,7 @@ import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
import org.onap.cps.ncmp.api.impl.utils.data.operation.ResourceDataOperationRequestUtils;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.models.CmResourceAddress;
import org.onap.cps.ncmp.api.models.DataOperationRequest;
import org.onap.cps.spi.exceptions.CpsException;
import org.onap.cps.utils.JsonObjectMapper;
@@ -70,9 +71,7 @@ public class DmiDataOperations extends DmiOperations {
* This method fetches the resource data from operational data store for given cm handle
* identifier on given resource using dmi client.
*
- * @param dataStoreName name of data store
- * @param cmHandleId network resource identifier
- * @param resourceId resource identifier
+ * @param cmResourceAddress target datastore, cm handle and resource identifier
* @param optionsParamInQuery options query
* @param topicParamInQuery topic name for (triggering) async responses
* @param requestId requestId for async responses
@@ -82,19 +81,17 @@ public class DmiDataOperations extends DmiOperations {
@Timed(value = "cps.ncmp.dmi.get",
description = "Time taken to fetch the resource data from operational data store for given cm handle "
+ "identifier on given resource using dmi client")
- public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
- final String cmHandleId,
- final String resourceId,
+ public ResponseEntity<Object> getResourceDataFromDmi(final CmResourceAddress cmResourceAddress,
final String optionsParamInQuery,
final String topicParamInQuery,
final String requestId,
final String authorization) {
- final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
+ final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmResourceAddress.cmHandleId());
final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
- final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null,
- yangModelCmHandle);
- final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, resourceId, optionsParamInQuery,
+ final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
+ final String dmiResourceDataUrl = getDmiRequestUrl(cmResourceAddress.datastoreName(),
+ cmResourceAddress.cmHandleId(), cmResourceAddress.resourceIdentifier(), optionsParamInQuery,
topicParamInQuery, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, authorization);
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java
index f14439f690..60f39fcea0 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java
@@ -115,23 +115,7 @@ public class AlternateIdChecker {
for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) {
final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
final String proposedAlternateId = ncmpServiceCmHandle.getAlternateId();
- final boolean isAcceptable;
- if (StringUtils.isEmpty(proposedAlternateId)) {
- isAcceptable = true;
- } else {
- if (acceptedAlternateIds.contains(proposedAlternateId)) {
- isAcceptable = false;
- log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
- + "assigned to a different cm handle (in this batch)", cmHandleId);
- } else {
- if (Operation.CREATE.equals(operation)) {
- isAcceptable = canApplyAlternateId(cmHandleId, NO_CURRENT_ALTERNATE_ID, proposedAlternateId);
- } else {
- isAcceptable = canApplyAlternateId(cmHandleId, proposedAlternateId);
- }
- }
- }
- if (isAcceptable) {
+ if (isProposedAlternateIdAcceptable(proposedAlternateId, operation, acceptedAlternateIds, cmHandleId)) {
acceptedAlternateIds.add(proposedAlternateId);
} else {
rejectedCmHandleIds.add(cmHandleId);
@@ -140,6 +124,22 @@ public class AlternateIdChecker {
return rejectedCmHandleIds;
}
+ private boolean isProposedAlternateIdAcceptable(final String proposedAlternateId, final Operation operation,
+ final Set<String> acceptedAlternateIds, final String cmHandleId) {
+ if (StringUtils.isEmpty(proposedAlternateId)) {
+ return true;
+ }
+ if (acceptedAlternateIds.contains(proposedAlternateId)) {
+ log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
+ + "assigned to a different cm handle (in this batch)", cmHandleId);
+ return false;
+ }
+ if (Operation.CREATE.equals(operation)) {
+ return canApplyAlternateId(cmHandleId, NO_CURRENT_ALTERNATE_ID, proposedAlternateId);
+ }
+ return canApplyAlternateId(cmHandleId, proposedAlternateId);
+ }
+
private boolean alternateIdAlreadyInDb(final String alternateId) {
try {
inventoryPersistence.getCmHandleDataNodeByAlternateId(alternateId);
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmResourceAddress.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmResourceAddress.java
new file mode 100644
index 0000000000..21d82fcf56
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmResourceAddress.java
@@ -0,0 +1,25 @@
+/*
+ * ============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.ncmp.api.models;
+
+public record CmResourceAddress(String datastoreName, String cmHandleId, String resourceIdentifier) {
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
index 74016e4c0c..d47be6cd5c 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
@@ -24,6 +24,7 @@
package org.onap.cps.ncmp.api.impl
import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse
+import org.onap.cps.ncmp.api.models.CmResourceAddress
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
@@ -121,35 +122,27 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
>> { new ResponseEntity<>(HttpStatus.CREATED) }
}
- def 'Get resource data for pass-through operational from DMI.'() {
+ def 'Get resource data for from DMI.'() {
given: 'cpsDataService returns valid data node'
mockDataNode()
+ and: 'some cm resource address'
+ def cmResourceAddress = new CmResourceAddress('some datastore','some CM Handle', 'some resource Id')
and: 'get resource data from DMI is called'
- mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >>
+ mockDmiDataOperations.getResourceDataFromDmi(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >>
new ResponseEntity<>('dmi-response', HttpStatus.OK)
- when: 'get resource data operational for cm-handle is called'
- def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
+ when: 'get resource data operational for the given cm resource address is called'
+ def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
then: 'DMI returns a json response'
assert response == 'dmi-response'
}
- def 'Get resource data for pass-through running from DMI.'() {
- given: 'cpsDataService returns valid data node'
- mockDataNode()
- and: 'DMI returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >>
- new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called'
- def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
- then: 'get resource data returns expected response'
- assert response == '{dmi-response}'
- }
-
def 'Get resource data for operational (cached) data.'() {
given: 'CPS Data service returns some object(s)'
mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
+ and: 'a cm resource address for the same datastore, cm handle and resource id'
+ def cmResourceAddress = new CmResourceAddress(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId')
when: 'get resource data is called'
- def response = objectUnderTest.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS)
+ def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, FetchDescendantsOption.OMIT_DESCENDANTS)
then: 'get resource data returns teh first object from the data service'
assert response == 'First Object'
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy
index eb0e1100ed..19ebc3d711 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy
@@ -20,18 +20,22 @@
package org.onap.cps.ncmp.api.impl.events.cmsubscription.service
-
+import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsQueryService
import org.onap.cps.ncmp.api.impl.operations.DatastoreType
import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.model.DataNode
+import org.onap.cps.utils.JsonObjectMapper
+import com.fasterxml.jackson.databind.ObjectMapper
import spock.lang.Specification
class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification {
+ def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockCpsQueryService = Mock(CpsQueryService)
+ def mockCpsDataService = Mock(CpsDataService)
- def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(mockCpsQueryService)
+ def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
def 'Check ongoing cm subscription #scenario'() {
given: 'a valid cm subscription query'
@@ -64,4 +68,43 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification
'datanodes present' | [new DataNode()] || false
'no datanodes present' | [] || true
}
+
+ def 'Add new subscriber to an ongoing cm notification subscription'() {
+ given: 'a valid cm subscription path query'
+ def cpsPathQuery = objectUnderTest.CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y');
+ and: 'a dataNode exists for the given cps path query'
+ mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
+ cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
+ when: 'the method to add/update cm notification subscription is called'
+ objectUnderTest.addOrUpdateCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId')
+ then: 'data service method to update list of subscribers is called once'
+ 1 * mockCpsDataService.updateNodeLeaves(
+ 'NCMP-Admin',
+ 'cm-data-subscriptions',
+ '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
+ '{"filter":[{"xpath":"/x/y","subscriptionIds":["sub-1","newSubId"]}]}', _)
+ }
+
+ def 'Add new cm notification subscription for #datastoreType'() {
+ given: 'a valid cm subscription path query'
+ def cpsPathQuery = objectUnderTest.CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreName, 'ch-1', '/x/y')
+ and: 'a parent node xpath for given path above'
+ def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles'
+ and: 'a datanode does not exist for the given cps path query'
+ mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
+ cpsPathQuery.formatted(datastoreName),
+ FetchDescendantsOption.OMIT_DESCENDANTS) >> []
+ when: 'the method to add/update cm notification subscription is called'
+ objectUnderTest.addOrUpdateCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
+ then: 'data service method to update list of subscribers is called once with the correct parameters'
+ 1 * mockCpsDataService.saveData(
+ 'NCMP-Admin',
+ 'cm-data-subscriptions',
+ parentNodeXpath.formatted(datastoreName),
+ '{"cm-handle":[{"id":"ch-1","filters":{"filter":[{"xpath":"/x/y","subscriptionIds":["newSubId"]}]}}]}', _,_)
+ where:
+ scenario | datastoreType || datastoreName
+ 'passthrough_running' | DatastoreType.PASSTHROUGH_RUNNING || "ncmp-datastore:passthrough-running"
+ 'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL || "ncmp-datastore:passthrough-operational"
+ }
} \ No newline at end of file
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
index e154588a10..eb6c7a0f48 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
@@ -28,6 +28,7 @@ import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext
import org.onap.cps.ncmp.api.models.DataOperationRequest
+import org.onap.cps.ncmp.api.models.CmResourceAddress
import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent
import org.onap.cps.ncmp.utils.TestUtils
import org.onap.cps.utils.JsonObjectMapper
@@ -81,8 +82,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi
dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
when: 'get resource data is invoked'
- def result = objectUnderTest.getResourceDataFromDmi(dataStore.datastoreName, cmHandleId, resourceIdentifier,
- options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
+ def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, cmHandleId, resourceIdentifier)
+ def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
then: 'the result is the response from the DMI service'
assert result == responseFromDmi
where: 'the following parameters are used'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy
index 0eabaa1d28..1e843676be 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy
@@ -80,7 +80,8 @@ class AlternateIdCheckerSpec extends Specification {
assert result == expectedRejectedCmHandleIds
where: 'the following alternate ids are used'
scenario | alt1 | alt2 | altAlreadyInDb || expectedRejectedCmHandleIds
- 'no alternate ids' | '' | '' | ['dont matter'] || []
+ 'blank alternate ids' | '' | '' | ['dont matter'] || []
+ 'null alternate ids' | null | null | ['dont matter'] || []
'new alternate ids' | 'fdn1' | 'fdn2' | ['other fdn'] || []
'one already used alternate id' | 'fdn1' | 'fdn2' | ['fdn1'] || ['ch-1']
'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter'] || ['ch-2']
diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml
index 699bf3c9ac..b6e12c0082 100644
--- a/cps-parent/pom.xml
+++ b/cps-parent/pom.xml
@@ -159,7 +159,7 @@
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
- <version>4.2.0</version>
+ <version>4.2.3</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml
index de427af13f..a604b06527 100644
--- a/docker-compose/docker-compose.yml
+++ b/docker-compose/docker-compose.yml
@@ -31,6 +31,8 @@ services:
POSTGRES_DB: cpsdb
POSTGRES_USER: ${DB_USERNAME:-cps}
POSTGRES_PASSWORD: ${DB_PASSWORD:-cps}
+ volumes:
+ - ./postgres-init.sql:/docker-entrypoint-initdb.d/postgres-init.sql
deploy:
resources:
reservations:
diff --git a/docker-compose/postgres-init.sql b/docker-compose/postgres-init.sql
new file mode 100644
index 0000000000..0c96de5b55
--- /dev/null
+++ b/docker-compose/postgres-init.sql
@@ -0,0 +1 @@
+ALTER SYSTEM SET shared_buffers = '512MB';
diff --git a/docs/deployment.rst b/docs/deployment.rst
index de276ce28f..ba8fcd9347 100644
--- a/docs/deployment.rst
+++ b/docs/deployment.rst
@@ -12,6 +12,13 @@ CPS Deployment
.. contents::
:depth: 2
+Database configuration
+======================
+CPS uses PostgreSQL database. As per the `PostgreSQL documentation on resource consumption
+<https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS>`_, the *shared_buffers*
+parameter should be set between 25% and 40% of total memory. It has a default value of 128 megabytes, so this should be
+set appropriately. For example, given a database with 2GB of memory, 512MB is a recommended value.
+
CPS OOM Charts
==============
The CPS kubernetes chart is located in the `OOM repository <https://github.com/onap/oom/tree/master/kubernetes/cps>`_.
diff --git a/integration-test/pom.xml b/integration-test/pom.xml
index b379e9ff19..ca2b26d1c9 100644
--- a/integration-test/pom.xml
+++ b/integration-test/pom.xml
@@ -68,6 +68,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
@@ -78,6 +83,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.springframework.kafka</groupId>
+ <artifactId>spring-kafka-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
@@ -88,8 +98,8 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-web</artifactId>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>kafka</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
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 33945a6c21..2603c48edf 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
@@ -21,12 +21,14 @@
package org.onap.cps.integration.base
import java.time.OffsetDateTime
+import java.time.format.DateTimeFormatter
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsDataspaceService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.api.CpsQueryService
import org.onap.cps.integration.DatabaseTestContainer
+import org.onap.cps.integration.KafkaTestContainer
import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
import org.onap.cps.ncmp.api.NetworkCmProxyDataService
import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
@@ -38,6 +40,7 @@ import org.onap.cps.spi.exceptions.DataspaceNotFoundException
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.repository.DataspaceRepository
import org.onap.cps.spi.utils.SessionManager
+import org.onap.cps.utils.JsonObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.domain.EntityScan
@@ -55,13 +58,11 @@ import spock.lang.Shared
import spock.lang.Specification
import spock.util.concurrent.PollingConditions
-import java.time.format.DateTimeFormatter
-
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR;
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = [CpsDataspaceService])
@Testcontainers
@@ -75,8 +76,11 @@ abstract class CpsIntegrationSpecBase extends Specification {
@Shared
DatabaseTestContainer databaseTestContainer = DatabaseTestContainer.getInstance()
+ @Shared
+ KafkaTestContainer kafkaTestContainer = KafkaTestContainer.getInstance()
+
@Autowired
- MockMvc mvc;
+ MockMvc mvc
@Autowired
CpsDataspaceService cpsDataspaceService
@@ -111,6 +115,9 @@ abstract class CpsIntegrationSpecBase extends Specification {
@Autowired
ModuleSyncWatchdog moduleSyncWatchdog
+ @Autowired
+ JsonObjectMapper jsonObjectMapper
+
MockRestServiceServer mockDmiServer = null
static final DMI_URL = 'http://mock-dmi-server'
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy
index 6b6f62edf9..f03872d56b 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy
@@ -20,7 +20,11 @@
package org.onap.cps.integration.functional
+import java.time.Duration
import java.time.OffsetDateTime
+import org.apache.kafka.common.TopicPartition
+import org.apache.kafka.common.serialization.StringDeserializer
+import org.onap.cps.integration.KafkaTestContainer
import org.onap.cps.integration.base.CpsIntegrationSpecBase
import org.onap.cps.ncmp.api.NetworkCmProxyDataService
import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
@@ -28,12 +32,15 @@ import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
import org.onap.cps.ncmp.api.models.DmiPluginRegistration
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.events.lcm.v1.LcmEvent
import spock.util.concurrent.PollingConditions
class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
NetworkCmProxyDataService objectUnderTest
+ def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class)
+
static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json')
static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json')
static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json')
@@ -47,6 +54,9 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
given: 'DMI will return modules when requested'
mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A)
+ and: 'consumer subscribed to topic'
+ kafkaConsumer.subscribe(['ncmp-events'])
+
when: 'a CM-handle is registered for creation'
def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
@@ -66,6 +76,14 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
})
+ and: 'the messages is polled'
+ def message = kafkaConsumer.poll(Duration.ofMillis(10000))
+ def records = message.records(new TopicPartition('ncmp-events', 0))
+
+ and: 'the newest lcm event notification is received with READY state'
+ def notificationMessage = jsonObjectMapper.convertJsonString(records.last().value().toString(), LcmEvent)
+ assert notificationMessage.event.newValues.cmHandleState.value() == 'READY'
+
and: 'the CM-handle has expected modules'
assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy
new file mode 100644
index 0000000000..df74a05b50
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy
@@ -0,0 +1,46 @@
+package org.onap.cps.integration.functional
+
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
+import org.onap.cps.integration.base.CpsIntegrationSpecBase;
+import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase {
+
+ @Autowired
+ CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService;
+
+ def 'Adding a new cm notification subscription'() {
+ given: 'there is no ongoing cm subscription for the following'
+ def datastoreType = PASSTHROUGH_RUNNING
+ def cmHandleId = 'ch-1'
+ def xpath = '/x/y'
+ assert cmNotificationSubscriptionPersistenceService.
+ getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 0
+ when: 'we add a new cm notification subscription'
+ cmNotificationSubscriptionPersistenceService.addOrUpdateCmNotificationSubscription(datastoreType,cmHandleId,xpath,
+ 'subId-1')
+ then: 'there is an ongoing cm subscription for that CM handle and xpath'
+ assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,xpath)
+ and: 'only one subscription id is related to now ongoing cm subscription'
+ assert cmNotificationSubscriptionPersistenceService.
+ getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 1
+ }
+
+ def 'Adding a cm notification subscription to an already existing'() {
+ given: 'an ongoing cm subscription'
+ def datastoreType = PASSTHROUGH_RUNNING
+ def cmHandleId = 'ch-1'
+ def xpath = '/x/y'
+ cmNotificationSubscriptionPersistenceService.addOrUpdateCmNotificationSubscription(datastoreType,cmHandleId,xpath,
+ 'subId-1')
+ when: 'a new cm notification subscription is made for the SAME CM handle and xpath'
+ cmNotificationSubscriptionPersistenceService.addOrUpdateCmNotificationSubscription(datastoreType,cmHandleId,xpath,
+ 'subId-2')
+ then: 'it is added to the ongoing list of subscription ids'
+ def subscriptionIds = cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath)
+ assert subscriptionIds.size() == 2
+ and: 'both subscription ids exists for the CM handle and xpath'
+ assert subscriptionIds.contains("subId-1") && subscriptionIds.contains("subId-2")
+ }
+}
diff --git a/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java b/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java
new file mode 100644
index 0000000000..d41f752912
--- /dev/null
+++ b/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java
@@ -0,0 +1,86 @@
+/*
+ * ============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;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.testcontainers.containers.KafkaContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * The Apache Kafka test container wrapper.
+ * Allow to use specific image and version with Singleton design pattern.
+ * This ensures only one instance of Kafka container across the integration tests.
+ * Avoid unnecessary resource and time consumption.
+ */
+public class KafkaTestContainer extends KafkaContainer {
+
+ private static final String IMAGE_NAME_AND_VERSION = "registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1";
+
+ private static KafkaTestContainer kafkaTestContainer;
+
+ private KafkaTestContainer() {
+ super(DockerImageName.parse(IMAGE_NAME_AND_VERSION).asCompatibleSubstituteFor("confluentinc/cp-kafka"));
+ }
+
+ /**
+ * Provides an instance of Kafka test container wrapper.
+ * This will allow to initialize Kafka messaging support before any integration test run.
+ *
+ * @return KafkaTestContainer the unique Kafka instance
+ */
+ public static KafkaTestContainer getInstance() {
+ if (kafkaTestContainer == null) {
+ kafkaTestContainer = new KafkaTestContainer();
+ Runtime.getRuntime().addShutdownHook(new Thread(kafkaTestContainer::close));
+ }
+ return kafkaTestContainer;
+ }
+
+ public static KafkaConsumer getConsumer(final String consumerGroupId, final Object valueDeserializer) {
+ return new KafkaConsumer<>(consumerProperties(consumerGroupId, valueDeserializer));
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ System.setProperty("spring.kafka.properties.bootstrap.servers", kafkaTestContainer.getBootstrapServers());
+ }
+
+ @Override
+ public void stop() {
+ // Method intentionally left blank
+ }
+
+ private static Map<String, Object> consumerProperties(final String consumerGroupId,
+ final Object valueDeserializer) {
+ final Map<String, Object> configProps = new HashMap<>();
+ configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaTestContainer.getBootstrapServers());
+ configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+ configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, valueDeserializer);
+ configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
+ configProps.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId);
+ return configProps;
+ }
+
+}