From f232f30bede7d35c71db2d0201695a1416e37323 Mon Sep 17 00:00:00 2001 From: sourabh_sourabh Date: Tue, 9 May 2023 14:34:59 +0100 Subject: [PATCH] NCMP: Update existing Batch endpoint (Moving url param into rest body) - NCMP batch endpoint is updated to accept details into request payload. - Removed unused code of previous impl. Issue-ID: CPS-1635 Signed-off-by: sourabh_sourabh Change-Id: Ic290b750557da06b861c5a4a9bb12debc495ec2e Signed-off-by: sourabh_sourabh --- .../handlers/NetworkCmProxyApiStubDefaultImpl.java | 10 +- cps-ncmp-rest/docs/openapi/components.yaml | 37 +++++ cps-ncmp-rest/docs/openapi/ncmp.yml | 6 +- cps-ncmp-rest/docs/openapi/openapi.yml | 2 +- .../rest/controller/NetworkCmProxyController.java | 43 +++-- .../handlers/NcmpDatastoreRequestHandler.java | 81 +++++---- .../NcmpPassthroughResourceRequestHandler.java | 20 +-- .../handlers/TaskManagementDefaultHandler.java | 14 +- .../NetworkCmProxyRestExceptionHandler.java | 2 +- .../exceptions/OperationNotSupportedException.java | 29 +--- .../mapper/ResourceDataBatchRequestMapper.java | 41 +++++ .../controller/NetworkCmProxyControllerSpec.groovy | 94 ++++++++--- .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 4 + .../cps/ncmp/api/NetworkCmProxyDataService.java | 29 ++-- .../api/impl/NetworkCmProxyDataServiceImpl.java | 26 ++- .../cps/ncmp/api/impl/client/DmiRestClient.java | 8 +- .../impl/exception/InvalidOperationException.java | 32 ++++ .../cps/ncmp/api/impl/operations/CmHandle.java | 42 +++++ .../api/impl/operations/DmiBatchOperation.java | 64 ++++++++ .../api/impl/operations/DmiDataOperations.java | 182 ++++++++++----------- .../api/impl/operations/DmiModelOperations.java | 5 +- .../ncmp/api/impl/operations/DmiRequestBody.java | 7 +- .../ncmp/api/impl/operations/OperationType.java | 71 ++++++++ .../ncmp/api/impl/utils/DmiServiceUrlBuilder.java | 49 +++++- .../impl/utils/ResourceDataBatchRequestUtils.java | 126 ++++++++++++++ .../ncmp/api/models/BatchOperationDefinition.java | 49 ++++++ .../ncmp/api/models/ResourceDataBatchRequest.java | 43 +++++ .../impl/NetworkCmProxyDataServiceImplSpec.groovy | 42 +++-- .../ncmp/api/impl/client/DmiRestClientSpec.groovy | 6 +- .../impl/operations/DmiDataOperationsSpec.groovy | 43 ++--- .../impl/operations/DmiModelOperationsSpec.groovy | 16 +- .../utils/ResourceDataBatchRequestUtilsSpec.groovy | 80 +++++++++ .../utils/RestQueryParametersValidatorSpec.groovy | 14 +- .../test/resources/resourceDataBatchRequest.json | 36 ++++ dmi-plugin-stub/files/batchResponse.json | 4 +- dmi-plugin-stub/mappings/batchCmHandles.json | 2 +- 36 files changed, 1030 insertions(+), 329 deletions(-) rename cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java => cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/OperationNotSupportedException.java (67%) create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy create mode 100644 cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json diff --git a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java index 6e28dbc44..7bd3acbf6 100644 --- a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java +++ b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.rest.stub.handlers; import java.util.List; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; +import org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest; import org.onap.cps.ncmp.rest.model.RestModuleDefinition; import org.onap.cps.ncmp.rest.model.RestModuleReference; import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; @@ -46,12 +47,9 @@ public interface NetworkCmProxyApiStubDefaultImpl extends NetworkCmProxyApi { } @Override - default ResponseEntity getResourceDataForCmHandleBatch(final String resourceIdentifier, - final String topicParamInQuery, - final String datastoreName, - final Object requestBody, - final String optionsParamInQuery, - final Boolean includeDescendants) { + default ResponseEntity getResourceDataForCmHandleBatch(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 7fc1063a1..2781f572f 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -285,6 +285,43 @@ components: properties: state: $ref: '#/components/schemas/CmHandleCompositeState' + # Batch Request Schemas + ResourceDataBatchRequest: + type: object + title: get resource data for given array of operations + properties: + operations: + type: array + items: + type: object + $ref: '#/components/schemas/BatchOperationDefinition' + description: contains batch request details + BatchOperationDefinition: + required: + - operation + - datastore + - operationId + properties: + operation: + type: string + example: 'read' + operationId: + type: string + example: '12' + datastore: + type: string + example: 'ncmp-datastore:passthrough-operational' + options: + type: string + example: '(fields=schemas/schema)' + resourceIdentifier: + type: string + example: 'parent/child' + targetIds: + type: array + items: + type: string + example: [ "da310eecdb8d44c2acc0ddaae01174b1","c748c58f8e0b438f9fd1f28370b17d47" ] examples: dataSampleRequest: diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index 2b70d9489..957a3b873 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -202,17 +202,13 @@ getResourceDataForCmHandleBatch: description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. operationId: getResourceDataForCmHandleBatch parameters: - - $ref: 'components.yaml#/components/parameters/datastoreName' - - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' - $ref: 'components.yaml#/components/parameters/requiredTopicParamInQuery' - - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery' requestBody: required: true content: application/json: schema: - type: object + $ref: 'components.yaml#/components/schemas/ResourceDataBatchRequest' responses: 200: description: OK diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index 5b4c0d349..b63b56823 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -34,7 +34,7 @@ paths: /v1/ch/{cm-handle}/data/ds/{datastore-name}: $ref: 'ncmp.yml#/resourceDataForCmHandle' - /v1/batch/data/ds/{datastore-name}: + /v1/data: $ref: 'ncmp.yml#/getResourceDataForCmHandleBatch' /v1/ch/{cm-handle}/data/ds/{datastore-name}/query: 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 fca1d6310..1b78fa034 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 @@ -23,10 +23,12 @@ package org.onap.cps.ncmp.rest.controller; -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE; -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE; -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH; -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE; +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL; +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE; import java.util.Collection; import java.util.List; @@ -44,8 +46,10 @@ import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandl import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreRequestHandler; import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler; import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper; +import org.onap.cps.ncmp.rest.mapper.ResourceDataBatchRequestMapper; import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; +import org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest; import org.onap.cps.ncmp.rest.model.RestModuleDefinition; import org.onap.cps.ncmp.rest.model.RestModuleReference; import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; @@ -72,6 +76,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { private final CmHandleStateMapper cmHandleStateMapper; private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler; private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler; + private final ResourceDataBatchRequestMapper resourceDataBatchRequestMapper; /** * Get resource data from datastore. @@ -100,19 +105,11 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } @Override - public ResponseEntity getResourceDataForCmHandleBatch(final String resourceIdentifier, - final String topicParamInQuery, - final String datastoreName, - final Object requestBody, - final String optionsParamInQuery, - final Boolean includeDescendants) { - - final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = getNcmpDatastoreRequestHandler(datastoreName); - - final List cmHandleIds = jsonObjectMapper.convertJsonString(jsonObjectMapper.asJsonString(requestBody), - List.class); - return ncmpDatastoreRequestHandler.executeRequest(datastoreName, cmHandleIds, resourceIdentifier, - optionsParamInQuery, topicParamInQuery, includeDescendants); + public ResponseEntity getResourceDataForCmHandleBatch(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest) { + return ncmpPassthroughResourceRequestHandler.executeRequest(topicParamInQuery, + resourceDataBatchRequestMapper.toResourceDataBatchRequest(resourceDataBatchRequest)); } /** @@ -134,7 +131,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String optionsParamInQuery, final String topicParamInQuery, final Boolean includeDescendants) { - validateDataStore(DatastoreType.OPERATIONAL, datastoreName); + validateDataStore(OPERATIONAL, datastoreName); return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants); } @@ -156,7 +153,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final Object requestBody, final String contentType) { - validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName); + validateDataStore(PASSTHROUGH_RUNNING, datastoreName); final Object responseObject = networkCmProxyDataService .writeResourceDataPassThroughRunningForCmHandle( @@ -182,7 +179,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final Object requestBody, final String contentType) { - validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName); + validateDataStore(PASSTHROUGH_RUNNING, datastoreName); networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType); @@ -206,7 +203,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String cmHandle, final Object requestBody, final String contentType) { - validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName); + validateDataStore(PASSTHROUGH_RUNNING, datastoreName); networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType); @@ -228,7 +225,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String resourceIdentifier, final String contentType) { - validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName); + validateDataStore(PASSTHROUGH_RUNNING, datastoreName); networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, DELETE, NO_BODY, contentType); @@ -381,7 +378,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } private NcmpDatastoreRequestHandler getNcmpDatastoreRequestHandler(final String datastoreName) { - if (DatastoreType.OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) { + if (OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) { return ncmpCachedResourceRequestHandler; } return ncmpPassthroughResourceRequestHandler; 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 a32c462e7..a8ca13a75 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 @@ -20,12 +20,19 @@ package org.onap.cps.ncmp.rest.controller.handlers; -import java.util.List; +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ; + import java.util.Map; import java.util.UUID; import java.util.function.Supplier; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +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.ResourceDataBatchRequest; +import org.onap.cps.ncmp.rest.exceptions.OperationNotSupportedException; import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; import org.onap.cps.ncmp.rest.util.TopicValidator; import org.springframework.beans.factory.annotation.Value; @@ -67,7 +74,7 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler final boolean asyncResponseRequested = topicParamInQuery != null; if (asyncResponseRequested && notificationFeatureEnabled) { return executeAsyncTaskAndGetResponseEntity(datastoreName, cmHandleId, resourceIdentifier, - optionsParamInQuery, topicParamInQuery, includeDescendants, false); + optionsParamInQuery, topicParamInQuery, includeDescendants); } if (asyncResponseRequested) { @@ -98,26 +105,21 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler } /** - * Executes synchronous/asynchronous request for batch of cm handles. + * Executes asynchronous request for batch of cm handles to resource data. * - * @param datastoreName the name of the datastore - * @param cmHandleIds list of cm handles - * @param resourceIdentifier the resource identifier - * @param optionsParamInQuery the options param in query - * @param topicParamInQuery the topic param in query - * @param includeDescendants whether to include descendants or not + * @param topicParamInQuery the topic param in query + * @param resourceDataBatchRequest batch request details for resource data * @return the response entity */ - public ResponseEntity executeRequest(final String datastoreName, - final List cmHandleIds, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final boolean includeDescendants) { - - return executeAsyncTaskAndGetResponseEntity(datastoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery, - topicParamInQuery, includeDescendants, true); - + public ResponseEntity executeRequest(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest) { + validateBatchRequest(topicParamInQuery, resourceDataBatchRequest); + if (!notificationFeatureEnabled) { + return ResponseEntity.ok(Map.of("status", + "Asynchronous request is unavailable as notification feature is currently disabled.")); + } + return getRequestIdAndSendBatchRequestToDmiService(topicParamInQuery, resourceDataBatchRequest); } protected ResponseEntity executeTaskAsync(final String topicParamInQuery, @@ -127,7 +129,6 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler TopicValidator.validateTopicName(topicParamInQuery); log.debug("Received Async request with id {}", requestId); cpsNcmpTaskExecutor.executeTask(taskSupplier, timeOutInMilliSeconds); - return ResponseEntity.ok(Map.of("requestId", requestId)); } @@ -136,25 +137,43 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler } private ResponseEntity executeAsyncTaskAndGetResponseEntity(final String datastoreName, - final Object targetObject, + final String cmHandleId, final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery, - final boolean includeDescendants, - final boolean isBulkRequest) { + final boolean includeDescendants) { final String requestId = UUID.randomUUID().toString(); - final Supplier taskSupplier; - if (isBulkRequest) { - taskSupplier = getTaskSupplierForBulkRequest(datastoreName, (List) targetObject, - resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants); - } else { - taskSupplier = getTaskSupplierForGetRequest(datastoreName, targetObject.toString(), resourceIdentifier, - optionsParamInQuery, topicParamInQuery, requestId, includeDescendants); - } + final Supplier taskSupplier = getTaskSupplierForGetRequest(datastoreName, cmHandleId, + resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants); if (taskSupplier == NO_OBJECT_SUPPLIER) { return new ResponseEntity<>(Map.of("status", "Unable to execute request as " + "datastore is not implemented."), HttpStatus.NOT_IMPLEMENTED); } return executeTaskAsync(topicParamInQuery, requestId, taskSupplier); } + + private ResponseEntity getRequestIdAndSendBatchRequestToDmiService(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest) { + final String requestId = UUID.randomUUID().toString(); + sendResourceDataBatchRequestAsynchronously(topicParamInQuery, resourceDataBatchRequest, requestId); + return ResponseEntity.ok(Map.of("requestId", requestId)); + } + + private void validateBatchRequest(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest) { + TopicValidator.validateTopicName(topicParamInQuery); + resourceDataBatchRequest.getBatchOperationDefinitions().forEach(batchOperationDetail -> { + if (OperationType.fromOperationName(batchOperationDetail.getOperation()) != READ) { + throw new OperationNotSupportedException( + batchOperationDetail.getOperation() + " operation not yet supported for target ids :" + + batchOperationDetail.getCmHandleIds()); + } else if (DatastoreType.fromDatastoreName(batchOperationDetail.getDatastore()) == OPERATIONAL) { + throw new InvalidDatastoreException(batchOperationDetail.getDatastore() + + " datastore is not supported for target ids : " + + batchOperationDetail.getCmHandleIds()); + } + }); + } } 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 18e5a9f5a..5c35818a3 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 @@ -20,10 +20,11 @@ package org.onap.cps.ncmp.rest.controller.handlers; -import java.util.List; import java.util.function.Supplier; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @@ -56,17 +57,14 @@ public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestH datastoreName, cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); } + @Async @Override - public Supplier getTaskSupplierForBulkRequest(final String datastoreName, - final List cmHandleIds, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final boolean includeDescendants) { + public void sendResourceDataBatchRequestAsynchronously(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest, + final String requestId) { + networkCmProxyDataService.requestResourceDataForCmHandleBatch(topicParamInQuery, resourceDataBatchRequest, + requestId); - return () -> networkCmProxyDataService.getResourceDataForCmHandleBatch( - datastoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); } - } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java index 6d68f7680..937935bec 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java @@ -20,8 +20,8 @@ package org.onap.cps.ncmp.rest.controller.handlers; -import java.util.List; import java.util.function.Supplier; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; import org.onap.cps.spi.FetchDescendantsOption; public interface TaskManagementDefaultHandler { @@ -46,14 +46,10 @@ public interface TaskManagementDefaultHandler { return NO_OBJECT_SUPPLIER; } - default Supplier getTaskSupplierForBulkRequest(final String datastoreName, - final List cmHandleIds, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final boolean includeDescendant) { - return NO_OBJECT_SUPPLIER; + default void sendResourceDataBatchRequestAsynchronously(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest, + final String requestId) { } static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) { 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 5faeee69f..f459acec2 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 @@ -77,7 +77,7 @@ public class NetworkCmProxyRestExceptionHandler { return wrapDmiErrorResponse(HttpStatus.BAD_GATEWAY, httpClientRequestException); } - @ExceptionHandler({DmiRequestException.class, DataValidationException.class, + @ExceptionHandler({DmiRequestException.class, DataValidationException.class, OperationNotSupportedException.class, HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class}) public static ResponseEntity handleDmiRequestExceptions(final Exception exception) { return buildErrorResponse(HttpStatus.BAD_REQUEST, exception); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/OperationNotSupportedException.java similarity index 67% rename from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java rename to cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/OperationNotSupportedException.java index 796cef23d..e1daf3df6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/OperationNotSupportedException.java @@ -18,26 +18,15 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.operations; +package org.onap.cps.ncmp.rest.exceptions; -import com.fasterxml.jackson.annotation.JsonValue; - -public enum OperationEnum { - - READ("read"), - CREATE("create"), - UPDATE("update"), - PATCH("patch"), - DELETE("delete"); - private final String value; - - OperationEnum(final String value) { - this.value = value; - } - - @Override - @JsonValue - public String toString() { - return String.valueOf(value); +public class OperationNotSupportedException extends RuntimeException { + /** + * Instantiates a new not implemented operation exception. + * + * @param message the message + */ + public OperationNotSupportedException(final String message) { + super(message); } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.java new file mode 100644 index 000000000..d045e3161 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.java @@ -0,0 +1,41 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.onap.cps.ncmp.api.models.BatchOperationDefinition; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface ResourceDataBatchRequestMapper { + + @Mapping(source = "operations", target = "batchOperationDefinitions") + ResourceDataBatchRequest toResourceDataBatchRequest( + org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest resourceDataBatchRequest); + + @Mapping(source = "targetIds", target = "cmHandleIds") + BatchOperationDefinition toBatchOperationDefinition( + org.onap.cps.ncmp.rest.model.BatchOperationDefinition batchOperationDefinition); +} 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 fb411c054..31e83aa7a 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 @@ -32,11 +32,14 @@ import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import org.onap.cps.ncmp.api.inventory.LockReasonCategory +import org.onap.cps.ncmp.rest.model.BatchOperationDefinition import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle 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 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.mapper.ResourceDataBatchRequestMapper +import org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest import org.onap.cps.ncmp.rest.util.DeprecationHelper import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.ModuleDefinition @@ -62,10 +65,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH +import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL @@ -97,6 +100,9 @@ class NetworkCmProxyControllerSpec extends Specification { @SpringBean CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper) + @SpringBean + ResourceDataBatchRequestMapper resourceDataBatchRequestMapper = Mappers.getMapper(ResourceDataBatchRequestMapper) + @SpringBean CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy() @@ -113,7 +119,6 @@ class NetworkCmProxyControllerSpec extends Specification { def ncmpBasePathV1 def requestBody = '{"some-key":"some-value"}' - def bulkRequestBody = '["testCmHandle"]' def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) @@ -200,44 +205,69 @@ class NetworkCmProxyControllerSpec extends Specification { 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#' } - def 'Get (async) bulk resource data from dmi service.'() { - given: 'bulk resource data url' - def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastore.datastoreName}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic" + def 'Get (async) batch resource data from dmi service.'() { + given: 'batch resource data url' + def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name" + def resourceDataBatchRequestJsonData = jsonObjectMapper.asJsonString( + getResourceDataBatchRequest("read", datastore.datastoreName)) + def expectedDmiResourceDataBatchRequest + = jsonObjectMapper.convertJsonString(resourceDataBatchRequestJsonData, org.onap.cps.ncmp.api.models.ResourceDataBatchRequest.class) when: 'post data resource request is performed' def response = mvc.perform( post(getUrl) .contentType(MediaType.APPLICATION_JSON) - .content(bulkRequestBody) + .content(resourceDataBatchRequestJsonData) ).andReturn().response then: 'response status is Ok' response.status == HttpStatus.OK.value() and: 'async request id is generated' assert response.contentAsString.contains("requestId") then: 'wait a little to allow execution of service method by task executor (on separate thread)' - Thread.sleep(100); + Thread.sleep(100) then: 'the service has been invoked with the correct parameters ' - 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastore.datastoreName, ['testCmHandle'], - 'parent/child', - '(a=1,b=2)', - 'myTopic', - _) + 1 * mockNetworkCmProxyDataService.requestResourceDataForCmHandleBatch('my-topic-name', expectedDmiResourceDataBatchRequest, _) where: 'the following data stores are used' datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL] } - def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() { - given: 'bulk resource data url' - def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic" + def 'Get batch resource data for #scenario from dmi service.'() { + given: 'batch resource data url' + def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name" + def resourceDataBatchRequestJsonData = jsonObjectMapper.asJsonString( + getResourceDataBatchRequest(operation, datastore)) + when: 'post data resource request is performed' + def response = mvc.perform( + post(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .content(resourceDataBatchRequestJsonData) + ).andReturn().response + then: 'response status is BAD_REQUEST' + response.status == HttpStatus.BAD_REQUEST.value() + where: 'the following parameters are used' + scenario | datastore | operation + 'non-supported datastoreName' | OPERATIONAL.datastoreName | 'read' + 'non-supported operation (passthrough-running)' | PASSTHROUGH_RUNNING.datastoreName | 'create' + 'non-supported operation (passthrough-operational)' | PASSTHROUGH_OPERATIONAL.datastoreName | 'create' + } + + def 'Get batch resource data when notification feature is disabled for datastore: #datastore.'() { + given: 'batch resource data url' + def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name" + def resourceDataBatchRequestJsonData = jsonObjectMapper.asJsonString( + getResourceDataBatchRequest("read", datastore.datastoreName)) + ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false when: 'post data resource request is performed' def response = mvc.perform( post(getUrl) .contentType(MediaType.APPLICATION_JSON) - .content(bulkRequestBody) + .content(resourceDataBatchRequestJsonData) ).andReturn().response - then: 'response status code is 501 not implemented' - response.status == HttpStatus.NOT_IMPLEMENTED.value() + then: 'response status is Ok' + 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."}' + where: 'the following data stores are used' + datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL] } def 'Query Resource Data from operational.'() { @@ -656,5 +686,23 @@ class NetworkCmProxyControllerSpec extends Specification { return assertContainsAll(response, expectedContent) } + def getResourceDataBatchRequest(operation, datastore) { + def resourceDataBatchRequest = new ResourceDataBatchRequest() + def batchOperationDefinitions = new ArrayList() + batchOperationDefinitions.add(getBatchOperationDefinition(operation, datastore)) + resourceDataBatchRequest.addOperationsItem(batchOperationDefinitions) + } + + def getBatchOperationDefinition(operation, datastore) { + def batchOperationDefinition = new BatchOperationDefinition() + batchOperationDefinition.setOperation(operation) + batchOperationDefinition.setOperationId("operational-12") + batchOperationDefinition.setDatastore(datastore) + batchOperationDefinition.setOptions("some option") + batchOperationDefinition.setResourceIdentifier("some resource identifier") + batchOperationDefinition.addTargetIdsItem("some-cm-handle") + return batchOperationDefinition + } + } 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 f44d6c990..a3afc5546 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 @@ -33,6 +33,7 @@ import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandl import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.mapper.ResourceDataBatchRequestMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch @@ -74,6 +75,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper) + @SpringBean + ResourceDataBatchRequestMapper resourceDataBatchRequestMapper = Mappers.getMapper(ResourceDataBatchRequestMapper) + @SpringBean CpsNcmpTaskExecutor stubbedCpsTaskExecutor = Stub() 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 05490d8fc..046c78879 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 @@ -24,15 +24,15 @@ package org.onap.cps.ncmp.api; import java.util.Collection; -import java.util.List; import java.util.Map; -import org.onap.cps.ncmp.api.impl.operations.OperationEnum; +import org.onap.cps.ncmp.api.impl.operations.OperationType; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; @@ -83,22 +83,15 @@ public interface NetworkCmProxyDataService { FetchDescendantsOption fetchDescendantsOption); /** - * Get resource data for given batch of cm handles using dmi. + * Get resource data for batch of cm handles using dmi. * - * @param datastoreName datastore name - * @param cmHandleIds cm handle identifiers - * @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 + * @param topicParamInQuery topic name for (triggering) async responses + * @param resourceDataBatchRequest cm handle identifiers */ - Object getResourceDataForCmHandleBatch(String datastoreName, - List cmHandleIds, - String resourceIdentifier, - String optionsParamInQuery, - String topicParamInQuery, - String requestId); + void requestResourceDataForCmHandleBatch(String topicParamInQuery, + ResourceDataBatchRequest + resourceDataBatchRequest, + String requestId); /** @@ -106,14 +99,14 @@ public interface NetworkCmProxyDataService { * * @param cmHandleId cm handle identifier * @param resourceIdentifier resource identifier - * @param operation required operation + * @param operationType required operation type * @param requestBody request body to create resource * @param contentType content type in body * @return {@code Object} return data */ Object writeResourceDataPassThroughRunningForCmHandle(String cmHandleId, String resourceIdentifier, - OperationEnum operation, + OperationType operationType, String requestBody, String contentType); 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 e478b0053..536775ec5 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 @@ -45,7 +45,7 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler; import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; -import org.onap.cps.ncmp.api.impl.operations.OperationEnum; +import org.onap.cps.ncmp.api.impl.operations.OperationType; import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions; import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; @@ -63,6 +63,7 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.CpsException; @@ -138,28 +139,21 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } @Override - public Object getResourceDataForCmHandleBatch(final String datastoreName, - final List cmHandleIds, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId) { - final ResponseEntity responseEntity = dmiDataOperations.getResourceDataFromDmi(datastoreName, cmHandleIds, - resourceIdentifier, - optionsParamInQuery, - topicParamInQuery, - requestId); - return responseEntity.getBody(); + public void requestResourceDataForCmHandleBatch(final String topicParamInQuery, + final ResourceDataBatchRequest + resourceDataBatchRequest, + final String requestId) { + dmiDataOperations.requestResourceDataFromDmi(topicParamInQuery, resourceDataBatchRequest, requestId); } @Override public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleId, final String resourceIdentifier, - final OperationEnum operation, + final OperationType operationType, final String requestData, final String dataType) { - return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, operation, - requestData, dataType); + return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, + operationType, requestData, dataType); } @Override diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java index 9d087806c..136935ba5 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java @@ -24,7 +24,7 @@ package org.onap.cps.ncmp.api.impl.client; import lombok.AllArgsConstructor; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; -import org.onap.cps.ncmp.api.impl.operations.OperationEnum; +import org.onap.cps.ncmp.api.impl.operations.OperationType; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -44,17 +44,17 @@ public class DmiRestClient { * Sends POST operation to DMI with json body containing module references. * @param dmiResourceUrl dmi resource url * @param requestBodyAsJsonString json data body - * @param operation the type of operation being executed (for error reporting only) + * @param operationType the type of operation being executed (for error reporting only) * @return response entity of type String */ public ResponseEntity postOperationWithJsonData(final String dmiResourceUrl, final String requestBodyAsJsonString, - final OperationEnum operation) { + final OperationType operationType) { final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders())); try { return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class); } catch (final HttpStatusCodeException httpStatusCodeException) { - final String exceptionMessage = "Unable to " + operation.toString() + " resource data."; + final String exceptionMessage = "Unable to " + operationType.toString() + " resource data."; throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(), httpStatusCodeException.getRawStatusCode()); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java new file mode 100644 index 000000000..17069098c --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java @@ -0,0 +1,32 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl.exception; + +public class InvalidOperationException extends RuntimeException { + /** + * Instantiates a new invalid operation exception. + * + * @param message the message + */ + public InvalidOperationException(final String message) { + super(message); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java new file mode 100644 index 000000000..618da7454 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java @@ -0,0 +1,42 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl.operations; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import lombok.Builder; +import lombok.Getter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Getter +@Builder +public class CmHandle { + private String id; + + @JsonProperty("cmHandleProperties") + private Map dmiProperties; + + public static CmHandle buildCmHandleWithProperties(final String cmHandleId, + final Map dmiProperties) { + return CmHandle.builder().id(cmHandleId).dmiProperties(dmiProperties).build(); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java new file mode 100644 index 000000000..76ad0f7b2 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl.operations; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import org.onap.cps.ncmp.api.models.BatchOperationDefinition; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Getter +@Builder +@JsonPropertyOrder({"operation", "operationId", "datastore", "options", "resourceIdentifier", "cmHandles"}) +public class DmiBatchOperation { + + @JsonProperty("operation") + private OperationType operationType; + private String operationId; + private String datastore; + private String options; + private String resourceIdentifier; + + private final List cmHandles = new ArrayList<>(); + + /** + * Create and initialise a (outgoing) DMI batch operation. + * + * @param batchOperationDefinition batchOperationDefinition definition of incoming of batch request + * @return mapped dmi operation details + */ + public static DmiBatchOperation buildDmiBatchRequestBodyWithoutCmHandles( + final BatchOperationDefinition batchOperationDefinition) { + + return DmiBatchOperation.builder() + .operationType(OperationType.fromOperationName(batchOperationDefinition.getOperation())) + .operationId(batchOperationDefinition.getOperationId()) + .datastore(DatastoreType.fromDatastoreName(batchOperationDefinition.getDatastore()).getDatastoreName()) + .options(batchOperationDefinition.getOptions()) + .resourceIdentifier(batchOperationDefinition.getResourceIdentifier()) + .build(); + } +} 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 1a3952306..3e8d40a83 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 @@ -22,25 +22,28 @@ package org.onap.cps.ncmp.api.impl.operations; import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ; +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.impl.executor.TaskExecutor; -import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; +import org.onap.cps.ncmp.api.impl.utils.ResourceDataBatchRequestUtils; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; /** * Operations class for DMI data. @@ -50,7 +53,6 @@ import org.springframework.stereotype.Component; public class DmiDataOperations extends DmiOperations { private static final long DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS = 30000L; - private static final String NO_CM_HANDLE_ID = ""; public DmiDataOperations(final InventoryPersistence inventoryPersistence, final JsonObjectMapper jsonObjectMapper, @@ -88,40 +90,13 @@ public class DmiDataOperations extends DmiOperations { return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ); } - /** - * This method fetches the resource data by data store for given list of cm handles using dmi client. - * - * @param dataStoreName data store name - * @param cmHandleIds list of cm handles - * @param resourceId resource identifier - * @param optionsParamInQuery options query - * @param topicParamInQuery topic name for (triggering) async responses - * @param requestId requestId for async responses - * @return {@code ResponseEntity} response entity - */ - public ResponseEntity getResourceDataFromDmi(final String dataStoreName, - final List cmHandleIds, - final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId) { - final Collection yangModelCmHandles - = inventoryPersistence.getYangModelCmHandles(cmHandleIds); - final Map>> dmiServiceNameCmHandlePropertiesMap = - DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles); - - buildBulkResourceDataRequestAndSend(dataStoreName, resourceId, optionsParamInQuery, - topicParamInQuery, requestId, dmiServiceNameCmHandlePropertiesMap); - return new ResponseEntity<>(HttpStatus.ACCEPTED); - } - /** * This method fetches all the resource data from operational data store for given cm handle * identifier using dmi client. * - * @param dataStoreName data store name - * @param cmHandleId network resource identifier - * @param requestId requestId for async responses + * @param dataStoreName data store name + * @param cmHandleId network resource identifier + * @param requestId requestId for async responses * @return {@code ResponseEntity} response entity */ public ResponseEntity getResourceDataFromDmi(final String dataStoreName, @@ -130,12 +105,37 @@ public class DmiDataOperations extends DmiOperations { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); - final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/", null, - null, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); + final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/", + null, null, + yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, - READ); + return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ); + } + + /** + * This method requests the resource data by data store for given list of cm handles using dmi client. + * The data wil be returned as message on the topic specified. + * + * @param topicParamInQuery topic name for (triggering) async responses + * @param resourceDataBatchRequest batch request for resource data + * @param requestId requestId for as a response + */ + public void requestResourceDataFromDmi(final String topicParamInQuery, + final ResourceDataBatchRequest resourceDataBatchRequest, + final String requestId) { + + final Set cmHandlesIds + = getDistinctCmHandleIdsFromBatchRequest(resourceDataBatchRequest); + + final Collection yangModelCmHandles + = getYangModelCmHandlesInReadyState(cmHandlesIds); + + final Map> operationsOutPerDmiServiceName + = ResourceDataBatchRequestUtils.processPerOperationInBatchRequest(resourceDataBatchRequest, + yangModelCmHandles); + + buildBatchRequestUrlAndSendToDmiService(topicParamInQuery, requestId, operationsOutPerDmiServiceName); } /** @@ -143,36 +143,39 @@ public class DmiDataOperations extends DmiOperations { * identifier on given resource using dmi client. * * @param cmHandleId network resource identifier - * @param resourceId resource identifier - * @param operation operation enum - * @param requestData the request data - * @param dataType data type + * @param resourceId resource identifier + * @param operationType operation enum + * @param requestData the request data + * @param dataType data type * @return {@code ResponseEntity} response entity */ public ResponseEntity writeResourceDataPassThroughRunningFromDmi(final String cmHandleId, final String resourceId, - final OperationEnum operation, + final OperationType operationType, final String requestData, final String dataType) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonRequestBody = getDmiRequestBody(operation, null, requestData, dataType, + final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType, yangModelCmHandle); final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId, null, null, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operation); + return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operationType); } private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) { return inventoryPersistence.getYangModelCmHandle(cmHandleId); } - private String getDmiRequestBody(final OperationEnum operation, final String requestId, final String requestData, - final String dataType, final YangModelCmHandle yangModelCmHandle) { + private String getDmiRequestBody(final OperationType operationType, + final String requestId, + final String requestData, + final String dataType, + final YangModelCmHandle yangModelCmHandle) { final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() - .operation(operation) + .operationType(operationType) .requestId(requestId) .data(requestData) .dataType(dataType) @@ -181,17 +184,6 @@ public class DmiDataOperations extends DmiOperations { return jsonObjectMapper.asJsonString(dmiRequestBody); } - private String getDmiBulkRequestBody(final OperationEnum operation, - final String requestId, - final String requestData) { - final DmiRequestBody dmiBulkRequestBody = DmiRequestBody.builder() - .operation(operation) - .requestId(requestId) - .data(requestData) - .build(); - return jsonObjectMapper.asJsonString(dmiBulkRequestBody); - } - private String getDmiRequestUrl(final String dataStoreName, final String cmHandleId, final String resourceId, @@ -204,15 +196,13 @@ public class DmiDataOperations extends DmiOperations { cmHandleId)); } - private String getDmiServiceBulkRequestUrl(final String dataStoreName, - final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery, - final String dmiServiceName) { - return dmiServiceUrlBuilder.getBulkRequestUrl( - dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, - topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName, - NO_CM_HANDLE_ID)); + private String getDmiServiceBatchRequestUrl(final String dmiServiceName, + final String topicParamInQuery, + final String requestId) { + final MultiValueMap batchRequestQueryParams = dmiServiceUrlBuilder + .getBatchRequestQueryParams(topicParamInQuery, requestId); + return dmiServiceUrlBuilder.getBatchRequestUrl(batchRequestQueryParams, + dmiServiceUrlBuilder.populateBatchUriVariables(dmiServiceName)); } private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, @@ -224,31 +214,41 @@ public class DmiDataOperations extends DmiOperations { } } - private void buildBulkResourceDataRequestAndSend(final String dataStoreName, - final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final Map>> - dmiServiceNameCmHandlePropertiesMap) { - dmiServiceNameCmHandlePropertiesMap.entrySet().parallelStream().forEach( - dmiServiceNameCmHandlePropertiesEntry -> { - final String dmiBulkResourceDataUrl = getDmiServiceBulkRequestUrl(dataStoreName, resourceId, - optionsParamInQuery, topicParamInQuery, dmiServiceNameCmHandlePropertiesEntry.getKey()); - final String jsonRequestBodyAsJsonString = - jsonObjectMapper.asJsonString(dmiServiceNameCmHandlePropertiesEntry.getValue()); - final String jsonRequestBody - = getDmiBulkRequestBody(READ, requestId, jsonRequestBodyAsJsonString); - sendDmiResourceDataRequestToDmiService(dmiBulkResourceDataUrl, jsonRequestBody); - }); + private static Set getDistinctCmHandleIdsFromBatchRequest(final ResourceDataBatchRequest + resourceDataBatchRequest) { + return resourceDataBatchRequest.getBatchOperationDefinitions().stream() + .flatMap(batchOperationDefinition -> + batchOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet()); + } + + private Collection getYangModelCmHandlesInReadyState(final Set requestedCmHandleIds) { + // TODO Need to publish an error response to client given topic. + // Code should be implemented into https://jira.onap.org/browse/CPS-1614 ( + // NCMP : Error handling for non-ready cm handle state) + return inventoryPersistence.getYangModelCmHandles(requestedCmHandleIds).stream() + .filter(yangModelCmHandle -> yangModelCmHandle.getCompositeState().getCmHandleState() + == CmHandleState.READY).collect(Collectors.toList()); + } + + private void buildBatchRequestUrlAndSendToDmiService(final String topicParamInQuery, + final String requestId, + final Map> + groupsOutPerDmiServiceName) { + + groupsOutPerDmiServiceName.entrySet().forEach(groupsOutPerDmiServiceNameEntry -> { + final String dmiServiceName = groupsOutPerDmiServiceNameEntry.getKey(); + final List dmiBatchRequestBodies = groupsOutPerDmiServiceNameEntry.getValue(); + final String dmiBatchResourceDataUrl = getDmiServiceBatchRequestUrl(dmiServiceName, topicParamInQuery, + requestId); + sendBatchRequestToDmiService(dmiBatchResourceDataUrl, dmiBatchRequestBodies); + }); } - private void sendDmiResourceDataRequestToDmiService(final String dmiBulkResourceDataUrl, - final String dmiResourceDataRequestAsJsonString) { - TaskExecutor.executeTask(() -> - dmiRestClient.postOperationWithJsonData(dmiBulkResourceDataUrl, - dmiResourceDataRequestAsJsonString, READ), - DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS) + private void sendBatchRequestToDmiService(final String batchResourceDataUrl, + final List dmiBatchRequestBodies) { + final String batchRequestBodiesAsJsonString = jsonObjectMapper.asJsonString(dmiBatchRequestBodies); + TaskExecutor.executeTask(() -> dmiRestClient.postOperationWithJsonData(batchResourceDataUrl, + batchRequestBodiesAsJsonString, READ), DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS) .whenCompleteAsync(this::handleTaskCompletion); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java index 392e9c1a2..1bbd72564 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java @@ -66,8 +66,7 @@ public class DmiModelOperations extends DmiOperations { * @return module references */ public List getModuleReferences(final YangModelCmHandle yangModelCmHandle) { - final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() - .build(); + final DmiRequestBody dmiRequestBody = DmiRequestBody.builder().build(); dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); final ResponseEntity dmiFetchModulesResponseEntity = getResourceFromDmiWithJsonData( yangModelCmHandle.resolveDmiServiceName(MODEL), @@ -109,7 +108,7 @@ public class DmiModelOperations extends DmiOperations { final String resourceName) { final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName); return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, - OperationEnum.READ); + OperationType.READ); } private static String getRequestBodyToFetchYangResources(final Collection newModuleReferences, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java index 3aa636615..6613d3c87 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl.operations; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -32,9 +33,11 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; @JsonInclude(JsonInclude.Include.NON_NULL) @Getter @Builder +@JsonPropertyOrder({"operation", "dataType", "data", "cmHandleProperties", "requestId"}) public class DmiRequestBody { - private OperationEnum operation; + @JsonProperty("operation") + private OperationType operationType; private String dataType; private String data; @JsonProperty("cmHandleProperties") diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java new file mode 100644 index 000000000..fa00d1a15 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java @@ -0,0 +1,71 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl.operations; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException; + +@Getter +public enum OperationType { + + READ("read"), + CREATE("create"), + UPDATE("update"), + PATCH("patch"), + DELETE("delete"); + + private final String operationName; + + OperationType(final String operationName) { + this.operationName = operationName; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(operationName); + } + + private static final Map operationNameToOperationEnum = new HashMap<>(); + + static { + Arrays.stream(OperationType.values()).forEach( + operationType -> operationNameToOperationEnum.put(operationType.getOperationName(), operationType)); + } + + /** + * From operation name get operation enum type. + * + * @param operationName the operation name + * @return the operation enum type + */ + public static OperationType fromOperationName(final String operationName) { + final OperationType operationType = operationNameToOperationEnum.get(operationName); + if (null == operationType) { + throw new InvalidOperationException(operationName + " is an invalid operation name"); + } + return operationType; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java index bba8f48fb..5c6fa9f0b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -53,15 +53,17 @@ public class DmiServiceUrlBuilder { } /** - * This method creates the dmi service url for bulk request. + * This method builds batch request url. * - * @param queryParams query param map as key,value pair - * @param uriVariables uri param map as key (placeholder),value pair - * @return {@code String} dmi service url as string + * @param batchRequestQueryParams query param map as key, value pair + * @param batchRequestUriVariables uri param map as key (placeholder), value pair + * @return {@code String} batch request url as string */ - public String getBulkRequestUrl(final MultiValueMap queryParams, - final Map uriVariables) { - return getUriComponentsBuilder(getBulkResourceDataBasePathUriBuilder(), queryParams, uriVariables) + public String getBatchRequestUrl(final MultiValueMap batchRequestQueryParams, + final Map batchRequestUriVariables) { + return getBatchResourceDataBasePathUriBuilder() + .queryParams(batchRequestQueryParams) + .uriVariables(batchRequestUriVariables) .buildAndExpand().toUriString(); } @@ -84,12 +86,12 @@ public class DmiServiceUrlBuilder { * * @return {@code UriComponentsBuilder} dmi service url builder object */ - public UriComponentsBuilder getBulkResourceDataBasePathUriBuilder() { + public UriComponentsBuilder getBatchResourceDataBasePathUriBuilder() { return UriComponentsBuilder.newInstance() .path("{dmiServiceName}") .pathSegment("{dmiBasePath}") .pathSegment("v1") - .pathSegment("batch"); + .pathSegment("data"); } /** @@ -113,6 +115,20 @@ public class DmiServiceUrlBuilder { return uriVariables; } + /** + * This method populates uri variables for batch request. + * + * @param dmiServiceName dmi service name + * @return {@code Map} uri variables as map + */ + public Map populateBatchUriVariables(final String dmiServiceName) { + final Map uriVariables = new HashMap<>(); + final String dmiBasePath = dmiProperties.getDmiBasePath(); + uriVariables.put("dmiServiceName", dmiServiceName); + uriVariables.put("dmiBasePath", dmiBasePath); + return uriVariables; + } + /** * This method is used to populate map from query params. * @@ -134,6 +150,21 @@ public class DmiServiceUrlBuilder { return queryParams; } + /** + * This method is used to populate map from query params for batch request. + * + * @param topicParamInQuery topic into url param + * @param requestId unique id of response for valid topic + * @return all valid query params as map + */ + public MultiValueMap getBatchRequestQueryParams(final String topicParamInQuery, + final String requestId) { + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); + getQueryParamConsumer().accept("requestId", requestId, queryParams); + return queryParams; + } + private TriConsumer> getQueryParamConsumer() { return (paramName, paramValue, paramMap) -> { if (Strings.isNotEmpty(paramValue)) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java new file mode 100644 index 000000000..e4c9bfb39 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java @@ -0,0 +1,126 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl.utils; + +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 lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.impl.operations.CmHandle; +import org.onap.cps.ncmp.api.impl.operations.DmiBatchOperation; +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.models.BatchOperationDefinition; +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ResourceDataBatchRequestUtils { + + private static final String UNKNOWN_SERVICE_NAME = null; + + /** + * Create a list of DMI batch operation per DMI service (name). + * + * @param resourceDataBatchRequestIn incoming batch request details for resource data + * @param yangModelCmHandles involved cm handles represented as YangModelCmHandle (incl. metadata) + * + * @return {@code Map>} Create a list of DMI batch operation per DMI service (name). + */ + public static Map> processPerOperationInBatchRequest( + final ResourceDataBatchRequest resourceDataBatchRequestIn, + final Collection yangModelCmHandles) { + + final Map>> dmiPropertiesPerCmHandleIdPerServiceName = + DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles); + + final Map dmiServiceNamesPerCmHandleId = + getDmiServiceNamesPerCmHandleId(dmiPropertiesPerCmHandleIdPerServiceName); + + final Map> dmiBatchOperationsOutPerDmiServiceName = new HashMap<>(); + + for (final BatchOperationDefinition batchOperationDefinitionIn : + resourceDataBatchRequestIn.getBatchOperationDefinitions()) { + for (final String cmHandleId : batchOperationDefinitionIn.getCmHandleIds()) { + final String dmiServiceName = dmiServiceNamesPerCmHandleId.get(cmHandleId); + final Map cmHandleIdProperties + = dmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).get(cmHandleId); + if (cmHandleIdProperties == null) { + publishErrorMessageToClientTopic(cmHandleId); + } else { + final DmiBatchOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName, + batchOperationDefinitionIn, dmiBatchOperationsOutPerDmiServiceName); + final CmHandle cmHandle = CmHandle.buildCmHandleWithProperties(cmHandleId, cmHandleIdProperties); + dmiBatchOperationOut.getCmHandles().add(cmHandle); + } + } + } + return dmiBatchOperationsOutPerDmiServiceName; + } + + private static void publishErrorMessageToClientTopic(final String requestedCmHandleId) { + log.warn("cm handle {} not found", requestedCmHandleId); + // TODO Need to publish an error response to client given topic. + // Code should be implemented into https://jira.onap.org/browse/CPS-1583 ( + // NCMP : Handle non-existing cm handles) + } + + private static Map getDmiServiceNamesPerCmHandleId( + final Map>> dmiDmiPropertiesPerCmHandleIdPerServiceName) { + final Map dmiServiceNamesPerCmHandleId = new HashMap<>(); + for (final Map.Entry>> dmiDmiPropertiesEntry + : dmiDmiPropertiesPerCmHandleIdPerServiceName.entrySet()) { + final String dmiServiceName = dmiDmiPropertiesEntry.getKey(); + final Set cmHandleIds = dmiDmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).keySet(); + for (final String cmHandleId : cmHandleIds) { + dmiServiceNamesPerCmHandleId.put(cmHandleId, dmiServiceName); + } + } + dmiDmiPropertiesPerCmHandleIdPerServiceName.put(UNKNOWN_SERVICE_NAME, Collections.emptyMap()); + return dmiServiceNamesPerCmHandleId; + } + + private static DmiBatchOperation getOrAddDmiBatchOperation(final String dmiServiceName, + final BatchOperationDefinition + batchOperationDefinitionIn, + final Map> + dmiBatchOperationsOutPerDmiServiceName) { + dmiBatchOperationsOutPerDmiServiceName + .computeIfAbsent(dmiServiceName, dmiServiceNameAsKey -> new ArrayList<>()); + final List dmiBatchOperationsOut + = dmiBatchOperationsOutPerDmiServiceName.get(dmiServiceName); + final boolean isNewOperation = dmiBatchOperationsOut.isEmpty() + || !dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1).getOperationId() + .equals(batchOperationDefinitionIn.getOperationId()); + if (isNewOperation) { + final DmiBatchOperation newDmiBatchOperationOut = + DmiBatchOperation.buildDmiBatchRequestBodyWithoutCmHandles(batchOperationDefinitionIn); + dmiBatchOperationsOut.add(newDmiBatchOperationOut); + return newDmiBatchOperationOut; + } + return dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java new file mode 100644 index 000000000..04075b3b7 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java @@ -0,0 +1,49 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.List; +import javax.validation.Valid; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class BatchOperationDefinition { + + private String operation; + private String operationId; + private String datastore; + private String options; + private String resourceIdentifier; + + @JsonProperty("targetIds") + @Valid + private List cmHandleIds = new ArrayList(); +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java new file mode 100644 index 000000000..7af107c37 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java @@ -0,0 +1,43 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; +import javax.validation.Valid; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResourceDataBatchRequest { + + @JsonProperty("operations") + @Valid + private List batchOperationDefinitions = Collections.emptyList(); +} 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 3d8e9cb2e..79f7e50e7 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 @@ -33,11 +33,13 @@ import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.models.BatchOperationDefinition import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters import org.onap.cps.ncmp.api.models.ConditionApiProperties import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.model.ConditionProperties import spock.lang.Shared @@ -54,8 +56,8 @@ import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE class NetworkCmProxyDataServiceImplSpec extends Specification { @@ -133,21 +135,13 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { response == '{dmi-response}' } - def 'Get bulk resource data for #datastoreName from DMI.'() { + def 'Get batch resource data for #datastoreName from DMI.'() { given: 'cpsDataService returns valid data node' - mockDataNode() - and: 'DMI returns valid response and data' - mockDmiDataOperations.getResourceDataFromDmi(datastoreName, ['testCmHandle'], - 'testResourceId', OPTIONS_PARAM,'some topic','requestId') >> - new ResponseEntity<>('{dmi-bulk-response}', HttpStatus.OK) + def resourceDataBatchRequest = getResourceDataBatchRequest(datastoreName) when: 'get batch resource data is called' - def response = objectUnderTest.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'], - 'testResourceId', - OPTIONS_PARAM, - 'some topic', - 'requestId') - then: 'get bulk resource data returns expected response' - response == '{dmi-bulk-response}' + objectUnderTest.requestResourceDataForCmHandleBatch('some topic', resourceDataBatchRequest, 'requestId') + then: 'get batch resource data returns expected response' + 1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', resourceDataBatchRequest, 'requestId') where: 'the following data stores are used' datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName] } @@ -373,4 +367,22 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode } + + def getResourceDataBatchRequest(datastore) { + def resourceDataBatchRequest = new ResourceDataBatchRequest() + def batchOperationDefinitions = new ArrayList() + batchOperationDefinitions.add(getBatchOperationDefinition(datastore)) + resourceDataBatchRequest.setBatchOperationDefinitions(batchOperationDefinitions) + } + + def getBatchOperationDefinition(datastore) { + def batchOperationDefinition = new BatchOperationDefinition() + batchOperationDefinition.setOperation("read") + batchOperationDefinition.setOperationId("operational-12") + batchOperationDefinition.setDatastore(datastore) + def targetIds = new ArrayList() + targetIds.add("some-cm-handle") + batchOperationDefinition.setCmHandleIds(targetIds) + return batchOperationDefinition + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy index b38ca10f7..6b0355eee 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -34,9 +34,9 @@ import org.springframework.web.client.HttpServerErrorException import org.springframework.web.client.RestTemplate import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE @SpringBootTest @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiRestClient]) 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 5fd4fbd43..934366626 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 @@ -24,6 +24,8 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest +import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired @@ -35,9 +37,9 @@ import spock.lang.Shared import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE @SpringBootTest @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiDataOperations]) @@ -50,8 +52,6 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def NO_REQUEST_ID = null @Shared def OPTIONS_PARAM = '(a=1,b=2)' - @Shared - def expectedBulkRequestAsJson = '{"operation": "read","data": {"fe1c1f1a070c4ce5bbfda7198592a0d3": {"neType": "RadioNode"},"b8e42eed0d9541ed8d8839e8eb86b3e0": {"neType": "RadioNode"}},"requestId": "bbb67474-f705-410a-93d1-b2844e7f45fd"}' @SpringBean JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) @@ -82,23 +82,26 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running' | '&options=(a=1,b=2)' } - def 'call get bulk resource data for #dataStore from DMI service with topic #scenario.'() { - given: 'collection of yang model cm Handles' + def 'call get batch resource data from DMI service #scenario.'() { + given: 'collection of yang model cm Handles and resource data batch request' mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) - and: 'a positive response from DMI service when it is called with the expected parameters' + def resourceDataBatchRequestJsonData = TestUtils.getResourceFileContent('resourceDataBatchRequest.json') + def resourceDataBatchRequest = spiedJsonObjectMapper.convertJsonString(resourceDataBatchRequestJsonData, ResourceDataBatchRequest.class) + resourceDataBatchRequest.batchOperationDefinitions[0].cmHandleIds = [cmHandleId] + def requestBodyAsJsonStringArg = null + and: 'a positive response from DMI service when it is called with valid request parameters' def responseFromDmi = new ResponseEntity(HttpStatus.ACCEPTED) - def expectedDmiBulkResourceDataUrl = "ncmp/v1/batch/data/ds/${dataStore}?resourceIdentifier=parent/child%26options=(a=1,b=2)&topic=my-topic-name&options=(fields=schemas/schema)" - mockDmiRestClient.postOperationWithJsonData(expectedDmiBulkResourceDataUrl, expectedBulkRequestAsJson, READ) >> responseFromDmi - dmiServiceUrlBuilder.getBulkRequestUrl(_, _) >> expectedDmiBulkResourceDataUrl - when: 'get resource data for bulk cm handle is invoked' - def result = objectUnderTest.getResourceDataFromDmi( dataStore.datastoreName, [cmHandleId], resourceIdentifier, - OPTIONS_PARAM, 'some-topic','requestId') - then: 'the result is the response from the DMI service' - assert result == responseFromDmi - where: 'the following parameters are used' - scenario | dataStore - 'datastore operational' | PASSTHROUGH_OPERATIONAL - 'datastore running' | PASSTHROUGH_RUNNING + def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name" + def expectedBatchRequestAsJson = '[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","cmHandleProperties":{"prop1":"val1"}}]}]' + mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName) >> responseFromDmi + dmiServiceUrlBuilder.getBatchRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl + when: 'get resource data for batch of cm handles are invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', resourceDataBatchRequest, 'requestId') + then: 'wait a little to allow execution of service method by task executor (on separate thread)' + Thread.sleep(100) + then: 'validate ncmp generated dmi request body json args' + 1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ) >> { args -> requestBodyAsJsonStringArg = args[1] } + assert requestBodyAsJsonStringArg == expectedBatchRequestAsJson } def 'call get all resource data.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index ed74ad334..d1025f9d6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -36,7 +36,7 @@ import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration import spock.lang.Shared -import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ @SpringBootTest @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations]) @@ -103,9 +103,9 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { then: 'the result is the response from DMI service' assert result == [] where: 'the following DMI properties are used' - scenario | dmiProperties || expectedAdditionalPropertiesInRequest - 'with properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' - 'without properties' | [] || '{}' + scenario | dmiProperties || expectedAdditionalPropertiesInRequest + 'with properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' + 'without properties' | [] || '{}' } def 'Retrieving yang resources.'() { @@ -154,10 +154,10 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { then: 'the result is the response from DMI service' assert result == [mod1:'some yang source'] where: 'the following DMI properties are used' - scenario | dmiProperties | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest - 'with module references and properties' | [yangModelCmHandleProperty] | newModuleReferences || '{"prop1":"val1"}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' - 'without module references' | [yangModelCmHandleProperty] | [] || '{"prop1":"val1"}' | '' - 'without properties' | [] | newModuleReferences || '{}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' + scenario | dmiProperties | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest + 'with module references and properties' | [yangModelCmHandleProperty] | newModuleReferences || '{"prop1":"val1"}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' + 'without module references' | [yangModelCmHandleProperty] | [] || '{"prop1":"val1"}' | '' + 'without properties' | [] | newModuleReferences || '{}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' } def 'Retrieving yang resources from DMI with null DMI properties.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy new file mode 100644 index 000000000..e65874930 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy @@ -0,0 +1,80 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl.utils + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import spock.lang.Specification + +class ResourceDataBatchRequestUtilsSpec extends Specification { + + @SpringBean + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + + def 'Process per operation in batch request with #serviceName.'() { + given: 'batch request with 3 operations' + def resourceDataBatchRequestJsonData = TestUtils.getResourceFileContent('resourceDataBatchRequest.json') + def resourceDataBatchRequest = jsonObjectMapper.convertJsonString(resourceDataBatchRequestJsonData, ResourceDataBatchRequest.class) + and: '4 known cm handles: ch1-dmi1, ch2-dmi1, ch3-dmi2, ch4-dmi2' + def yangModelCmHandles = getYangModelCmHandles() + when: 'Operation in batch request is processed' + def operationsOutPerDmiServiceName = ResourceDataBatchRequestUtils.processPerOperationInBatchRequest(resourceDataBatchRequest, yangModelCmHandles) + and: 'converted to a json node' + def dmiBatchRequestBody = jsonObjectMapper.asJsonString(operationsOutPerDmiServiceName.get(serviceName)) + def dmiBatchRequestBodyAsJsonNode = jsonObjectMapper.convertToJsonNode(dmiBatchRequestBody).get(operationIndex) + then: 'it contains the correct operation details' + assert dmiBatchRequestBodyAsJsonNode.get('operation').asText() == 'read' + assert dmiBatchRequestBodyAsJsonNode.get('operationId').asText() == expectedOperationId + assert dmiBatchRequestBodyAsJsonNode.get('datastore').asText() == expectedDatastore + and: 'the correct cm handles (just for #serviceName)' + assert dmiBatchRequestBodyAsJsonNode.get('cmHandles').size() == expectedCmHandleIds.size() + expectedCmHandleIds.each { + dmiBatchRequestBodyAsJsonNode.get('cmHandles').toString().contains(it) + } + where: 'the following dmi service and operations are checked' + serviceName | operationIndex || expectedOperationId | expectedDatastore | expectedCmHandleIds + 'dmi1' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] + 'dmi1' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch1-dmi1', 'ch2-dmi1'] + 'dmi1' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] + 'dmi2' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch3-dmi2'] + 'dmi2' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch7-dmi2'] + 'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2'] + } + + static def getYangModelCmHandles() { + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def readyState = new CompositeStateBuilder().withCmHandleState(CmHandleState.READY).withLastUpdatedTimeNow().build() + return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch8-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + ] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy index e1055bb21..dc471e64f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy @@ -64,13 +64,13 @@ class RestQueryParametersValidatorSpec extends Specification { and: 'the exception details contain the correct significant term ' thrown.details.contains(expectedWordInDetails) where: - scenario | conditionName | conditionParameters || expectedWordInDetails - 'unknown condition name' | 'unknownCondition' | [['key':'value']] || 'conditionName' - 'no condition name' | '' | [['key':'value']] || 'conditionName' - 'empty properties' | 'validConditionName' | [[ : ]] || 'conditionsParameter' - 'empty conditions' | 'validConditionName' | [[:]] || 'conditionsParameter' - 'too many properties' | 'validConditionName' | [[key1:'value1', key2:'value2']] || 'conditionsParameter' - 'empty key' | 'validConditionName' | [['':'wrong']] || 'conditionsParameter' + scenario | conditionName | conditionParameters || expectedWordInDetails + 'unknown condition name' | 'unknownCondition' | [['key': 'value']] || 'conditionName' + 'no condition name' | '' | [['key': 'value']] || 'conditionName' + 'empty properties' | 'validConditionName' | [[:]] || 'conditionsParameter' + 'empty conditions' | 'validConditionName' | [[:]] || 'conditionsParameter' + 'too many properties' | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter' + 'empty key' | 'validConditionName' | [['': 'wrong']] || 'conditionsParameter' } def 'CM Handle Query validation: validate module name condition properties - valid query.'() { diff --git a/cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json b/cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json new file mode 100644 index 000000000..98ed39b9a --- /dev/null +++ b/cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json @@ -0,0 +1,36 @@ +{ + "operations": [ + { + "operation": "read", + "operationId": "operational-14", + "datastore": "ncmp-datastore:passthrough-operational", + "options": "some option", + "resourceIdentifier": "some resource identifier", + "targetIds": [ + "ch3-dmi2", + "unknown-cm-handle", + "ch6-dmi1" + ] + }, + { + "operation": "read", + "operationId": "running-12", + "datastore": "ncmp-datastore:passthrough-running", + "targetIds": [ + "ch1-dmi1", + "ch7-dmi2", + "ch2-dmi1" + ] + }, + { + "operation": "read", + "operationId": "operational-15", + "datastore": "ncmp-datastore:passthrough-operational", + "options": "some option", + "targetIds": [ + "ch4-dmi2", + "ch6-dmi1" + ] + } + ] +} diff --git a/dmi-plugin-stub/files/batchResponse.json b/dmi-plugin-stub/files/batchResponse.json index b0615df94..9181b64f2 100644 --- a/dmi-plugin-stub/files/batchResponse.json +++ b/dmi-plugin-stub/files/batchResponse.json @@ -2,8 +2,8 @@ "eventId": "4cb32729-85e3-44d1-aa6e-c923b9b059a5", "eventCorrelationId": "68f15800-8ed4-4bae-9e53-27a9e03e1911", "eventTime": "2023-03-28T14:29:23.876+0000", - "eventType": "org.onap.cps.ncmp.event.model.BulkResponseEvent", - "eventSchema": "urn:cps:org.onap.cps.ncmp.event.model.BulkResponseEvent", + "eventType": "org.onap.cps.ncmp.event.model.BatchResponseEvent", + "eventSchema": "urn:cps:org.onap.cps.ncmp.event.model.BatchResponseEvent", "eventSchemaVersion": "v1", "event": { "payload": [ diff --git a/dmi-plugin-stub/mappings/batchCmHandles.json b/dmi-plugin-stub/mappings/batchCmHandles.json index 5f290cd47..851959bf9 100644 --- a/dmi-plugin-stub/mappings/batchCmHandles.json +++ b/dmi-plugin-stub/mappings/batchCmHandles.json @@ -1,7 +1,7 @@ { "request": { "method": "POST", - "urlPattern": "/dmi/v1/batch/data/ds/.*" + "urlPattern": "/dmi/v1/data?.*" }, "response": { "status": 200, -- 2.16.6