aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-ncmp-rest/docs/openapi/components.yaml10
-rwxr-xr-xcps-ncmp-rest/docs/openapi/ncmp.yml27
-rwxr-xr-xcps-ncmp-rest/docs/openapi/openapi.yml5
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java77
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy91
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java19
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java76
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java41
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy82
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java9
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java10
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java3
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java63
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy25
-rwxr-xr-xcps-ri/src/test/resources/data/fragment.sql21
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/CpsAdminService.java12
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java7
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java12
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java41
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java2
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java11
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy12
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy14
-rw-r--r--csit/data/cmHandleRegistration.json5
-rw-r--r--csit/plans/cps/testplan.txt8
-rw-r--r--csit/tests/cps-model-sync/cps-model-sync.robot2
-rw-r--r--csit/tests/public-properties-query/public-properties-query.robot51
27 files changed, 576 insertions, 160 deletions
diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml
index 247fabd023..a7955c19f9 100644
--- a/cps-ncmp-rest/docs/openapi/components.yaml
+++ b/cps-ncmp-rest/docs/openapi/components.yaml
@@ -211,6 +211,16 @@ components:
type: string
example: my-module-revision
+ CmHandleQueryRestParameters:
+ type: object
+ title: Cm Handle query parameters for executing cm handle search
+ properties:
+ publicCmHandleProperties:
+ type: object
+ additionalProperties:
+ type: string
+ example: Book Type
+
RestOutputCmHandle:
type: object
title: CM handle Details
diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml
index 03ed98a0e9..05e4b84853 100755
--- a/cps-ncmp-rest/docs/openapi/ncmp.yml
+++ b/cps-ncmp-rest/docs/openapi/ncmp.yml
@@ -291,6 +291,33 @@ retrieveCmHandleDetailsById:
application/json:
schema:
$ref: 'components.yaml#/components/schemas/RestOutputCmHandle'
+ 404:
+ $ref: 'components.yaml#/components/responses/NotFound'
+ 500:
+ $ref: 'components.yaml#/components/responses/InternalServerError'
+
+queryCmHandles:
+ post:
+ description: Execute cm handle query search
+ tags:
+ - network-cm-proxy
+ summary: Execute cm handle query upon a given set of query parameters
+ operationId: queryCmHandles
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yaml#/components/schemas/CmHandleQueryRestParameters'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
400:
$ref: 'components.yaml#/components/responses/BadRequest'
401:
diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml
index 12a8318efb..935b657e1f 100755
--- a/cps-ncmp-rest/docs/openapi/openapi.yml
+++ b/cps-ncmp-rest/docs/openapi/openapi.yml
@@ -39,4 +39,7 @@ paths:
$ref: 'ncmp.yml#/executeCmHandleSearch'
/v1/ch/{cm-handle}:
- $ref: 'ncmp.yml#/retrieveCmHandleDetailsById' \ No newline at end of file
+ $ref: 'ncmp.yml#/retrieveCmHandleDetailsById'
+
+ /v1/data/ch/searches:
+ $ref: 'ncmp.yml#/queryCmHandles'
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 de6c3c4f3d..84fcd88a96 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
@@ -31,18 +31,25 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum
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.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException;
+import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
import org.onap.cps.ncmp.rest.model.CmHandleProperties;
import org.onap.cps.ncmp.rest.model.CmHandleProperty;
import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties;
+import org.onap.cps.ncmp.rest.model.CmHandleQueryRestParameters;
import org.onap.cps.ncmp.rest.model.CmHandles;
import org.onap.cps.ncmp.rest.model.ConditionProperties;
import org.onap.cps.ncmp.rest.model.Conditions;
@@ -50,6 +57,7 @@ import org.onap.cps.ncmp.rest.model.ModuleNameAsJsonObject;
import org.onap.cps.ncmp.rest.model.ModuleNamesAsJsonArray;
import org.onap.cps.ncmp.rest.model.RestModuleReference;
import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
+import org.onap.cps.utils.CpsValidator;
import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -63,6 +71,9 @@ import org.springframework.web.bind.annotation.RestController;
public class NetworkCmProxyController implements NetworkCmProxyApi {
private static final String NO_BODY = null;
+ private static final String NO_REQUEST_ID = null;
+ private static final String NO_TOPIC = null;
+ public static final String ASYNC_REQUEST_ID = "requestId";
private final NetworkCmProxyDataService networkCmProxyDataService;
private final JsonObjectMapper jsonObjectMapper;
@@ -82,11 +93,19 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
final @NotNull @Valid String resourceIdentifier,
final @Valid String optionsParamInQuery,
final @Valid String topicParamInQuery) {
+ final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
+ final Map<String, Object> asyncResponseData = asyncResponse.getBody();
+
final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle(cmHandle,
resourceIdentifier,
optionsParamInQuery,
- topicParamInQuery);
- return ResponseEntity.ok(responseObject);
+ asyncResponseData == null ? NO_TOPIC : topicParamInQuery,
+ asyncResponseData == null ? NO_REQUEST_ID : asyncResponseData.get(ASYNC_REQUEST_ID).toString());
+
+ if (asyncResponseData == null) {
+ return ResponseEntity.ok(responseObject);
+ }
+ return ResponseEntity.ok(asyncResponse);
}
/**
@@ -103,11 +122,19 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
final @NotNull @Valid String resourceIdentifier,
final @Valid String optionsParamInQuery,
final @Valid String topicParamInQuery) {
+ final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
+ final Map<String, Object> asyncResponseData = asyncResponse.getBody();
+
final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(cmHandle,
resourceIdentifier,
optionsParamInQuery,
- topicParamInQuery);
- return ResponseEntity.ok(responseObject);
+ asyncResponseData == null ? NO_TOPIC : topicParamInQuery,
+ asyncResponseData == null ? NO_REQUEST_ID : asyncResponseData.get(ASYNC_REQUEST_ID).toString());
+
+ if (asyncResponseData == null) {
+ return ResponseEntity.ok(responseObject);
+ }
+ return ResponseEntity.ok(asyncResponse);
}
@Override
@@ -189,6 +216,19 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
}
/**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryRestParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ public ResponseEntity<List<String>> queryCmHandles(
+ final CmHandleQueryRestParameters cmHandleQueryRestParameters) {
+ final Set<String> cmHandleIds = networkCmProxyDataService.queryCmHandles(
+ jsonObjectMapper.convertToValueType(cmHandleQueryRestParameters, CmHandleQueryApiParameters.class));
+ return ResponseEntity.ok(List.copyOf(cmHandleIds));
+ }
+
+ /**
* Search for Cm Handle and Properties by Name.
* @param cmHandleId cm-handle identifier
* @return cm handle and its properties
@@ -257,4 +297,33 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
restOutputCmHandle.setPublicCmHandleProperties(cmHandlePublicProperties);
return restOutputCmHandle;
}
+
+ private ResponseEntity<Map<String, Object>> populateAsyncResponse(final String topicParamInQuery) {
+ final boolean processAsynchronously = hasTopicParameter(topicParamInQuery);
+ final Map<String, Object> responseData;
+ if (processAsynchronously) {
+ responseData = getAsyncResponseData();
+ } else {
+ responseData = null;
+ }
+ return ResponseEntity.ok().body(responseData);
+ }
+
+ private static boolean hasTopicParameter(final String topicName) {
+ if (topicName == null) {
+ return false;
+ }
+ if (CpsValidator.validateTopicName(topicName)) {
+ return true;
+ }
+ throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
+ }
+
+ private Map<String, Object> getAsyncResponseData() {
+ final Map<String, Object> asyncResponseData = new HashMap<>(1);
+ final String resourceDataRequestId = UUID.randomUUID().toString();
+ asyncResponseData.put(ASYNC_REQUEST_ID, resourceDataRequestId);
+ return asyncResponseData;
+ }
+
}
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 88633450b3..efe0f3ae6c 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
@@ -60,7 +60,10 @@ class NetworkCmProxyControllerSpec extends Specification {
NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
@SpringBean
- JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+ ObjectMapper objectMapper = new ObjectMapper()
+
+ @SpringBean
+ JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
@SpringBean
NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
@@ -72,6 +75,7 @@ class NetworkCmProxyControllerSpec extends Specification {
@Shared
def NO_TOPIC = null
+ def NO_REQUEST_ID = null
def 'Get Resource Data from pass-through operational.'() {
given: 'resource data url'
@@ -86,14 +90,15 @@ class NetworkCmProxyControllerSpec extends Specification {
1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
'parent/child',
'(a=1,b=2)',
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
and: 'response status is Ok'
response.status == HttpStatus.OK.value()
}
- def 'Get Resource Data from pass-through operational with #scenario.'() {
+ def 'Get Resource Data from #datastoreInUrl with #scenario.'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
"?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
when: 'get data resource request is performed'
def response = mvc.perform(
@@ -101,19 +106,30 @@ class NetworkCmProxyControllerSpec extends Specification {
.contentType(MediaType.APPLICATION_JSON)
).andReturn().response
then: 'the NCMP data service is called with operational data for cm handle'
- 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
+ expectedNumberOfMethodExecutions
+ * mockNetworkCmProxyDataService."${expectedMethodName}"('testCmHandle',
'parent/child',
'(a=1,b=2)',
- expectedTopicName)
- and: 'response status is Ok'
- response.status == HttpStatus.OK.value()
+ expectedTopicName,
+ _)
+ then: 'response status is expected'
+ response.status == expectedHttpStatus
where: 'the following parameters are used'
- scenario | topicQueryParam || expectedTopicName
- 'Url with valid topic' | "&topic=my-topic-name" || "my-topic-name"
- 'No topic in url' | '' || NO_TOPIC
- 'Null topic in url' | "&topic=null" || "null"
- 'Empty topic in url' | "&topic=\"\"" || "\"\""
- 'Missing topic in url' | "&topic=" || ""
+ scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedMethodName | expectedNumberOfMethodExecutions | expectedHttpStatus
+ 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 'getResourceDataOperationalForCmHandle' | 1 | HttpStatus.OK.value()
+ 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 'getResourceDataOperationalForCmHandle' | 1 | HttpStatus.OK.value()
+ 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 'getResourceDataOperationalForCmHandle' | 1 | HttpStatus.OK.value()
+ 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'missing topic in url' | 'passthrough-operational' | '&topic=' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#' || null | 'getResourceDataOperationalForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 'getResourceDataPassThroughRunningForCmHandle' | 1 | HttpStatus.OK.value()
+ 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 'getResourceDataPassThroughRunningForCmHandle' | 1 | HttpStatus.OK.value()
+ 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 'getResourceDataPassThroughRunningForCmHandle' | 1 | HttpStatus.OK.value()
+ 'empty topic in url' | 'passthrough-running' | '&topic=\"\"' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'missing topic in url' | 'passthrough-running' | '&topic=' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'blank topic value in url' | 'passthrough-running' | '&topic=\" \"' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
+ 'invalid non-empty topic value in url' | 'passthrough-running' | '&topic=1_5_*_#' || null | 'getResourceDataPassThroughRunningForCmHandle' | 0 | HttpStatus.BAD_REQUEST.value()
}
def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
@@ -124,7 +140,8 @@ class NetworkCmProxyControllerSpec extends Specification {
mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
resourceIdentifier,
'(a=1,b=2)',
- NO_TOPIC) >> '{valid-json}'
+ NO_TOPIC,
+ NO_REQUEST_ID) >> '{valid-json}'
when: 'get data resource request is performed'
def response = mvc.perform(
get(getUrl)
@@ -241,6 +258,31 @@ class NetworkCmProxyControllerSpec extends Specification {
response.contentAsString == '{"cmHandles":[]}'
}
+ def 'Query for cm handles matching query parameters'() {
+ given: 'an endpoint and json data'
+ def searchesEndpoint = "$ncmpBasePathV1/data/ch/searches"
+ String jsonString = '{"publicCmHandleProperties": {"name": "Contact", "value": "newemailforstore@bookstore.com"}}'
+ and: 'the service method is invoked with module names and returns cm handle ids'
+ 1 * mockNetworkCmProxyDataService.queryCmHandles(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
+ when: 'the searches api is invoked'
+ def response = mvc.perform(post(searchesEndpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonString)).andReturn().response
+ then: 'cm handle ids are returned'
+ response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
+ }
+
+ def 'Query for cm handles with invalid request payload'() {
+ when: 'the searches api is invoked'
+ def searchesEndpoint = "$ncmpBasePathV1/data/ch/searches"
+ def invalidInputData = '{invalidJson}'
+ def response = mvc.perform(post(searchesEndpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidInputData)).andReturn().response
+ then: 'BAD_REQUEST is returned'
+ 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" +
@@ -271,5 +313,24 @@ class NetworkCmProxyControllerSpec extends Specification {
and: 'the response is No Content'
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"
+ when: 'get data resource request is performed'
+ 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'
+ scenario | datastoreInUrl
+ ':passthrough-operational' | 'passthrough-operational'
+ ':passthrough-running' | 'passthrough-running'
+ }
+
}
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 d50b8c5ea8..058c42b7b9 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
@@ -26,6 +26,8 @@ package org.onap.cps.ncmp.api;
import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
import java.util.Collection;
+import java.util.Set;
+import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
@@ -52,12 +54,14 @@ public interface NetworkCmProxyDataService {
* @param resourceIdentifier resource identifier
* @param optionsParamInQuery options query
* @param topicParamInQuery topic name for (triggering) async responses
+ * @param requestId unique requestId for async request
* @return {@code Object} resource data
*/
Object getResourceDataOperationalForCmHandle(String cmHandleId,
String resourceIdentifier,
String optionsParamInQuery,
- String topicParamInQuery);
+ String topicParamInQuery,
+ String requestId);
/**
* Get resource data for data store pass-through running
@@ -66,13 +70,15 @@ public interface NetworkCmProxyDataService {
* @param cmHandleId cm handle identifier
* @param resourceIdentifier resource identifier
* @param optionsParamInQuery options query
- * @param topicParamInQuery topic query
+ * @param topicParamInQuery topic name for (triggering) async responses
+ * @param requestId unique requestId for async request
* @return {@code Object} resource data
*/
Object getResourceDataPassThroughRunningForCmHandle(String cmHandleId,
String resourceIdentifier,
String optionsParamInQuery,
- String topicParamInQuery);
+ String topicParamInQuery,
+ String requestId);
/**
* Write resource data for data store pass-through running
@@ -115,4 +121,11 @@ public interface NetworkCmProxyDataService {
*/
NcmpServiceCmHandle getNcmpServiceCmHandle(String cmHandleId);
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryApiParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryApiParameters cmHandleQueryApiParameters);
}
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 81c060eb3b..9c3d9448ef 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
@@ -31,14 +31,14 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMES
import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
+import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
-import java.util.regex.Pattern;
+import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -47,12 +47,12 @@ import org.onap.cps.api.CpsDataService;
import org.onap.cps.api.CpsModuleService;
import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
-import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException;
import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError;
import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
@@ -65,7 +65,6 @@ import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
import org.onap.cps.spi.model.ModuleReference;
import org.onap.cps.utils.CpsValidator;
import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@@ -90,12 +89,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
private final YangModelCmHandleRetriever yangModelCmHandleRetriever;
- // valid kafka topic name regex
- private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|"
- + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$");
- private static final String NO_REQUEST_ID = null;
- private static final String NO_TOPIC = null;
-
@Override
public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
final DmiPluginRegistration dmiPluginRegistration) {
@@ -119,20 +112,22 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
public Object getResourceDataOperationalForCmHandle(final String cmHandleId,
final String resourceIdentifier,
final String optionsParamInQuery,
- final String topicParamInQuery) {
+ final String topicParamInQuery,
+ final String requestId) {
CpsValidator.validateNameCharacters(cmHandleId);
- return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier,
- DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery);
+ return getResourceDataResponse(cmHandleId, resourceIdentifier,
+ DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery, requestId);
}
@Override
public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
final String resourceIdentifier,
final String optionsParamInQuery,
- final String topicParamInQuery) {
+ final String topicParamInQuery,
+ final String requestId) {
CpsValidator.validateNameCharacters(cmHandleId);
- return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier,
- DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery);
+ return getResourceDataResponse(cmHandleId, resourceIdentifier,
+ DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery, requestId);
}
@Override
@@ -165,6 +160,20 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
return cpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNames);
}
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+
+ cmHandleQueryApiParameters.getPublicProperties().forEach((key, value) -> {
+ if (Strings.isNullOrEmpty(key)) {
+ throw new DataValidationException("Invalid Query Parameter.",
+ "Missing property name - please supply a valid name.");
+ }
+ });
+
+ return cpsAdminService.queryCmHandles(jsonObjectMapper.convertToValueType(cmHandleQueryApiParameters,
+ org.onap.cps.spi.model.CmHandleQueryParameters.class));
+ }
+
/**
* Retrieve cm handle details for a given cm handle.
*
@@ -329,35 +338,14 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
yangModelCmHandle.getId());
}
- private static boolean hasTopicParameter(final String topicName) {
- if (topicName == null) {
- return false;
- }
- if (TOPIC_NAME_PATTERN.matcher(topicName).matches()) {
- return true;
- }
- throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
- }
-
- private Map<String, Object> buildDmiResponse(final String requestId) {
- final Map<String, Object> dmiResponseMap = new HashMap<>();
- dmiResponseMap.put("requestId", requestId);
- return dmiResponseMap;
- }
-
- private Object validateTopicNameAndGetResourceData(final String cmHandleId,
- final String resourceIdentifier,
- final DmiOperations.DataStoreEnum dataStore,
- final String optionsParamInQuery,
- final String topicParamInQuery) {
- final boolean processAsynchronously = hasTopicParameter(topicParamInQuery);
- if (processAsynchronously) {
- final String resourceDataRequestId = UUID.randomUUID().toString();
- return ResponseEntity.status(HttpStatus.OK)
- .body(buildDmiResponse(resourceDataRequestId));
- }
+ private Object getResourceDataResponse(final String cmHandleId,
+ final String resourceIdentifier,
+ final DmiOperations.DataStoreEnum dataStore,
+ final String optionsParamInQuery,
+ final String topicParamInQuery,
+ final String requestId) {
final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(
- cmHandleId, resourceIdentifier, optionsParamInQuery, dataStore, NO_REQUEST_ID, NO_TOPIC);
+ cmHandleId, resourceIdentifier, optionsParamInQuery, dataStore, requestId, topicParamInQuery);
return handleResponse(responseEntity, OperationEnum.READ);
}
} \ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java
new file mode 100644
index 0000000000..3f584ed153
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.models;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.Map;
+import javax.validation.Valid;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@JsonInclude(Include.NON_NULL)
+public class CmHandleQueryApiParameters {
+
+ @JsonProperty("publicCmHandleProperties")
+ @Valid
+ private Map<String, String> publicProperties = Collections.emptyMap();
+
+}
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 489c71c0e9..2d01dba522 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
@@ -23,7 +23,6 @@
package org.onap.cps.ncmp.api.impl
import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
-import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException
import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
import spock.lang.Shared
@@ -119,7 +118,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'DMI returns a json response'
response == 'dmi-response'
}
@@ -137,7 +137,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'exception is thrown with the expected response code and details'
def exceptionThrown = thrown(HttpClientRequestException.class)
exceptionThrown.details.contains('NOK-json')
@@ -160,7 +161,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'exception is thrown'
def exceptionThrown = thrown(HttpClientRequestException.class)
and: 'details contain the original response'
@@ -183,7 +185,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'get resource data returns expected response'
response == '{dmi-response}'
}
@@ -204,7 +207,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
'testResourceId',
OPTIONS_PARAM,
- NO_TOPIC)
+ NO_TOPIC,
+ NO_REQUEST_ID)
then: 'exception is thrown'
def exceptionThrown = thrown(HttpClientRequestException.class)
and: 'details contain the original response'
@@ -212,72 +216,6 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
exceptionThrown.httpStatus == HttpStatus.NOT_FOUND.value()
}
- def 'DMI Operational data request with #scenario'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, NO_REQUEST_ID, NO_TOPIC)
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called data operational with blank topic'
- def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '',
- '', emptyTopic)
- then: 'a invalid topic exception is thrown'
- thrown(InvalidTopicException)
- where: 'the following parameters are used'
- scenario | emptyTopic
- 'no topic value in url' | ''
- 'empty topic value in url' | '\"\"'
- 'blank topic value in url' | ' '
- 'invalid non-empty topic value in url' | '1_5_*_#'
- }
-
- def 'Get resource data for data operational from DMI with valid topic i.e. async request.'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode(*_) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, 'my-topic-name')
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called for data operational with valid topic'
- def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '', '', 'my-topic-name')
- then: 'non empty request id is generated'
- assert responseData.body.requestId.length() > 0
- }
-
- def 'Get resource data for pass through running from DMI with valid topic async request.'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, 'my-topic-name')
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called for data operational with valid topic'
- def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
- '', OPTIONS_PARAM, 'my-topic-name')
- then: 'non empty request id is generated'
- assert responseData.body.requestId.length() > 0
- }
-
- def 'DMI pass through running data request with #scenario'() {
- given: 'cps data service returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
- and: 'dmi data operation returns valid response and data'
- mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, NO_REQUEST_ID, NO_TOPIC)
- >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
- when: 'get resource data is called for data operational with valid topic'
- def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
- '', '', emptyTopic)
- then: 'a invalid topic exception is thrown'
- thrown(InvalidTopicException)
- where: 'the following parameters are used'
- scenario | emptyTopic
- 'no topic value in url' | ''
- 'empty topic value in url' | '\"\"'
- 'blank topic value in url' | ' '
- 'invalid non-empty topic value in url' | '1_5_*_#'
- }
-
def 'Getting Yang Resources.'() {
when: 'yang resources is called'
objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
index 50b27207ee..2e7bb7e969 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
@@ -24,6 +24,7 @@ package org.onap.cps.spi.impl;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import lombok.AllArgsConstructor;
@@ -36,8 +37,10 @@ import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.DataspaceInUseException;
import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
import org.onap.cps.spi.repository.AnchorRepository;
import org.onap.cps.spi.repository.DataspaceRepository;
+import org.onap.cps.spi.repository.ModuleReferenceRepository;
import org.onap.cps.spi.repository.SchemaSetRepository;
import org.onap.cps.spi.repository.YangResourceRepository;
import org.springframework.dao.DataIntegrityViolationException;
@@ -51,6 +54,7 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
private final AnchorRepository anchorRepository;
private final SchemaSetRepository schemaSetRepository;
private final YangResourceRepository yangResourceRepository;
+ private final ModuleReferenceRepository moduleReferenceRepository;
@Override
public void createDataspace(final String dataspaceName) {
@@ -132,6 +136,11 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
anchorRepository.delete(anchorEntity);
}
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) {
+ return moduleReferenceRepository.queryCmHandles(cmHandleQueryParameters);
+ }
+
private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) {
final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java
index 6551937e10..4bc9dd9603 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java
@@ -21,6 +21,8 @@
package org.onap.cps.spi.repository;
import java.util.Collection;
+import java.util.Set;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
import org.onap.cps.spi.model.ModuleReference;
/**
@@ -31,4 +33,12 @@ public interface ModuleReferenceQuery {
Collection<ModuleReference> identifyNewModuleReferences(
final Collection<ModuleReference> moduleReferencesToCheck);
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters);
+
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java
index ce2bfe7847..f70e218373 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java
@@ -27,8 +27,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
-public interface ModuleReferenceRepository extends
- JpaRepository<YangResourceEntity, Long>, ModuleReferenceQuery {
+public interface ModuleReferenceRepository extends JpaRepository<YangResourceEntity, Long>, ModuleReferenceQuery {
Collection<ModuleReference> identifyNewModuleReferences(
final Collection<ModuleReference> moduleReferencesToCheck);
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
index 0e79deb8e8..40a93da71c 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
@@ -24,21 +24,32 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
+import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
+import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.ModuleReference;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Transactional
+@AllArgsConstructor
public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
@PersistenceContext
private EntityManager entityManager;
+ private final CpsDataPersistenceService cpsDataPersistenceService;
+
@Override
@SneakyThrows
public Collection<ModuleReference> identifyNewModuleReferences(
@@ -57,6 +68,56 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
return identifyNewModuleReferencesForCmHandle(tempTableName);
}
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) {
+
+ if (cmHandleQueryParameters.getPublicProperties().entrySet().isEmpty()) {
+ return getAllCmHandles();
+ }
+
+ final Collection<DataNode> amalgamatedQueryResult = new ArrayList<>();
+ int queryConditionCounter = 0;
+ for (final Map.Entry<String, String> entry : cmHandleQueryParameters.getPublicProperties().entrySet()) {
+ final StringBuilder cmHandlePath = new StringBuilder();
+ cmHandlePath.append("//public-properties[@name='").append(entry.getKey()).append("' ");
+ cmHandlePath.append("and @value='").append(entry.getValue()).append("']");
+ cmHandlePath.append("/ancestor::cm-handles");
+
+ final Collection<DataNode> singleConditionQueryResult =
+ cpsDataPersistenceService.queryDataNodes("NCMP-Admin",
+ "ncmp-dmi-registry", String.valueOf(cmHandlePath), FetchDescendantsOption.OMIT_DESCENDANTS);
+ if (++queryConditionCounter == 1) {
+ amalgamatedQueryResult.addAll(singleConditionQueryResult);
+ } else {
+ amalgamatedQueryResult.retainAll(singleConditionQueryResult);
+ }
+
+ if (amalgamatedQueryResult.isEmpty()) {
+ break;
+ }
+ }
+
+ return extractCmHandleIds(amalgamatedQueryResult);
+ }
+
+ private Set<String> getAllCmHandles() {
+ final Collection<DataNode> cmHandles = cpsDataPersistenceService.queryDataNodes("NCMP-Admin",
+ "ncmp-dmi-registry", "//public-properties/ancestor::cm-handles",
+ FetchDescendantsOption.OMIT_DESCENDANTS);
+ return extractCmHandleIds(cmHandles);
+ }
+
+ private Set<String> extractCmHandleIds(final Collection<DataNode> cmHandles) {
+ return cmHandles.stream().map(cmHandle -> cmHandle.getLeaves().get("id").toString())
+ .collect(Collectors.toSet());
+ }
+
private void createTemporaryTable(final String tempTableName) {
final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE " + tempTableName + "(");
sqlStringBuilder.append(" id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,");
@@ -95,7 +156,7 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
+ " WHERE yang_resource.module_name IS NULL;", tempTableName);
final List<Object[]> resultsAsObjects =
- entityManager.createNativeQuery(sql).getResultList();
+ (List<Object[]>) entityManager.createNativeQuery(sql).getResultList();
final List<ModuleReference> resultsAsModuleReferences = new ArrayList<>(resultsAsObjects.size());
for (final Object[] row : resultsAsObjects) {
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
index 063bd5b5ae..f486cb76d0 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021 Nordix Foundation
+ * Copyright (C) 2021-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 Bell Canada
* ================================================================================
@@ -22,6 +22,7 @@
package org.onap.cps.spi.impl
+import org.mockito.Mock
import org.onap.cps.spi.CpsAdminPersistenceService
import org.onap.cps.spi.exceptions.AlreadyDefinedException
import org.onap.cps.spi.exceptions.AnchorNotFoundException
@@ -30,15 +31,21 @@ import org.onap.cps.spi.exceptions.DataspaceNotFoundException
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException
import org.onap.cps.spi.model.Anchor
+import org.onap.cps.spi.model.CmHandleQueryParameters
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper
class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
@Autowired
CpsAdminPersistenceService objectUnderTest
+ @Mock
+ ObjectMapper objectMapper
+
static final String SET_DATA = '/data/anchor.sql'
+ static final String SET_FRAGMENT_DATA = '/data/fragment.sql'
static final String SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES = '/data/anchors-schemaset-modules.sql'
static final String DATASPACE_WITH_NO_DATA = 'DATASPACE-002-NO-DATA'
static final Integer DELETED_ANCHOR_ID = 3002
@@ -219,4 +226,20 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
'dataspace contains schemasets' | 'DATASPACE-003' || DataspaceInUseException | 'contains 1 schemaset(s)'
}
+ @Sql([CLEAR_DATA, SET_FRAGMENT_DATA])
+ def 'Retrieve cm handle ids when #scenario.'() {
+ when: 'the service is invoked'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ cmHandleQueryParameters.setPublicProperties(publicProperties)
+ def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
+ then: 'the correct expected cm handles are returned'
+ returnedCmHandles == expectedCmHandleIds
+ where: 'the following data is used'
+ scenario | publicProperties || expectedCmHandleIds
+ 'single matching property' | ['Contact' : 'newemailforstore@bookstore.com'] || ['PNFDemo2', 'PNFDemo', 'PNFDemo4'] as Set
+ 'public property dont match' | ['wont_match' : 'wont_match'] || [] as Set
+ '2 properties, only one match (and)' | ['Contact' : 'newemailforstore@bookstore.com', 'Contact2': 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] as Set
+ '2 properties, no match (and)' | ['Contact' : 'newemailforstore@bookstore.com', 'Contact2': ''] || [] as Set
+ 'No public properties - return all cm handles' | [ : ] || ['PNFDemo3', 'PNFDemo', 'PNFDemo2', 'PNFDemo4'] as Set
+ }
}
diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql
index a27bb5fdea..fb4a5f77cb 100755
--- a/cps-ri/src/test/resources/data/fragment.sql
+++ b/cps-ri/src/test/resources/data/fragment.sql
@@ -1,6 +1,6 @@
/*
============LICENSE_START=======================================================
- Copyright (C) 2021 Nordix Foundation.
+ Copyright (C) 2021-2022 Nordix Foundation.
Modifications Copyright (C) 2021 Pantheon.tech
Modifications Copyright (C) 2021-2022 Bell Canada.
================================================================================
@@ -21,14 +21,16 @@
*/
INSERT INTO DATASPACE (ID, NAME) VALUES
- (1001, 'DATASPACE-001');
+ (1001, 'DATASPACE-001'),
+ (1002, 'NCMP-Admin');
INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
(2001, 'SCHEMA-SET-001', 1001);
INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
(3001, 'ANCHOR-001', 1001, 2001),
- (3003, 'ANCHOR-003', 1001, 2001);
+ (3003, 'ANCHOR-003', 1001, 2001),
+ (3004, 'ncmp-dmi-registry', 1002, 2001);
INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES
(4001, 1001, 3001, null, '/parent-1'),
@@ -67,4 +69,15 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
(4230, 1001, 3003, 4227, '/parent-206/child-206/grand-child-206[@key="X"]', '{"key": "X"}'),
(4231, 1001, 3003, null, '/parent-206[@key="A"]', '{"key": "A"}'),
(4232, 1001, 3003, 4231, '/parent-206[@key="A"]/child-206', '{}'),
- (4233, 1001, 3003, null, '/parent-206[@key="B"]', '{"key": "B"}'); \ No newline at end of file
+ (4233, 1001, 3003, null, '/parent-206[@key="B"]', '{"key": "B"}');
+
+INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
+ (5000, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo"]', '{"id": "PNFDemo", "dmi-service-name": "http://172.21.235.14:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5001, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo2"]', '{"id": "PNFDemo2", "dmi-service-name": "http://172.26.46.68:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5002, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo3"]', '{"id": "PNFDemo3", "dmi-service-name": "http://172.26.46.68:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5003, 1002, 3004, null, '/dmi-registry/cm-handles[@id="PNFDemo4"]', '{"id": "PNFDemo4", "dmi-service-name": "http://172.26.46.68:8783", "dmi-data-service-name": "", "dmi-model-service-name": ""}'),
+ (5004, 1002, 3004, 5000, '/dmi-registry/cm-handles[@id="PNFDemo"]/public-properties[@name="Contact"]', '{"name": "Contact", "value": "newemailforstore@bookstore.com"}'),
+ (5005, 1002, 3004, 5001, '/dmi-registry/cm-handles[@id="PNFDemo2"]/public-properties[@name="Contact"]', '{"name": "Contact", "value": "newemailforstore@bookstore.com"}'),
+ (5006, 1002, 3004, 5002, '/dmi-registry/cm-handles[@id="PNFDemo3"]/public-properties[@name="Contact"]', '{"name": "Contact3", "value": "PNF3@bookstore.com"}'),
+ (5007, 1002, 3004, 5003, '/dmi-registry/cm-handles[@id="PNFDemo4"]/public-properties[@name="Contact"]', '{"name": "Contact", "value": "newemailforstore@bookstore.com"}'),
+ (5008, 1002, 3004, 5004, '/dmi-registry/cm-handles[@id="PNFDemo4"]/public-properties[@name="Contact2"]', '{"name": "Contact2", "value": "newemailforstore2@bookstore.com"}'); \ No newline at end of file
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
index 2cb01ac1ef..2106f1584e 100755
--- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2021 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
@@ -23,9 +23,11 @@
package org.onap.cps.api;
import java.util.Collection;
+import java.util.Set;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.CpsException;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
/**
* CPS Admin Service.
@@ -100,4 +102,12 @@ public interface CpsAdminService {
* given module names
*/
Collection<String> queryAnchorNames(String dataspaceName, Collection<String> moduleNames);
+
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
index 7bec1e39f0..762754f9a8 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
@@ -24,12 +24,14 @@ package org.onap.cps.api.impl;
import java.time.OffsetDateTime;
import java.util.Collection;
+import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.onap.cps.api.CpsAdminService;
import org.onap.cps.api.CpsDataService;
import org.onap.cps.spi.CpsAdminPersistenceService;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
import org.onap.cps.utils.CpsValidator;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -91,4 +93,9 @@ public class CpsAdminServiceImpl implements CpsAdminService {
final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
return anchors.stream().map(Anchor::getName).collect(Collectors.toList());
}
+
+ @Override
+ public Set<String> queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) {
+ return cpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters);
+ }
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
index dd4059d88c..25167e844a 100755
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation.
+ * Copyright (C) 2020-2022 Nordix Foundation.
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
@@ -23,8 +23,10 @@
package org.onap.cps.spi;
import java.util.Collection;
+import java.util.Set;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.CmHandleQueryParameters;
/*
Service for handling CPS admin data.
@@ -99,4 +101,12 @@ public interface CpsAdminPersistenceService {
* @param anchorName anchor name
*/
void deleteAnchor(String dataspaceName, String anchorName);
+
+ /**
+ * Query and return cm handles that match the given query parameters.
+ *
+ * @param cmHandleQueryParameters the cm handle query parameters
+ * @return collection of cm handle ids
+ */
+ Set<String> queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters);
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java b/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java
new file mode 100644
index 0000000000..ff4e627636
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.Map;
+import javax.validation.Valid;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@JsonInclude(Include.NON_NULL)
+public class CmHandleQueryParameters {
+
+ @JsonProperty("publicCmHandleProperties")
+ @Valid
+ private Map<String, String> publicProperties = Collections.emptyMap();
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
index 55e7b9970b..43aa06b81b 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
@@ -26,11 +26,13 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Setter(AccessLevel.PROTECTED)
@Getter
+@EqualsAndHashCode
public class DataNode {
DataNode() { }
diff --git a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
index dd16495638..28b49c9666 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
@@ -22,6 +22,7 @@ package org.onap.cps.utils;
import com.google.common.collect.Lists;
import java.util.Collection;
+import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -32,6 +33,8 @@ import org.onap.cps.spi.exceptions.DataValidationException;
public final class CpsValidator {
private static final char[] UNSUPPORTED_NAME_CHARACTERS = "!\" #$%&'()*+,./\\:;<=>?@[]^`{|}~".toCharArray();
+ private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|"
+ + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$");
/**
* Validate characters in names within cps.
@@ -48,4 +51,12 @@ public final class CpsValidator {
}
}
}
+
+ /**
+ * Validate kafka topic name pattern.
+ * @param topicName name of the topic to be validated
+ */
+ public static boolean validateTopicName(final String topicName) {
+ return topicName != null && TOPIC_NAME_PATTERN.matcher(topicName).matches();
+ }
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
index bb122d1ae2..cbe1ebbbdf 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
@@ -25,6 +25,7 @@ package org.onap.cps.api.impl
import org.onap.cps.api.CpsDataService
import org.onap.cps.spi.CpsAdminPersistenceService
import org.onap.cps.spi.model.Anchor
+import org.onap.cps.spi.model.CmHandleQueryParameters
import spock.lang.Specification
import java.time.OffsetDateTime
@@ -95,4 +96,13 @@ class CpsAdminServiceImplSpec extends Specification {
1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace')
}
+ def 'Query CM Handles.'() {
+ given: 'a cm handle query'
+ def cmHandleQueryParameters = new CmHandleQueryParameters()
+ when: 'query cm handles is invoked'
+ objectUnderTest.queryCmHandles(cmHandleQueryParameters)
+ then: 'associated persistence service method is invoked with correct parameter'
+ 1 * mockCpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters)
+ }
+
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
index 191472ceea..ce728ef1c1 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
@@ -45,4 +45,18 @@ class CpsValidatorSpec extends Specification {
'position 5' | 'name with spaces' || 'name with spaces invalid token encountered at position 5'
'position 9' | 'nameWith Space' || 'nameWith Space invalid token encountered at position 9'
}
+
+ def 'Validating topic names.'() {
+ when: 'the topic name is validated'
+ def isValidTopicName = CpsValidator.validateTopicName(topicName)
+ then: 'boolean response will be returned for #scenario'
+ assert isValidTopicName == booleanResponse
+ where: 'the following names are used'
+ scenario | topicName || booleanResponse
+ 'valid topic' | 'my-topic-name' || true
+ 'empty topic' | '' || false
+ 'blank topic' | ' ' || false
+ 'null topic' | null || false
+ 'invalid non empty topic' | '1_5_*_#' || false
+ }
}
diff --git a/csit/data/cmHandleRegistration.json b/csit/data/cmHandleRegistration.json
deleted file mode 100644
index 0133148fda..0000000000
--- a/csit/data/cmHandleRegistration.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "cmHandles": [
- "PNFDemo"
- ]
-} \ No newline at end of file
diff --git a/csit/plans/cps/testplan.txt b/csit/plans/cps/testplan.txt
index 8069bb72e7..d4615e7010 100644
--- a/csit/plans/cps/testplan.txt
+++ b/csit/plans/cps/testplan.txt
@@ -1,5 +1,5 @@
# ============LICENSE_START=======================================================
-# Copyright (C) 2021 Nordix Foundation
+# Copyright (C) 2021-2022 Nordix Foundation
# ================================================================================
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,8 +17,8 @@
# Test suites are relative paths under csit/tests/.
# Place the suites in run order.
actuator
-cps-model-sync
-ncmp-passthrough
cps-admin
cps-data
-
+cps-model-sync
+ncmp-passthrough
+public-properties-query \ No newline at end of file
diff --git a/csit/tests/cps-model-sync/cps-model-sync.robot b/csit/tests/cps-model-sync/cps-model-sync.robot
index 7de1f3a1be..ea082b5a89 100644
--- a/csit/tests/cps-model-sync/cps-model-sync.robot
+++ b/csit/tests/cps-model-sync/cps-model-sync.robot
@@ -34,7 +34,7 @@ ${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE=
${ncmpInventoryBasePath} /ncmpInventory
${ncmpBasePath} /ncmp
${dmiUrl} http://${DMI_HOST}:${DMI_PORT}
-${jsonDataCreate} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"cmHandle":"PNFDemo","cmHandleProperties":{"Book1":"Sci-Fi Book"},"publicCmHandleProperties":{"Contact":"storeemail@bookstore.com"}}]}
+${jsonDataCreate} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"cmHandle":"PNFDemo","cmHandleProperties":{"Book1":"Sci-Fi Book"},"publicCmHandleProperties":{"Contact":"storeemail@bookstore.com", "Contact2":"storeemail2@bookstore.com"}}]}
${jsonDataUpdate} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","updatedCmHandles":[{"cmHandle":"PNFDemo","cmHandleProperties":{"Book1":"Romance Book"},"publicCmHandleProperties":{"Contact":"newemailforstore@bookstore.com"}}]}
*** Test Cases ***
diff --git a/csit/tests/public-properties-query/public-properties-query.robot b/csit/tests/public-properties-query/public-properties-query.robot
new file mode 100644
index 0000000000..3a640871b9
--- /dev/null
+++ b/csit/tests/public-properties-query/public-properties-query.robot
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+*** Settings ***
+Documentation Public Properties Query Test
+
+Library Collections
+Library OperatingSystem
+Library RequestsLibrary
+Library BuiltIn
+
+Suite Setup Create Session CPS_URL http://${CPS_CORE_HOST}:${CPS_CORE_PORT}
+
+*** Variables ***
+
+${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE=
+${ncmpBasePath} /ncmp/v1
+${jsonMatchingQueryParameters} {"publicCmHandleProperties": {"Contact" : "newemailforstore@bookstore.com", "Contact2" : "storeemail2@bookstore.com"}}
+${jsonMissingPropertyQueryParameters} {"publicCmHandleProperties": { "" : "doesnt matter"}}
+
+*** Test Cases ***
+Retrieve CM Handles where query parameters Match
+ ${uri}= Set Variable ${ncmpBasePath}/data/ch/searches
+ ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth}
+ ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonMatchingQueryParameters}
+ ${responseJson}= Set Variable ${response.json()}
+ Should Be Equal As Strings ${response.status_code} 200
+ Should Contain ${responseJson} PNFDemo
+
+Throw 400 when Structure of Request is Incorrect
+ ${uri}= Set Variable ${ncmpBasePath}/data/ch/searches
+ ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth}
+ ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonMissingPropertyQueryParameters} expected_status=400
+ Should Be Equal As Strings ${response} <Response [400]>