From 1f2e66e720678f00682e6429aab7fa62251baf29 Mon Sep 17 00:00:00 2001 From: sourabh_sourabh Date: Wed, 16 Feb 2022 21:53:41 +0530 Subject: Async: NCMP Rest impl. including Request ID generation - Based on topic api can be sync or async - RequestId is generated using UUID - Used UriComponentBuilder to populate dmi service url. - New test cases are introduced to validate service url generation. Issue-ID: CPS-828 Signed-off-by: sourabh_sourabh Change-Id: I2696d5cb012d9274b0c1265f16aa9b2369657f05 --- cps-ncmp-rest/docs/openapi/components.yaml | 12 +++++++ cps-ncmp-rest/docs/openapi/ncmp.yml | 2 ++ .../rest/controller/NetworkCmProxyController.java | 14 +++++--- .../NetworkCmProxyRestExceptionHandler.java | 6 ++++ .../controller/NetworkCmProxyControllerSpec.groovy | 41 +++++++++++++++++++--- .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 32 ++++++++--------- 6 files changed, 83 insertions(+), 24 deletions(-) (limited to 'cps-ncmp-rest') diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index d82813b87..4dd7e4711 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -329,6 +329,18 @@ components: sample 3: value: options: (depth=2,fields=book/authors) + topicParamInQuery: + name: topic + in: query + description: topic parameter in query. + required: false + schema: + type: string + allowReserved: true + examples: + sample 1: + value: + topic: my-topic-name contentParamInHeader: name: Content-Type in: header diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index a267fb491..1afd7c911 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -29,6 +29,7 @@ getResourceDataForPassthroughOperational: - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/acceptParamInHeader' - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' + - $ref: 'components.yaml#/components/parameters/topicParamInQuery' responses: 200: description: OK @@ -60,6 +61,7 @@ resourceDataForPassthroughRunning: - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/acceptParamInHeader' - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' + - $ref: 'components.yaml#/components/parameters/topicParamInQuery' responses: 200: description: OK 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 86f4460ea..385180d8b 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 @@ -76,17 +76,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * @param resourceIdentifier resource identifier * @param acceptParamInHeader accept header parameter * @param optionsParamInQuery options query parameter + * @param topicParamInQuery topic query parameter * @return {@code ResponseEntity} response from dmi plugin */ @Override public ResponseEntity getResourceDataOperationalForCmHandle(final String cmHandle, final @NotNull @Valid String resourceIdentifier, final String acceptParamInHeader, - final @Valid String optionsParamInQuery) { + final @Valid String optionsParamInQuery, + final @Valid String topicParamInQuery) { final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle(cmHandle, resourceIdentifier, acceptParamInHeader, - optionsParamInQuery); + optionsParamInQuery, + topicParamInQuery); return ResponseEntity.ok(responseObject); } @@ -97,17 +100,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * @param resourceIdentifier resource identifier * @param acceptParamInHeader accept header parameter * @param optionsParamInQuery options query parameter + * @param topicParamInQuery topic query parameter * @return {@code ResponseEntity} response from dmi plugin */ @Override public ResponseEntity getResourceDataRunningForCmHandle(final String cmHandle, final @NotNull @Valid String resourceIdentifier, final String acceptParamInHeader, - final @Valid String optionsParamInQuery) { + final @Valid String optionsParamInQuery, + final @Valid String topicParamInQuery) { final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, acceptParamInHeader, - optionsParamInQuery); + optionsParamInQuery, + topicParamInQuery); return ResponseEntity.ok(responseObject); } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java index 5aaf1c31f..2495f3642 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java @@ -30,6 +30,7 @@ import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController; import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController; import org.onap.cps.ncmp.rest.model.ErrorMessage; import org.onap.cps.spi.exceptions.CpsException; +import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -69,6 +70,11 @@ public class NetworkCmProxyRestExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, exception); } + @ExceptionHandler({DataNodeNotFoundException.class}) + public static ResponseEntity handleNotFoundExceptions(final CpsException exception) { + return buildErrorResponse(HttpStatus.NOT_FOUND, exception); + } + private static ResponseEntity buildErrorResponse(final HttpStatus status, final Exception exception) { if (exception.getCause() != null || !(exception instanceof CpsException)) { log.error("Exception occurred", exception); 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 c99771443..9cbf626ba 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 @@ -24,6 +24,7 @@ package org.onap.cps.ncmp.rest.controller import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import spock.lang.Shared import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete @@ -70,7 +71,10 @@ class NetworkCmProxyControllerSpec extends Specification { def requestBody = '{"some-key":"some-value"}' - def 'Get Resource Data from pass-through operational.' () { + @Shared + def NO_TOPIC = null + + 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)" @@ -84,12 +88,40 @@ class NetworkCmProxyControllerSpec extends Specification { 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle', 'parent/child', 'application/json', - '(a=1,b=2)') + '(a=1,b=2)', + NO_TOPIC) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() + } + + def 'Get Resource Data from pass-through operational with #scenario.'() { + given: 'resource data url' + def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" + + "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" + 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: 'the NCMP data service is called with operational data for cm handle' + 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle', + 'parent/child', + 'application/json', + '(a=1,b=2)', + expectedTopicName) and: 'response status is Ok' response.status == HttpStatus.OK.value() + 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=" || "" } - def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.' () { + 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)" @@ -97,7 +129,8 @@ class NetworkCmProxyControllerSpec extends Specification { mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle', resourceIdentifier, 'application/json', - '(a=1,b=2)') >> '{valid-json}' + '(a=1,b=2)', + NO_TOPIC) >> '{valid-json}' when: 'get data resource request is performed' def response = mvc.perform( get(getUrl) diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy index 8004328bc..260ffd213 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -45,6 +45,7 @@ import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandl import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY import static org.springframework.http.HttpStatus.BAD_REQUEST import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR +import static org.springframework.http.HttpStatus.NOT_FOUND import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post @@ -76,40 +77,39 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { def dataNodeBaseEndpointNcmpInventory @Shared - def errorMessage = 'some error message' + def sampleErrorMessage = 'some error message' @Shared - def errorDetails = 'some error details' - @Shared - def dataNodeNotFoundErrorMessage = 'DataNode not found' + def sampleErrorDetails = 'some error details' def setup() { dataNodeBaseEndpointNcmp = "$basePathNcmp/v1" dataNodeBaseEndpointNcmpInventory = "$basePathNcmpInventory/v1" } - def 'Get request with generic #scenario exception returns correct HTTP Status.'() { + def 'Get request with generic #scenario exception returns correct HTTP Status with #scenario'() { when: 'an exception is thrown by the service' setupTestException(exception, NCMP) def response = performTestRequest(NCMP) then: 'an HTTP response is returned with correct message and details' assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails) where: - scenario | exception || expectedErrorDetails | expectedErrorMessage | expectedErrorCode - 'CPS' | new CpsException(errorMessage, errorDetails) || errorDetails | errorMessage | INTERNAL_SERVER_ERROR - 'NCMP-server' | new ServerNcmpException(errorMessage, errorDetails) || null | errorMessage | INTERNAL_SERVER_ERROR - 'NCMP-client' | new DmiRequestException(errorMessage, errorDetails) || null | errorMessage | BAD_REQUEST - 'DataNode Validation' | new DataNodeNotFoundException(dataNodeNotFoundErrorMessage, errorDetails) || null | dataNodeNotFoundErrorMessage | BAD_REQUEST - 'other' | new IllegalStateException(errorMessage) || null | errorMessage | INTERNAL_SERVER_ERROR + scenario | exception || expectedErrorDetails | expectedErrorMessage | expectedErrorCode + 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || sampleErrorDetails | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'NCMP-client' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | BAD_REQUEST + 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null | 'DataNode not found' | NOT_FOUND + 'other' | new IllegalStateException(sampleErrorMessage) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND } def 'Post request with exception returns correct HTTP Status.'() { given: 'the service throws data validation exception' - def exception = new DataValidationException(errorMessage, errorDetails) + def exception = new DataValidationException(sampleErrorMessage, sampleErrorDetails) setupTestException(exception, NCMPINVENTORY) when: 'the HTTP request is made' def response = performTestRequest(NCMPINVENTORY) then: 'an HTTP response is returned with correct message and details' - assertTestResponse(response, BAD_REQUEST, errorMessage, errorDetails) + assertTestResponse(response, BAD_REQUEST, sampleErrorMessage, sampleErrorDetails) } def setupTestException(exception, apiType) { @@ -130,9 +130,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { static void assertTestResponse(response, expectedStatus , expectedErrorMessage , expectedErrorDetails) { assert response.status == expectedStatus.value() def content = new JsonSlurper().parseText(response.contentAsString) - assert content['status'] == expectedStatus.toString() - assert content['message'] == expectedErrorMessage - assert expectedErrorDetails == null || content['details'] == expectedErrorDetails + assert content['status'].toString().contains(expectedStatus.toString()) + assert content['message'].toString().contains(expectedErrorMessage) + assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails) } enum ApiType { -- cgit 1.2.3-korg