diff options
70 files changed, 2590 insertions, 1073 deletions
@@ -52,11 +52,6 @@ committers: company: 'Bell Canada' id: 'puthuparambil.aditya' timezone: 'Europe/Dublin' - - name: 'Renu Kumari' - email: 'renu.kumari@bell.ca' - company: 'Bell Canada' - id: 'renukumari' - timezone: 'America/Toronto' - name: 'Joseph Keenan' email: 'joseph.keenan@est.tech' company: 'Ericsson Software Technology' diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index 9b6f41ec23..f7a06c53cf 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -45,13 +45,12 @@ spring: username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driverClassName: org.postgresql.Driver
- initialization-mode: always
hikari:
minimumIdle: 5
maximumPoolSize: 80
- idleTimeout: 120000
- connectionTimeout: 300000
- leakDetectionThreshold: 300000
+ idleTimeout: 60000
+ connectionTimeout: 120000
+ leakDetectionThreshold: 30000
pool-name: CpsDatabasePool
cache:
@@ -91,6 +90,9 @@ spring: default-property-inclusion: NON_NULL
serialization:
FAIL_ON_EMPTY_BEANS: false
+ sql:
+ init:
+ mode: ALWAYS
app:
ncmp:
async-m2m:
diff --git a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java index 67af6f10a9..bb919b5c6e 100644 --- a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java +++ b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java @@ -30,10 +30,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; import org.onap.cps.ncmp.rest.model.RestModuleDefinition; import org.onap.cps.ncmp.rest.model.RestModuleReference; @@ -58,20 +57,55 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { private String pathToResponseFiles; @Override - public ResponseEntity<Void> createResourceDataRunningForCmHandle(@NotNull @Valid final String resourceIdentifier, - final String cmHandleId, @Valid final Object body, final String contentType) { + public ResponseEntity<Object> getResourceDataForCmHandle(final String dataStoreName, + final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + if (DatastoreType.PASSTHROUGH_OPERATIONAL == DatastoreType.fromDatastoreName(dataStoreName)) { + final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery); + final Map<String, Object> asyncResponseData = asyncResponse.getBody(); + Object responseObject = null; + // read JSON file and map/convert to java POJO + final ClassPathResource resource = + new ClassPathResource(pathToResponseFiles + "passthrough-operational-example.json"); + try (InputStream inputStream = resource.getInputStream()) { + final String string = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + final ObjectMapper mapper = new ObjectMapper(); + responseObject = mapper.readValue(string, Object.class); + } catch (final IOException exception) { + log.error("Error reading the file.", exception); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + if (asyncResponseData == null) { + return ResponseEntity.ok(responseObject); + } + return ResponseEntity.ok(asyncResponse); + } + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName, + final String resourceIdentifier, + final String cmHandleId, + final Object body, + final String contentType) { return new ResponseEntity<>(HttpStatus.CREATED); } @Override - public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandleId, - @NotNull @Valid final String resourceIdentifier, final String contentType) { + public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName, + final String cmHandleId, + final String resourceIdentifier, + final String contentType) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @Override public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { List<RestOutputCmHandle> restOutputCmHandles = null; // read JSON file and map/convert to java POJO final ClassPathResource resource = new ClassPathResource(pathToResponseFiles + "cmHandlesSearch.json"); @@ -88,19 +122,19 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { @Override public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, - final Boolean dataSyncEnabled) { + final Boolean dataSyncEnabled) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @Override public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @Override public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @@ -119,48 +153,12 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } - /** - * Get resource data from operational datastore. - * - * @param cmHandleId cm handle identifier - * @param resourceIdentifier resource identifier - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter - * @return {@code ResponseEntity} response from dmi plugin - */ - @Override - public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandleId, - final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery) { - final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery); - final Map<String, Object> asyncResponseData = asyncResponse.getBody(); - Object responseObject = null; - // read JSON file and map/convert to java POJO - final ClassPathResource resource = new ClassPathResource(pathToResponseFiles - + "passthrough-operational-example.json"); - try (InputStream inputStream = resource.getInputStream()) { - final String string = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - final ObjectMapper mapper = new ObjectMapper(); - responseObject = mapper.readValue(string, Object.class); - } catch (final IOException exception) { - log.error("Error reading the file.", exception); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } - if (asyncResponseData == null) { - return ResponseEntity.ok(responseObject); - } - return ResponseEntity.ok(asyncResponse); - - } - - @Override - public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandleId, - final String resourceIdentifier, final String options, final String topic) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - @Override - public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandleId, final Object body, final String contentType) { + public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName, + final String resourceIdentifier, + final String cmHandleId, + final Object body, + final String contentType) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @@ -170,8 +168,11 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { } @Override - public ResponseEntity<Object> updateResourceDataRunningForCmHandle(@NotNull @Valid final String resourceIdentifier, - final String cmHandleId, @Valid final Object body, final String contentType) { + public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName, + final String resourceIdentifier, + final String cmHandleId, + final Object body, + final String contentType) { 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 427f083698..7ca09ceedb 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -86,7 +86,7 @@ components: type: array items: type: string - example: [my-cm-handle1, my-cm-handle2, my-cm-handle3] + example: [ my-cm-handle1, my-cm-handle2, my-cm-handle3 ] DmiPluginRegistrationErrorResponse: type: object properties: @@ -124,14 +124,14 @@ components: type: string example: my-cm-handle cmHandleProperties: - $ref: '#/components/schemas/RestCmHandleProperties' + $ref: '#/components/schemas/RestCmHandleProperties' publicCmHandleProperties: - $ref: '#/components/schemas/RestCmHandleProperties' + $ref: '#/components/schemas/RestCmHandleProperties' RestCmHandleProperties: - type: object - additionalProperties: - type: string - example: my-property + type: object + additionalProperties: + type: string + example: my-property #Response Schemas RestModuleReference: @@ -288,21 +288,21 @@ components: examples: dataSampleRequest: - summary: Sample request - description: Sample request body - value: - test:bookstore: - bookstore-name: Chapters - categories: - - code: '01' - name: SciFi - books: + summary: Sample request + description: Sample request body + value: + test:bookstore: + bookstore-name: Chapters + categories: + - code: '01' + name: SciFi + books: - authors: - Iain M. Banks - Ursula K. Le Guin - - code: '02' - name: kids - books: + - code: '02' + name: kids + books: - authors: - Philip Pullman @@ -351,22 +351,22 @@ components: - Philip Pullman dataSampleResponse: - summary: Sample response - description: Sample response for selecting 'sample 1'. - value: - bookstore: - categories: - - code: '01' - books: - - authors: - - Iain M. Banks - - Ursula K. Le Guin - name: SciFi - - code: '02' - books: - - authors: - - Philip Pullman - name: kids + summary: Sample response + description: Sample response for selecting 'sample 1'. + value: + bookstore: + categories: + - code: '01' + books: + - authors: + - Iain M. Banks + - Ursula K. Le Guin + name: SciFi + - code: '02' + books: + - authors: + - Philip Pullman + name: kids allCmHandleQueryParameters: value: @@ -448,7 +448,7 @@ components: includeDescendantsOptionInQuery: name: include-descendants in: query - description: include-descendants + description: Determines if descendants are included in response required: false schema: type: boolean @@ -526,6 +526,14 @@ components: type: string default: application/json example: application/yang-data+json + datastoreName: + name: ncmp-datastore-name + in: path + description: The type of the requested data + required: true + schema: + type: string + example: ncmp-datastore:operational responses: NotFound: @@ -555,9 +563,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 403 - message: Forbidden error message - details: Forbidden error details + status: 403 + message: Forbidden error message + details: Forbidden error details BadRequest: description: Bad Request content: @@ -565,9 +573,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 400 BAD_REQUEST - message: Bad request error message - details: Bad request error details + status: 400 BAD_REQUEST + message: Bad request error message + details: Bad request error details Conflict: description: Conflict content: @@ -575,9 +583,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 409 CONFLICT - message: Conflict error message - details: Conflict error details + status: 409 CONFLICT + message: Conflict error message + details: Conflict error details NotImplemented: description: The given path has not been implemented content: @@ -585,9 +593,9 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 501 - message: Not implemented error message - details: Not implemented error details + status: 501 + message: Not implemented error message + details: Not implemented error details Ok: description: OK content: @@ -596,10 +604,10 @@ components: type: object Created: description: Created - content: {} + content: { } NoContent: description: No Content - content: {} + content: { } InternalServerError: description: Internal Server Error content: diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index 4266fc4152..38db26f470 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -17,51 +17,21 @@ # # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END========================================================= -getResourceDataForPassthroughOperational: - get: - tags: - - network-cm-proxy - summary: Get resource data from pass-through operational for cm handle - description: Get resource data from pass-through operational for given cm handle - operationId: getResourceDataOperationalForCmHandle - parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' - - $ref: 'components.yaml#/components/parameters/topicParamInQuery' - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - examples: - dataSampleResponse: - $ref: 'components.yaml#/components/examples/dataSampleResponse' - 400: - $ref: 'components.yaml#/components/responses/BadRequest' - 401: - $ref: 'components.yaml#/components/responses/Unauthorized' - 403: - $ref: 'components.yaml#/components/responses/Forbidden' - 500: - $ref: 'components.yaml#/components/responses/InternalServerError' - 502: - $ref: 'components.yaml#/components/responses/BadGateway' -resourceDataForPassthroughRunning: +resourceDataForCmHandle: get: tags: - network-cm-proxy - summary: Get resource data from pass-through running for cm handle - description: Get resource data from pass-through running for given cm handle - operationId: getResourceDataRunningForCmHandle + summary: Get resource data for cm handle + description: Get resource data for given cm handle + operationId: getResourceDataForCmHandle parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' - $ref: 'components.yaml#/components/parameters/topicParamInQuery' + - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery' responses: 200: description: OK @@ -82,6 +52,7 @@ resourceDataForPassthroughRunning: $ref: 'components.yaml#/components/responses/InternalServerError' 502: $ref: 'components.yaml#/components/responses/BadGateway' + post: tags: - network-cm-proxy @@ -89,6 +60,7 @@ resourceDataForPassthroughRunning: description: create resource data from pass-through running for given cm handle operationId: createResourceDataRunningForCmHandle parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' @@ -128,6 +100,7 @@ resourceDataForPassthroughRunning: description: Update resource data from pass-through running for the given cm handle operationId: updateResourceDataRunningForCmHandle parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' @@ -167,6 +140,7 @@ resourceDataForPassthroughRunning: description: Patch resource data from pass-through running for the given cm handle operationId: patchResourceDataRunningForCmHandle parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' @@ -200,6 +174,7 @@ resourceDataForPassthroughRunning: description: Delete resource data from pass-through running for a given cm handle operationId: deleteResourceDataRunningForCmHandle parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' - $ref: 'components.yaml#/components/parameters/cmHandleInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index 8e020668a0..4c546beb26 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -26,11 +26,8 @@ info: servers: - url: /ncmp paths: - /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-operational: - $ref: 'ncmp.yml#/getResourceDataForPassthroughOperational' - - /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running: - $ref: 'ncmp.yml#/resourceDataForPassthroughRunning' + /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}: + $ref: 'ncmp.yml#/resourceDataForCmHandle' /v1/ch/{cm-handle}/modules: $ref: 'ncmp.yml#/fetchModuleReferencesByCmHandle' diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index 6a700c3e12..b3021d2410 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -185,7 +185,8 @@ <goal>copy-resources</goal> </goals> <configuration> - <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-ncmp</outputDirectory> + <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-ncmp + </outputDirectory> <resources> <resource> <directory>${project.basedir}/target/generated-sources/swagger/</directory> 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 d2ed393794..2f6668a351 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 @@ -29,21 +29,19 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.stream.Collectors; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType; +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandler; +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory; +import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException; import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper; import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; @@ -53,9 +51,7 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState; import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties; import org.onap.cps.ncmp.rest.util.DeprecationHelper; -import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -68,94 +64,65 @@ import org.springframework.web.bind.annotation.RestController; public class NetworkCmProxyController implements NetworkCmProxyApi { private static final String NO_BODY = null; - private static final String NO_REQUEST_ID = null; - private static final String NO_TOPIC = null; private final NetworkCmProxyDataService networkCmProxyDataService; private final JsonObjectMapper jsonObjectMapper; - private final DeprecationHelper deprecationHelper; private final NcmpRestInputMapper ncmpRestInputMapper; private final CmHandleStateMapper cmHandleStateMapper; - private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; - @Value("${notification.async.executor.time-out-value-in-ms:2000}") - private int timeOutInMilliSeconds; - @Value("${notification.enabled:true}") - private boolean asyncEnabled; + private final NcmpDatastoreResourceRequestHandlerFactory ncmpDatastoreResourceRequestHandlerFactory; /** - * Get resource data from operational datastore. + * Get resource data from datastore. * - * @param cmHandle cm handle identifier - * @param resourceIdentifier resource identifier + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param resourceIdentifier resource identifier * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter + * @param topicParamInQuery topic query parameter + * @param includeDescendants whether include descendants * @return {@code ResponseEntity} response from dmi plugin */ + @Override - public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandle, - final @NotNull @Valid String resourceIdentifier, - final @Valid String optionsParamInQuery, - final @Valid String topicParamInQuery) { - if (asyncEnabled && isValidTopic(topicParamInQuery)) { - final String requestId = UUID.randomUUID().toString(); - log.info("Received Async passthrough-operational request with id {}", requestId); - cpsNcmpTaskExecutor.executeTask(() -> - networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId - ), timeOutInMilliSeconds - ); - return ResponseEntity.ok(Map.of("requestId", requestId)); - } else { - log.warn("Asynchronous messaging is currently disabled for passthrough-operational." - + " Will use synchronous operation."); - } + public ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName, + final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { - final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID); + final NcmpDatastoreResourceRequestHandler ncmpDatastoreResourceRequestHandler = + ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.fromDatastoreName(datastoreName)); - return ResponseEntity.ok(responseObject); + return ncmpDatastoreResourceRequestHandler.getResourceData(cmHandle, resourceIdentifier, + optionsParamInQuery, topicParamInQuery, includeDescendants); } /** - * Get resource data from pass-through running datastore. + * Patch resource data from passthrough-running. * - * @param cmHandle cm handle identifier * @param resourceIdentifier resource identifier - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of body * @return {@code ResponseEntity} response from dmi plugin */ - @Override - public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandle, - final @NotNull @Valid String resourceIdentifier, - final @Valid String optionsParamInQuery, - final @Valid String topicParamInQuery) { - if (asyncEnabled && isValidTopic(topicParamInQuery)) { - final String requestId = UUID.randomUUID().toString(); - log.info("Received Async passthrough-running request with id {}", requestId); - cpsNcmpTaskExecutor.executeTask(() -> - networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId - ), timeOutInMilliSeconds - ); - return ResponseEntity.ok(Map.of("requestId", requestId)); - } else { - log.warn("Asynchronous messaging is currently disabled for passthrough-running." - + " Will use synchronous operation."); - } - - final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID); - - return ResponseEntity.ok(responseObject); - } @Override public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, - final Object requestBody, final String contentType) { - final Object responseObject = networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, PATCH, jsonObjectMapper.asJsonString(requestBody), contentType); + final String datastoreName, + final String cmHandle, + final Object requestBody, + final String contentType) { + + acceptPassthroughRunningOnly(datastoreName); + + final Object responseObject = networkCmProxyDataService + .writeResourceDataPassThroughRunningForCmHandle( + cmHandle, resourceIdentifier, PATCH, + jsonObjectMapper.asJsonString(requestBody), contentType); return ResponseEntity.ok(responseObject); } @@ -163,14 +130,21 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Create resource data in datastore pass-through running for given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param requestBody the request body - * @param contentType content type of body + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of body * @return {@code ResponseEntity} response from dmi plugin */ @Override public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, final Object requestBody, final String contentType) { + final String datastoreName, + final String cmHandle, + final Object requestBody, + final String contentType) { + + acceptPassthroughRunningOnly(datastoreName); + networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType); return new ResponseEntity<>(HttpStatus.CREATED); @@ -180,36 +154,45 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Update resource data in datastore pass-through running for given cm-handle. * * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param requestBody the request body - * @param contentType content type of the body + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param requestBody the request body + * @param contentType content type of the body * @return response entity */ + @Override public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String resourceIdentifier, + final String datastoreName, final String cmHandle, final Object requestBody, final String contentType) { + acceptPassthroughRunningOnly(datastoreName); + networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Delete resource data in datastore pass-through running for a given cm-handle. + * Delete resource data in datastore pass-through running for a given cm-handle. * + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier * @param resourceIdentifier resource identifier - * @param cmHandle cm handle identifier - * @param contentType content type of the body + * @param contentType content type of the body * @return response entity no content if request is successful */ @Override - public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandle, + public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName, + final String cmHandle, final String resourceIdentifier, final String contentType) { + + acceptPassthroughRunningOnly(datastoreName); + networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, DELETE, NO_BODY, contentType); + resourceIdentifier, DELETE, NO_BODY, contentType); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -240,7 +223,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ @Override public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryParameters cmHandleQueryParameters) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); final Set<String> cmHandleIds = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters); @@ -249,6 +232,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Search for Cm Handle and Properties by Name. + * * @param cmHandleId cm-handle identifier * @return cm handle and its properties */ @@ -261,33 +245,35 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Get Cm Handle Properties by Cm Handle Id. + * * @param cmHandleId cm-handle identifier * @return cm handle properties */ @Override public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); cmHandlePublicProperties.add(networkCmProxyDataService.getCmHandlePublicProperties(cmHandleId)); final RestOutputCmHandlePublicProperties restOutputCmHandlePublicProperties = - new RestOutputCmHandlePublicProperties(); + new RestOutputCmHandlePublicProperties(); restOutputCmHandlePublicProperties.setPublicCmHandleProperties(cmHandlePublicProperties); return ResponseEntity.ok(restOutputCmHandlePublicProperties); } /** * Get Cm Handle State by Cm Handle Id. + * * @param cmHandleId cm-handle identifier * @return cm handle state */ @Override public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId( - final String cmHandleId) { + final String cmHandleId) { final CompositeState cmHandleState = networkCmProxyDataService.getCmHandleCompositeState(cmHandleId); final RestOutputCmHandleCompositeState restOutputCmHandleCompositeState = - new RestOutputCmHandleCompositeState(); + new RestOutputCmHandleCompositeState(); restOutputCmHandleCompositeState.setState( - cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState)); + cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState)); return ResponseEntity.ok(restOutputCmHandleCompositeState); } @@ -314,26 +300,27 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) { final List<RestModuleReference> restModuleReferences = - networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream() - .map(ncmpRestInputMapper::toRestModuleReference) - .collect(Collectors.toList()); + networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream() + .map(ncmpRestInputMapper::toRestModuleReference) + .collect(Collectors.toList()); return new ResponseEntity<>(restModuleReferences, HttpStatus.OK); } /** * Set the data sync enabled flag, along with the data sync state for the specified cm handle. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param dataSyncEnabledFlag data sync enabled flag * @return response entity ok if request is successful */ @Override public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, - final Boolean dataSyncEnabledFlag) { + final Boolean dataSyncEnabledFlag) { networkCmProxyDataService.setDataSyncEnabled(cmHandleId, dataSyncEnabledFlag); return new ResponseEntity<>(HttpStatus.OK); } + private RestOutputCmHandle toRestOutputCmHandle(final NcmpServiceCmHandle ncmpServiceCmHandle) { final RestOutputCmHandle restOutputCmHandle = new RestOutputCmHandle(); final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); @@ -345,15 +332,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { return restOutputCmHandle; } - private static boolean isValidTopic(final String topicName) { - if (topicName == null) { - return false; - } - if (CpsValidator.validateTopicName(topicName)) { - return true; + private void acceptPassthroughRunningOnly(final String datastoreName) { + final DatastoreType datastoreType = DatastoreType.fromDatastoreName(datastoreName); + + if (DatastoreType.PASSTHROUGH_RUNNING != datastoreType) { + throw new InvalidDatastoreException(datastoreName + " is not supported"); } - throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic"); } - } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java new file mode 100644 index 0000000000..e8ab997d6f --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java @@ -0,0 +1,67 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException; + +@Getter +public enum DatastoreType { + + OPERATIONAL("ncmp-datastore:operational"), + PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"), + PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"); + + DatastoreType(final String datastoreName) { + this.datastoreName = datastoreName; + } + + private final String datastoreName; + private static final Map<String, DatastoreType> datastoreNameToDatastoreType = new HashMap<>(); + + static { + Arrays.stream(DatastoreType.values()).forEach( + type -> datastoreNameToDatastoreType.put(type.getDatastoreName(), type)); + } + + /** + * From datastore name get datastore type. + * + * @param datastoreName the datastore name + * @return the datastore type + */ + public static DatastoreType fromDatastoreName(final String datastoreName) { + + final DatastoreType datastoreType = datastoreNameToDatastoreType.get(datastoreName); + + if (null == datastoreType) { + throw new InvalidDatastoreException(datastoreName + " is an invalid datastore name"); + } + + return datastoreType; + } + +} + diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java new file mode 100644 index 0000000000..6ed9b8c4d1 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java @@ -0,0 +1,55 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.spi.FetchDescendantsOption; + +@Slf4j +public class NcmpDatastoreOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastoreOperationalResourceRequestHandler(final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + final FetchDescendantsOption fetchDescendantsOption = + Boolean.TRUE.equals(includeDescendant) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS + : FetchDescendantsOption.OMIT_DESCENDANTS; + + return () -> networkCmProxyDataService.getResourceDataOperational(cmHandle, resourceIdentifier, + fetchDescendantsOption); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java new file mode 100644 index 0000000000..196e5bd33d --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java @@ -0,0 +1,51 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; + +@Slf4j +public class NcmpDatastorePassthroughOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastorePassthroughOperationalResourceRequestHandler( + final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + return () -> networkCmProxyDataService.getResourceDataOperationalForCmHandle( + cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java new file mode 100644 index 0000000000..5bf16b7499 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java @@ -0,0 +1,50 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; + +@Slf4j +public class NcmpDatastorePassthroughRunningResourceRequestHandler extends NcmpDatastoreResourceRequestHandler { + + public NcmpDatastorePassthroughRunningResourceRequestHandler( + final NetworkCmProxyDataService networkCmProxyDataService, + final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, + final int timeOutInMilliSeconds, + final boolean notificationFeatureEnabled) { + super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + } + + @Override + public Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant) { + + return () -> networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( + cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java new file mode 100644 index 0000000000..a6d313b05f --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java @@ -0,0 +1,92 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +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.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.onap.cps.ncmp.rest.util.TopicValidator; +import org.springframework.http.ResponseEntity; + +@RequiredArgsConstructor +@Slf4j +public abstract class NcmpDatastoreResourceRequestHandler { + + private static final String NO_REQUEST_ID = null; + private static final String NO_TOPIC = null; + + protected final NetworkCmProxyDataService networkCmProxyDataService; + protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; + protected final int timeOutInMilliSeconds; + protected final boolean notificationFeatureEnabled; + + protected abstract Supplier<Object> getTask(final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Boolean includeDescendant); + + + /** + * Get resource data from datastore. + * + * @param cmHandleId the cm handle + * @param resourceIdentifier the resource identifier + * @param optionsParamInQuery the options param in query + * @param topicParamInQuery the topic param in query + * @param includeDescendants whether include descendants + * @return the response entity + */ + public ResponseEntity<Object> getResourceData(final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + + final String requestId = UUID.randomUUID().toString(); + final boolean asyncResponseRequested = topicParamInQuery != null; + + if (asyncResponseRequested && notificationFeatureEnabled) { + TopicValidator.validateTopicName(topicParamInQuery); + log.debug("Received Async request with id {}", requestId); + cpsNcmpTaskExecutor.executeTask( + getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, + includeDescendants), timeOutInMilliSeconds); + + return ResponseEntity.ok(Map.of("requestId", requestId)); + } + + if (asyncResponseRequested) { + log.warn("Asynchronous messaging is currently disabled, will use synchronous operation."); + } + + final Object responseObject = + getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID, + includeDescendants).get(); + + return ResponseEntity.ok(responseObject); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java new file mode 100644 index 0000000000..35bd578ce2 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NcmpDatastoreResourceRequestHandlerFactory { + + private final NetworkCmProxyDataService networkCmProxyDataService; + private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; + + @Value("${notification.async.executor.time-out-value-in-ms:2000}") + private int timeOutInMilliSeconds; + @Value("${notification.enabled:true}") + private boolean notificationFeatureEnabled; + + /** + * Gets ncmp datastore handler. + * + * @param datastoreType the datastore type + * @return the ncmp datastore handler + */ + public NcmpDatastoreResourceRequestHandler getNcmpDatastoreResourceRequestHandler( + final DatastoreType datastoreType) { + + switch (datastoreType) { + case OPERATIONAL: + return new NcmpDatastoreOperationalResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + case PASSTHROUGH_RUNNING: + return new NcmpDatastorePassthroughRunningResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + case PASSTHROUGH_OPERATIONAL: + return new NcmpDatastorePassthroughOperationalResourceRequestHandler(networkCmProxyDataService, + cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + default: + return null; + } + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java new file mode 100644 index 0000000000..ff13a93e58 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java @@ -0,0 +1,32 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.exceptions; + +public class InvalidDatastoreException extends RuntimeException { + /** + * Instantiates a new Invalid datastore exception. + * + * @param message the message + */ + public InvalidDatastoreException(final String message) { + super(message); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java index b56ca7b8c2..6a52d5861e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.exception; +package org.onap.cps.ncmp.rest.exceptions; import lombok.Getter; 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 c72373344d..58a60d2e1c 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 @@ -25,7 +25,6 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.exception.DmiRequestException; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; -import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException; import org.onap.cps.ncmp.api.impl.exception.NcmpException; import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException; import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController; @@ -33,6 +32,8 @@ import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController; import org.onap.cps.ncmp.rest.model.DmiErrorMessage; import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiresponse; import org.onap.cps.ncmp.rest.model.ErrorMessage; +import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; @@ -60,7 +61,7 @@ public class NetworkCmProxyRestExceptionHandler { */ @ExceptionHandler public static ResponseEntity<Object> handleInternalServerErrorExceptions( - final Exception exception) { + final Exception exception) { return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception); } @@ -75,12 +76,17 @@ public class NetworkCmProxyRestExceptionHandler { return wrapDmiErrorResponse(HttpStatus.BAD_GATEWAY, httpClientRequestException); } - @ExceptionHandler({DmiRequestException.class, DataValidationException.class, HttpMessageNotReadableException.class, - InvalidTopicException.class}) + @ExceptionHandler({DmiRequestException.class, DataValidationException.class, + HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class}) public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) { return buildErrorResponse(HttpStatus.BAD_REQUEST, exception); } + @ExceptionHandler({AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class }) + public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) { + return buildErrorResponse(HttpStatus.CONFLICT, exception); + } + @ExceptionHandler({DataNodeNotFoundException.class}) public static ResponseEntity<Object> handleNotFoundExceptions(final CpsException exception) { return buildErrorResponse(HttpStatus.NOT_FOUND, exception); @@ -105,7 +111,8 @@ public class NetworkCmProxyRestExceptionHandler { return new ResponseEntity<>(errorMessage, status); } - private static ResponseEntity<Object> wrapDmiErrorResponse(final HttpStatus httpStatus, + private static ResponseEntity<Object> wrapDmiErrorResponse( + final HttpStatus httpStatus, final HttpClientRequestException httpClientRequestException) { final var dmiErrorMessage = new DmiErrorMessage(); final var dmiErrorResponse = new DmiErrorMessageDmiresponse(); diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java new file mode 100644 index 0000000000..313e7bc012 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java @@ -0,0 +1,44 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.util; + +import java.util.regex.Pattern; +import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException; + +public class TopicValidator { + + private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|" + + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$"); + + /** + * Validate kafka topic name pattern. + * + * @param topicName name of the topic to be validated + * + * @throws InvalidTopicException if the topic is not valid + */ + public static void validateTopicName(final String topicName) { + if (!TOPIC_NAME_PATTERN.matcher(topicName).matches()) { + throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic"); + } + } + +} 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 d568a5a925..b6194bc796 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 @@ -23,51 +23,60 @@ package org.onap.cps.ncmp.rest.controller +import com.fasterxml.jackson.databind.ObjectMapper import org.mapstruct.factory.Mappers +import org.onap.cps.TestUtils +import org.onap.cps.ncmp.api.NetworkCmProxyDataService import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState -import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.inventory.DataStoreSyncState +import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.ModuleDefinition +import org.onap.cps.spi.model.ModuleReference +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder import spock.lang.Shared +import spock.lang.Specification import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.TestUtils -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.ncmp.api.NetworkCmProxyDataService -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import spock.lang.Specification +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +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 @WebMvcTest(NetworkCmProxyController) class NetworkCmProxyControllerSpec extends Specification { + public static final int TIMEOUT_IN_MS = 2000 + public static final boolean NOTIFICATION_ENABLED = true + @Autowired MockMvc mvc @@ -92,6 +101,9 @@ class NetworkCmProxyControllerSpec extends Specification { @SpringBean DeprecationHelper stubbedDeprecationHelper = Stub() + @SpringBean + NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub() + @Value('${rest.api.ncmp-base-path}/v1') def ncmpBasePathV1 @@ -104,21 +116,38 @@ class NetworkCmProxyControllerSpec extends Specification { def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + void setup() { + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.OPERATIONAL) >> + new NcmpDatastoreOperationalResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.PASSTHROUGH_OPERATIONAL) >> + new NcmpDatastorePassthroughOperationalResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + DatastoreType.PASSTHROUGH_RUNNING) >> + new NcmpDatastorePassthroughRunningResourceRequestHandler( + mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + } + def 'Get Resource Data from pass-through operational.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)" + "?resourceIdentifier=parent/child&options=(a=1,b=2)" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle' 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle', - 'parent/child', - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) + 'parent/child', + '(a=1,b=2)', + NO_TOPIC, + NO_REQUEST_ID) and: 'response status is Ok' response.status == HttpStatus.OK.value() } @@ -126,22 +155,22 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get Resource Data from #datastoreInUrl with #scenario.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" + "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response + get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response then: 'task executor is called appropriate number of times' - expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, 2000) + expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS) and: 'response status is expected' response.status == HttpStatus.OK.value() where: 'the following parameters are used' - scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions - 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1 - 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0 - 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1 - 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1 - 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0 - 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1 + scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions + 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1 + 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0 + 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1 + 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1 + 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0 + 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1 } def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() { @@ -168,17 +197,17 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" + "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" and: 'ncmp service returns json object' mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle', - resourceIdentifier, - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) >> '{valid-json}' + resourceIdentifier, + '(a=1,b=2)', + NO_TOPIC, + NO_REQUEST_ID) >> '{valid-json}' when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'response status is Ok' response.status == HttpStatus.OK.value() @@ -194,7 +223,7 @@ class NetworkCmProxyControllerSpec extends Specification { '? needs to be encoded as %3F' | 'idWith%3F' } - def 'Update resource data from pass-through running.' () { + def 'Update resource data from pass-through running.'() { given: 'update resource data url' def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + "?resourceIdentifier=parent/child" @@ -210,15 +239,14 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.OK.value() } - def 'Create Resource Data from pass-through running with #scenario.' () { + def 'Create Resource Data from pass-through running with #scenario.'() { given: 'resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" - def requestBody = '{"some-key":"some-value"}' + "?resourceIdentifier=parent/child" when: 'create resource request is performed' def response = mvc.perform( - post(url) - .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) + post(url) + .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) ).andReturn().response then: 'ncmp service method to create resource called' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', @@ -227,14 +255,14 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.CREATED.value() } - def 'Get module references for the given dataspace and cm handle.' () { + def 'Get module references for the given dataspace and cm handle.'() { given: 'get module references url' def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules" when: 'get module resource request is performed' - def response =mvc.perform(get(getUrl)).andReturn().response + def response = mvc.perform(get(getUrl)).andReturn().response then: 'ncmp service method to get yang resource module references is called' mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle') - >> [new ModuleReference(moduleName: 'some-name1',revision: '2021-10-03')] + >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')] and: 'response contains an array with the module name and revision' response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]' and: 'response returns an OK http code' @@ -248,15 +276,15 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns two cm handles' def cmHandle1 = new NcmpServiceCmHandle() cmHandle1.cmHandleId = 'some-cmhandle-id1' - cmHandle1.publicProperties = [color:'yellow'] + cmHandle1.publicProperties = [color: 'yellow'] def cmHandle2 = new NcmpServiceCmHandle() cmHandle2.cmHandleId = 'some-cmhandle-id2' - cmHandle2.publicProperties = [color:'green'] + cmHandle2.publicProperties = [color: 'green'] mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .content(jsonString)).andReturn().response + .contentType(MediaType.APPLICATION_JSON) + .content(jsonString)).andReturn().response then: 'response status returns OK' response.status == HttpStatus.OK.value() and: 'the expected response content is returned' @@ -268,14 +296,15 @@ class NetworkCmProxyControllerSpec extends Specification { def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle" and: 'an existing ncmp service cm handle' def cmHandleId = 'some-cm-handle' - def dmiProperties = [ prop:'some DMI property' ] - def publicProperties = [ "public prop":'some public property' ] + def dmiProperties = [prop: 'some DMI property'] + def publicProperties = ["public prop": 'some public property'] def compositeState = compositeStateTestObject() def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) and: 'the service method is invoked with the cm handle id' 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle when: 'the cm handle details api is invoked' - def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandleDetailsEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the public properties' @@ -286,30 +315,34 @@ class NetworkCmProxyControllerSpec extends Specification { !response.contentAsString.contains("some DMI property") } - def 'Get Cm Handle public properties by Cm Handle id.' () { + def 'Get Cm Handle public properties by Cm Handle id.'() { given: 'a cm handle properties endpoint' def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties" and: 'some cm handle public properties' - def publicProperties = [ 'public prop':'some public property' ] + def publicProperties = ['public prop': 'some public property'] and: 'the service method is invoked with the cm handle id returning the cm handle public properties' - 1 * mockNetworkCmProxyDataService.getCmHandlePublicProperties('some-cm-handle') >> publicProperties + 1 * mockNetworkCmProxyDataService + .getCmHandlePublicProperties('some-cm-handle') >> publicProperties when: 'the cm handle properties api is invoked' - def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the public properties' assertContainsPublicProperties(response) } - def 'Get Cm Handle composite state by Cm Handle id.' () { + def 'Get Cm Handle composite state by Cm Handle id.'() { given: 'a cm handle state endpoint' def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state" and: 'some cm handle composite state' def compositeState = compositeStateTestObject() and: 'the service method is invoked with the cm handle id returning the cm handle composite state' - 1 * mockNetworkCmProxyDataService.getCmHandleCompositeState('some-cm-handle') >> compositeState + 1 * mockNetworkCmProxyDataService + .getCmHandleCompositeState('some-cm-handle') >> compositeState when: 'the cm handle state api is invoked' - def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response + def response = mvc.perform( + get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' response.status == HttpStatus.OK.value() and: 'the response contains the cm handle state' @@ -323,13 +356,14 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns two cm handles' def cmHandel1 = new NcmpServiceCmHandle() cmHandel1.cmHandleId = 'some-cmhandle-id1' - cmHandel1.publicProperties = [color:'yellow'] + cmHandel1.publicProperties = [color: 'yellow'] def cmHandel2 = new NcmpServiceCmHandle() cmHandel2.cmHandleId = 'some-cmhandle-id2' - cmHandel2.publicProperties = [color:'green'] + cmHandel2.publicProperties = [color: 'green'] mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2] when: 'the searches api is invoked' - def response = mvc.perform(post(searchesEndpoint) + def response = mvc.perform( + post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(jsonString)).andReturn().response then: 'an empty cm handle identifier is returned' @@ -342,9 +376,10 @@ class NetworkCmProxyControllerSpec extends Specification { and: 'the service method is invoked with module names and returns cm handle ids' 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2'] when: 'the searches api is invoked' - def response = mvc.perform(post(searchesEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .content('{}')).andReturn().response + def response = mvc.perform( + post(searchesEndpoint) + .contentType(MediaType.APPLICATION_JSON) + .content('{}')).andReturn().response then: 'cm handle ids are returned' response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]' } @@ -353,37 +388,39 @@ class NetworkCmProxyControllerSpec extends Specification { when: 'the searches api is invoked' def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" def invalidInputData = '{invalidJson}' - def response = mvc.perform(post(searchesEndpoint) + def response = mvc.perform( + post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(invalidInputData)).andReturn().response then: 'BAD_REQUEST is returned' response.getStatus() == 400 } - def 'Patch resource data in pass-through running datastore.' () { + def 'Patch resource data in pass-through running datastore.'() { given: 'patch resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" when: 'patch data resource request is performed' def response = mvc.perform( - patch(url) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON).content(requestBody) + patch(url) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON).content(requestBody) ).andReturn().response then: 'ncmp service method to update resource is called' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8') + 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8') and: 'the response status is OK' response.status == HttpStatus.OK.value() } - def 'Delete resource data in pass-through running datastore.' () { + def 'Delete resource data in pass-through running datastore.'() { given: 'delete resource data url' def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + - "?resourceIdentifier=parent/child" + "?resourceIdentifier=parent/child" when: 'delete data resource request is performed' def response = mvc.perform( - delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response + delete(url) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response then: 'the ncmp service method to delete resource is called (with null as body)' 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8') @@ -394,12 +431,12 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() { given: 'resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" + - "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name" + "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name" when: 'get data resource request is performed' def response = mvc.perform( - get(getUrl) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON_VALUE) + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON_VALUE) ).andReturn().response then: 'async request id is generated' assert response.contentAsString.contains("requestId") @@ -409,32 +446,93 @@ class NetworkCmProxyControllerSpec extends Specification { ':passthrough-running' | 'passthrough-running' } - def 'Get module definitions based on cmHandleId.' () { + def 'Get module definitions based on cmHandleId.'() { when: 'get module definition request is performed' - def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions")) - .andReturn().response + def response = mvc.perform( + get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions")) + .andReturn().response then: 'ncmp service method to get module definitions is called' mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle') - >> [new ModuleDefinition('sampleModuleName', '2021-10-03', - 'module sampleModuleName{ sample module content }')] + >> [new ModuleDefinition('sampleModuleName', '2021-10-03', + 'module sampleModuleName{ sample module content }')] and: 'response contains an array with the module name, revision and content' response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]' and: 'response returns an OK http code' response.status == HttpStatus.OK.value() } - def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario' () { + def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() { when: 'the set data sync enabled request is invoked' - def response = mvc.perform(put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag)) - .andReturn().response + def response = mvc.perform( + put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag)) + .andReturn().response then: 'method to set data sync enabled is called' 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) and: 'the response returns an OK http code' response.status == HttpStatus.OK.value() where: 'the following parameters are used' - scenario | dataSyncEnabledFlag - 'enabled' | true - 'disabled' | false + scenario | dataSyncEnabledFlag + 'enabled' | true + 'disabled' | false + } + + def 'Get Resource Data from operational with or without descendants'() { + given: 'resource data url with descendants #enabled' + def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" + + "?resourceIdentifier=parent/child&include-descendants=${enabled}" + when: 'get data resource request is performed' + def response = mvc.perform( + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + ).andReturn().response + then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption' + 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle', + 'parent/child', + descendantsOption) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() + where: 'the following parameters are used' + enabled | descendantsOption + false | FetchDescendantsOption.OMIT_DESCENDANTS + true | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS + } + + def 'Attempt execute #operation rest operation on resource data with #scenario'() { + given: 'resource data url' + def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child" + when: 'selected request for data resource is performed on url' + def response = mvc.perform( + executeRestOperation(operation, url)) + .andReturn().response + then: 'the response status is as expected' + assert response.status == HttpStatus.BAD_REQUEST.value() + and: 'the response is as expected' + assert response.getContentAsString().contains(datastoreInUrl) + where: 'the following parameters are used' + scenario | operation | datastoreInUrl + 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational' + 'invalid datastore' | 'POST' | 'invalid' + 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational' + 'invalid datastore' | 'PUT' | 'invalid' + 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational' + 'invalid datastore' | 'PATCH' | 'invalid' + 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational' + 'invalid datastore' | 'DELETE' | 'invalid' + } + + def executeRestOperation(operation, url) { + if (operation == 'POST') { + return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) + } + if (operation == 'PUT') { + return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) + } + if (operation == 'PATCH') { + return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody) + } + if (operation == 'DELETE') { + return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE) + } } def dataStores() { @@ -453,7 +551,7 @@ class NetworkCmProxyControllerSpec extends Specification { } def assertContainsAll(response, assertContent) { - assertContent.forEach( string -> { assert(response.contentAsString.contains(string)) }) + assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) }) return void } @@ -476,8 +574,8 @@ class NetworkCmProxyControllerSpec extends Specification { def assertContainsPublicProperties(response) { def expectedContent = [ - '"publicCmHandleProperties":' , - '"public prop"' , + '"publicCmHandleProperties":', + '"public prop"', '"some public property"' ] return assertContainsAll(response, expectedContent) diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy new file mode 100644 index 0000000000..3f7a8a5ce2 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy @@ -0,0 +1,20 @@ +package org.onap.cps.ncmp.rest.controller.handlers + +import spock.lang.Specification + +class NcmpDatastoreResourceRequestHandlerFactorySpec extends Specification { + + def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(null, null) + + def 'Creating ncmp datastore request handlers.'() { + when: 'a ncmp datastore request handler is created for #datastoreType' + def result = objectUnderTest.getNcmpDatastoreResourceRequestHandler(datastoreType) + then: 'the result is of the expected class' + result.class == expectedClass + where: 'the following type of datastore is used' + datastoreType || expectedClass + DatastoreType.OPERATIONAL || NcmpDatastoreOperationalResourceRequestHandler + DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpDatastorePassthroughOperationalResourceRequestHandler + DatastoreType.PASSTHROUGH_RUNNING || NcmpDatastorePassthroughRunningResourceRequestHandler + } +}
\ No newline at end of file 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 ce908e7547..9d1077fd2f 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 @@ -29,9 +29,12 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper -import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper +import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper +import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException @@ -48,9 +51,7 @@ import spock.lang.Specification import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMP import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY -import static org.springframework.http.HttpStatus.BAD_REQUEST -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR -import static org.springframework.http.HttpStatus.NOT_FOUND +import static org.springframework.http.HttpStatus.* import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post @@ -78,6 +79,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean DeprecationHelper stubbedDeprecationHelper = Stub() + @SpringBean + NcmpDatastoreResourceRequestHandlerFactory mockedNcmpDatastoreResourceRequestHandlerFactory = Mock() + @Value('${rest.api.ncmp-base-path}') def basePathNcmp @@ -104,13 +108,15 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { then: 'an HTTP response is returned with correct message and details' assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails) where: - scenario | exception || expectedErrorDetails | expectedErrorMessage | expectedErrorCode - 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || sampleErrorDetails | sampleErrorMessage | INTERNAL_SERVER_ERROR - 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR - 'NCMP-client' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | BAD_REQUEST - 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null | 'DataNode not found' | NOT_FOUND - 'other' | new IllegalStateException(sampleErrorMessage) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR - 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND + scenario | exception || expectedErrorDetails | expectedErrorMessage | expectedErrorCode + 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || sampleErrorDetails | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'NCMP-client' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | BAD_REQUEST + 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null | 'DataNode not found' | NOT_FOUND + 'other' | new IllegalStateException(sampleErrorMessage) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND + 'Existing entry' | new AlreadyDefinedException('name',null) || 'name already exists' | 'Already defined exception' | CONFLICT + 'Existing entries' | new AlreadyDefinedExceptionBatch(["x[@id='abc']"]) || 'Check logs for details' | null | CONFLICT } def 'Post request with exception returns correct HTTP Status.'() { @@ -125,7 +131,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { def 'Failing DMI Request - passthrough scenario'() { given: 'failing DMI request' - setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400) , NCMP) + setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400), NCMP) when: 'the DMI request is executed' def response = performTestRequest(NCMP) then: 'NCMP service responds with 502 Bad Gateway status' @@ -150,11 +156,11 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { return mvc.perform(post("$dataNodeBaseEndpointNcmpInventory/ch").contentType(MediaType.APPLICATION_JSON).content(jsonData)).andReturn().response } - static void assertTestResponse(response, expectedStatus , expectedErrorMessage , expectedErrorDetails) { + static void assertTestResponse(response, expectedStatus, expectedErrorMessage, expectedErrorDetails) { assert response.status == expectedStatus.value() def content = new JsonSlurper().parseText(response.contentAsString) assert content['status'].toString().contains(expectedStatus.toString()) - assert content['message'].toString().contains(expectedErrorMessage) + assert expectedErrorMessage == null || content['message'].toString().contains(expectedErrorMessage) assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails) } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy new file mode 100644 index 0000000000..e626e1505d --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.util + +import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException +import spock.lang.Specification + +class TopicValidatorSpec extends Specification { + + def 'Valid topic name validation.'() { + when: 'a valid topic name is validated' + TopicValidator.validateTopicName('my-valid-topic') + then: 'no exception is thrown' + noExceptionThrown() + } + + def 'Validating invalid topic names.'() { + when: 'the invalid topic name is validated' + TopicValidator.validateTopicName(topicName) + then: 'boolean response will be returned for #scenario' + thrown(InvalidTopicException) + where: 'the following names are used' + scenario | topicName + 'empty topic' | '' + 'blank topic' | ' ' + 'invalid non empty topic' | '1_5_*_#' + } + +} 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 45dba211a3..0ea0674281 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 @@ -33,6 +33,7 @@ import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; @@ -67,6 +68,18 @@ public interface NetworkCmProxyDataService { String requestId); /** + * Get resource data for operational. + * + * @param cmHandleId cm handle identifier + * @param resourceIdentifier resource identifier + * @Link FetchDescendantsOption fetch descendants option + * @return {@code Object} resource data + */ + Object getResourceDataOperational(String cmHandleId, + String resourceIdentifier, + FetchDescendantsOption fetchDescendantsOption); + + /** * Get resource data for data store pass-through running * using dmi. * 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 5b072f35ed..3f440d65bd 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 @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.impl; +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters; @@ -57,7 +58,8 @@ 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.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; @@ -116,24 +118,32 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService final String topicParamInQuery, final String requestId) { final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId, - resourceIdentifier, - optionsParamInQuery, - DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, - requestId, topicParamInQuery); + resourceIdentifier, + optionsParamInQuery, + DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, + requestId, topicParamInQuery); return responseEntity.getBody(); } @Override + public Object getResourceDataOperational(final String cmHandleId, + final String resourceIdentifier, + final FetchDescendantsOption fetchDescendantsOption) { + return cpsDataService.getDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier, + fetchDescendantsOption); + } + + @Override public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId, final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery, final String requestId) { final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId, - resourceIdentifier, - optionsParamInQuery, - DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, - requestId, topicParamInQuery); + resourceIdentifier, + optionsParamInQuery, + DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, + requestId, topicParamInQuery); return responseEntity.getBody(); } @@ -145,7 +155,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService final String dataType) { CpsValidator.validateNameCharacters(cmHandleId); return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, operation, - requestData, dataType); + requestData, dataType); } @Override @@ -196,29 +206,29 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService * Set the data sync enabled flag, along with the data sync state * based on the data sync enabled boolean for the cm handle id provided. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param dataSyncEnabled data sync enabled flag */ @Override public void setDataSyncEnabled(final String cmHandleId, final boolean dataSyncEnabled) { CpsValidator.validateNameCharacters(cmHandleId); final CompositeState compositeState = inventoryPersistence - .getCmHandleState(cmHandleId); + .getCmHandleState(cmHandleId); if (compositeState.getDataSyncEnabled().equals(dataSyncEnabled)) { log.info("Data-Sync Enabled flag is already: {} ", dataSyncEnabled); } else if (compositeState.getCmHandleState() != CmHandleState.READY) { throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. Cm handle state is: " - + compositeState.getCmHandleState()); + + compositeState.getCmHandleState()); } else { final DataStoreSyncState dataStoreSyncState = compositeState.getDataStores() - .getOperationalDataStore().getDataStoreSyncState(); + .getOperationalDataStore().getDataStoreSyncState(); if (!dataSyncEnabled && dataStoreSyncState == DataStoreSyncState.SYNCHRONIZED) { - cpsDataService.deleteDataNode("NFP-Operational", cmHandleId, - "/netconf-state", OffsetDateTime.now()); + cpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, + "/netconf-state", OffsetDateTime.now()); } CompositeStateUtils.setDataSyncEnabledFlagWithDataSyncState(dataSyncEnabled, compositeState); inventoryPersistence.saveCmHandleState(cmHandleId, - compositeState); + compositeState); } } @@ -233,9 +243,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService final Set<NcmpServiceCmHandle> ncmpServiceCmHandles = cmHandleQueries.getCmHandlesByDmiPluginIdentifier(dmiPluginIdentifier); final Set<String> cmHandleIds = new HashSet<>(ncmpServiceCmHandles.size()); - ncmpServiceCmHandles.forEach(cmHandle -> { - cmHandleIds.add(cmHandle.getCmHandleId()); - }); + ncmpServiceCmHandles.forEach(cmHandle -> cmHandleIds.add(cmHandle.getCmHandleId())); return cmHandleIds; } @@ -262,7 +270,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) { CpsValidator.validateNameCharacters(cmHandleId); final YangModelCmHandle yangModelCmHandle = - inventoryPersistence.getYangModelCmHandle(cmHandleId); + inventoryPersistence.getYangModelCmHandle(cmHandleId); final List<YangModelCmHandle.Property> yangModelPublicProperties = yangModelCmHandle.getPublicProperties(); final Map<String, String> cmHandlePublicProperties = new HashMap<>(); YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties); @@ -290,14 +298,18 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService public List<CmHandleRegistrationResponse> parseAndCreateCmHandlesInDmiRegistrationAndSyncModules( final DmiPluginRegistration dmiPluginRegistration) { List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(); + final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(); try { - cmHandleRegistrationResponses = dmiPluginRegistration.getCreatedCmHandles().stream() - .map(cmHandle -> - YangModelCmHandle.toYangModelCmHandle( - dmiPluginRegistration.getDmiPlugin(), - dmiPluginRegistration.getDmiDataPlugin(), - dmiPluginRegistration.getDmiModelPlugin(), - cmHandle)).map(this::registerNewCmHandle).collect(Collectors.toList()); + dmiPluginRegistration.getCreatedCmHandles() + .forEach(cmHandle -> { + final YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle( + dmiPluginRegistration.getDmiPlugin(), + dmiPluginRegistration.getDmiDataPlugin(), + dmiPluginRegistration.getDmiModelPlugin(), + cmHandle); + cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED); + }); + cmHandleRegistrationResponses = registerNewCmHandles(cmHandleStatePerCmHandle); } catch (final DataValidationException dataValidationException) { cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createFailureResponse(dmiPluginRegistration .getCreatedCmHandles().stream() @@ -346,15 +358,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService inventoryPersistence.deleteListOrListElement("/dmi-registry/cm-handles[@id='" + cmHandleId + "']"); } - private CmHandleRegistrationResponse registerNewCmHandle(final YangModelCmHandle yangModelCmHandle) { + private List<CmHandleRegistrationResponse> registerNewCmHandles(final Map<YangModelCmHandle, CmHandleState> + cmHandleStatePerCmHandle) { + final List<String> cmHandleIds = cmHandleStatePerCmHandle.keySet().stream().map(YangModelCmHandle::getId) + .collect(Collectors.toList()); try { - lcmEventsCmHandleStateHandler.updateCmHandleState(yangModelCmHandle, CmHandleState.ADVISED); - return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId()); - } catch (final AlreadyDefinedException alreadyDefinedException) { - return CmHandleRegistrationResponse.createFailureResponse( - yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST); + lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); + return CmHandleRegistrationResponse.createSuccessResponses(cmHandleIds); + } catch (final AlreadyDefinedExceptionBatch alreadyDefinedExceptionBatch) { + return CmHandleRegistrationResponse.createFailureResponses( + alreadyDefinedExceptionBatch.getAlreadyDefinedXpaths(), + RegistrationError.CM_HANDLE_ALREADY_EXIST); } catch (final Exception exception) { - return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception); + return CmHandleRegistrationResponse.createFailureResponses(cmHandleIds, exception); } } } 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 d457f2601b..d5b459b025 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 @@ -56,7 +56,7 @@ public class DmiRestClient { } catch (final HttpStatusCodeException httpStatusCodeException) { final String exceptionMessage = "Unable to " + operation.toString() + " resource data."; throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(), - httpStatusCodeException.getRawStatusCode()); + httpStatusCodeException.getRawStatusCode()); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java index 45e2754222..f842ddbc1a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -46,8 +47,10 @@ import org.onap.cps.utils.CpsValidator; @Setter @NoArgsConstructor @JsonInclude(Include.NON_NULL) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class YangModelCmHandle { + @EqualsAndHashCode.Include private String id; @JsonProperty("dmi-service-name") diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java index 569e91e2c9..daabbb56fa 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java @@ -20,37 +20,14 @@ package org.onap.cps.ncmp.api.inventory; -import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle; -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; -import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; -import org.springframework.stereotype.Component; - -@RequiredArgsConstructor -@Component -public class CmHandleQueries { - - private static final String NCMP_DATASPACE_NAME = "NCMP-Admin"; - private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; - - private final CpsDataPersistenceService cpsDataPersistenceService; - private static final Map<String, NcmpServiceCmHandle> NO_QUERY_TO_EXECUTE = null; - private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; +public interface CmHandleQueries { /** * Query CmHandles based on PublicProperties. @@ -58,52 +35,17 @@ public class CmHandleQueries { * @param publicPropertyQueryPairs public properties for query * @return CmHandles which have these public properties */ - public Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties( - final Map<String, String> publicPropertyQueryPairs) { - if (publicPropertyQueryPairs.isEmpty()) { - return Collections.emptyMap(); - } - Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandles = null; - for (final Map.Entry<String, String> publicPropertyQueryPair : publicPropertyQueryPairs.entrySet()) { - final String cpsPath = "//public-properties[@name=\"" + publicPropertyQueryPair.getKey() - + "\" and @value=\"" + publicPropertyQueryPair.getValue() + "\"]"; - - final Collection<DataNode> dataNodes = queryCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS); - if (cmHandleIdToNcmpServiceCmHandles == null) { - cmHandleIdToNcmpServiceCmHandles = collectDataNodesToNcmpServiceCmHandles(dataNodes); - } else { - final Collection<String> cmHandleIdsToRetain = dataNodes.parallelStream() - .map(dataNode -> dataNode.getLeaves().get("id").toString()).collect(Collectors.toSet()); - cmHandleIdToNcmpServiceCmHandles.keySet().retainAll(cmHandleIdsToRetain); - } - if (cmHandleIdToNcmpServiceCmHandles.isEmpty()) { - break; - } - } - return cmHandleIdToNcmpServiceCmHandles; - } + Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs); /** * Combine Maps of CmHandles. * - * @param firstQuery first CmHandles Map + * @param firstQuery first CmHandles Map * @param secondQuery second CmHandles Map * @return combined Map of CmHandles */ - public Map<String, NcmpServiceCmHandle> combineCmHandleQueries( - final Map<String, NcmpServiceCmHandle> firstQuery, - final Map<String, NcmpServiceCmHandle> secondQuery) { - if (firstQuery == NO_QUERY_TO_EXECUTE && secondQuery == NO_QUERY_TO_EXECUTE) { - return NO_QUERY_TO_EXECUTE; - } else if (firstQuery == NO_QUERY_TO_EXECUTE) { - return secondQuery; - } else if (secondQuery == NO_QUERY_TO_EXECUTE) { - return firstQuery; - } else { - firstQuery.keySet().retainAll(secondQuery.keySet()); - return firstQuery; - } - } + Map<String, NcmpServiceCmHandle> combineCmHandleQueries(Map<String, NcmpServiceCmHandle> firstQuery, + Map<String, NcmpServiceCmHandle> secondQuery); /** * Method which returns cm handles by the cm handles state. @@ -111,10 +53,7 @@ public class CmHandleQueries { * @param cmHandleState cm handle state * @return a list of cm handles */ - public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) { - return queryCmHandleDataNodesByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]", - INCLUDE_ALL_DESCENDANTS); - } + List<DataNode> queryCmHandlesByState(CmHandleState cmHandleState); /** * Method to return data nodes representing the cm handles. @@ -122,49 +61,24 @@ public class CmHandleQueries { * @param cpsPath cps path for which the cmHandle is requested * @return a list of data nodes representing the cm handles. */ - public List<DataNode> queryCmHandleDataNodesByCpsPath(final String cpsPath, - final FetchDescendantsOption fetchDescendantsOption) { - return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption); - } + List<DataNode> queryCmHandleDataNodesByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption); /** * Method to check the state of a cm handle with given id. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param requiredCmHandleState the required state of the cm handle * @return a boolean, true if the state is equal to the required state */ - public boolean cmHandleHasState(final String cmHandleId, final CmHandleState requiredCmHandleState) { - final DataNode stateDataNode = getCmHandleState(cmHandleId); - final String cmHandleStateAsString = (String) stateDataNode.getLeaves().get("cm-handle-state"); - return CmHandleState.valueOf(cmHandleStateAsString).equals(requiredCmHandleState); - } + boolean cmHandleHasState(String cmHandleId, CmHandleState requiredCmHandleState); /** * Method which returns cm handles by the operational sync state of cm handle. + * * @param dataStoreSyncState sync state * @return a list of cm handles */ - public List<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) { - return queryCmHandleDataNodesByCpsPath("//state/datastores" + "/operational[@sync-state=\"" - + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS); - } - - private Map<String, NcmpServiceCmHandle> collectDataNodesToNcmpServiceCmHandles( - final Collection<DataNode> dataNodes) { - final Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandle = new HashMap<>(); - dataNodes.forEach(dataNode -> { - final NcmpServiceCmHandle ncmpServiceCmHandle = createNcmpServiceCmHandle(dataNode); - cmHandleIdToNcmpServiceCmHandle.put(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle); - }); - return cmHandleIdToNcmpServiceCmHandle; - } - - private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) { - return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter - .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())); - } + List<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState); /** * Get all cm handles by DMI plugin identifier. @@ -172,34 +86,5 @@ public class CmHandleQueries { * @param dmiPluginIdentifier DMI plugin identifier * @return set of cm handles */ - public Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(final String dmiPluginIdentifier) { - final Map<String, DataNode> cmHandleAsDataNodePerCmHandleId = new HashMap<>(); - for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) { - for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty( - dmiPluginIdentifier, - modelledDmiServiceLeaf.getLeafName())) { - cmHandleAsDataNodePerCmHandleId.put( - cmHandleAsDataNode.getLeaves().get("id").toString(), cmHandleAsDataNode); - } - } - final Set<NcmpServiceCmHandle> ncmpServiceCmHandles = new HashSet<>(cmHandleAsDataNodePerCmHandleId.size()); - cmHandleAsDataNodePerCmHandleId.values().forEach( - cmHandleAsDataNode -> ncmpServiceCmHandles.add(createNcmpServiceCmHandle(cmHandleAsDataNode))); - return ncmpServiceCmHandles; - } - - private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, - final String dmiProperty) { - return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - "/dmi-registry/cm-handles[@" + dmiProperty + "='" + dmiPluginIdentifier + "']", - OMIT_DESCENDANTS); - } - - private DataNode getCmHandleState(final String cmHandleId) { - final String xpath = "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state"; - return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - xpath, OMIT_DESCENDANTS); - } + Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(String dmiPluginIdentifier); } - - diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java new file mode 100644 index 0000000000..e9e2fcacff --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java @@ -0,0 +1,168 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.inventory; + +import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle; +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; +import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.CpsDataPersistenceService; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.model.DataNode; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class CmHandleQueriesImpl implements CmHandleQueries { + + private static final String NCMP_DATASPACE_NAME = "NCMP-Admin"; + private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; + + private final CpsDataPersistenceService cpsDataPersistenceService; + private static final Map<String, NcmpServiceCmHandle> NO_QUERY_TO_EXECUTE = null; + private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; + + + @Override + public Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties( + final Map<String, String> publicPropertyQueryPairs) { + if (publicPropertyQueryPairs.isEmpty()) { + return Collections.emptyMap(); + } + Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandles = null; + for (final Map.Entry<String, String> publicPropertyQueryPair : publicPropertyQueryPairs.entrySet()) { + final String cpsPath = "//public-properties[@name=\"" + publicPropertyQueryPair.getKey() + + "\" and @value=\"" + publicPropertyQueryPair.getValue() + "\"]"; + + final Collection<DataNode> dataNodes = queryCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS); + if (cmHandleIdToNcmpServiceCmHandles == null) { + cmHandleIdToNcmpServiceCmHandles = collectDataNodesToNcmpServiceCmHandles(dataNodes); + } else { + final Collection<String> cmHandleIdsToRetain = dataNodes.parallelStream() + .map(dataNode -> dataNode.getLeaves().get("id").toString()).collect(Collectors.toSet()); + cmHandleIdToNcmpServiceCmHandles.keySet().retainAll(cmHandleIdsToRetain); + } + if (cmHandleIdToNcmpServiceCmHandles.isEmpty()) { + break; + } + } + return cmHandleIdToNcmpServiceCmHandles; + } + + @Override + public Map<String, NcmpServiceCmHandle> combineCmHandleQueries(final Map<String, NcmpServiceCmHandle> firstQuery, + final Map<String, NcmpServiceCmHandle> secondQuery) { + if (firstQuery == NO_QUERY_TO_EXECUTE && secondQuery == NO_QUERY_TO_EXECUTE) { + return NO_QUERY_TO_EXECUTE; + } else if (firstQuery == NO_QUERY_TO_EXECUTE) { + return secondQuery; + } else if (secondQuery == NO_QUERY_TO_EXECUTE) { + return firstQuery; + } else { + firstQuery.keySet().retainAll(secondQuery.keySet()); + return firstQuery; + } + } + + @Override + public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) { + return queryCmHandleDataNodesByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]", + INCLUDE_ALL_DESCENDANTS); + } + + @Override + public List<DataNode> queryCmHandleDataNodesByCpsPath(final String cpsPath, + final FetchDescendantsOption fetchDescendantsOption) { + return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption); + } + + @Override + public boolean cmHandleHasState(final String cmHandleId, final CmHandleState requiredCmHandleState) { + final DataNode stateDataNode = getCmHandleState(cmHandleId); + final String cmHandleStateAsString = (String) stateDataNode.getLeaves().get("cm-handle-state"); + return CmHandleState.valueOf(cmHandleStateAsString).equals(requiredCmHandleState); + } + + @Override + public List<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) { + return queryCmHandleDataNodesByCpsPath("//state/datastores" + "/operational[@sync-state=\"" + + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS); + } + + private Map<String, NcmpServiceCmHandle> collectDataNodesToNcmpServiceCmHandles( + final Collection<DataNode> dataNodes) { + final Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandle = new HashMap<>(); + dataNodes.forEach(dataNode -> { + final NcmpServiceCmHandle ncmpServiceCmHandle = createNcmpServiceCmHandle(dataNode); + cmHandleIdToNcmpServiceCmHandle.put(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle); + }); + return cmHandleIdToNcmpServiceCmHandle; + } + + private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) { + return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter + .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())); + } + + @Override + public Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(final String dmiPluginIdentifier) { + final Map<String, DataNode> cmHandleAsDataNodePerCmHandleId = new HashMap<>(); + for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) { + for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty( + dmiPluginIdentifier, + modelledDmiServiceLeaf.getLeafName())) { + cmHandleAsDataNodePerCmHandleId.put( + cmHandleAsDataNode.getLeaves().get("id").toString(), cmHandleAsDataNode); + } + } + final Set<NcmpServiceCmHandle> ncmpServiceCmHandles = new HashSet<>(cmHandleAsDataNodePerCmHandleId.size()); + cmHandleAsDataNodePerCmHandleId.values().forEach( + cmHandleAsDataNode -> ncmpServiceCmHandles.add(createNcmpServiceCmHandle(cmHandleAsDataNode))); + return ncmpServiceCmHandles; + } + + private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, + final String dmiProperty) { + return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + "/dmi-registry/cm-handles[@" + dmiProperty + "='" + dmiPluginIdentifier + "']", + OMIT_DESCENDANTS); + } + + private DataNode getCmHandleState(final String cmHandleId) { + final String xpath = "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state"; + return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + xpath, OMIT_DESCENDANTS); + } +} + + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java index 9174dc7a7c..bfc3a9ac06 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java @@ -1,7 +1,6 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,57 +20,15 @@ package org.onap.cps.ncmp.api.inventory; -import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; -import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP; -import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED; -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; - -import java.time.OffsetDateTime; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsDataService; -import org.onap.cps.api.CpsModuleService; -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -import org.onap.cps.spi.CpsAdminPersistenceService; -import org.onap.cps.spi.CpsDataPersistenceService; -import org.onap.cps.spi.FetchDescendantsOption; -import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; -import org.onap.cps.utils.CpsValidator; -import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.stereotype.Component; - -@Slf4j -@RequiredArgsConstructor -@Component -public class InventoryPersistence { - - private static final String NCMP_DATASPACE_NAME = "NCMP-Admin"; - - private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; - - private static final String NCMP_DMI_REGISTRY_PARENT = "/dmi-registry"; - - private static final String CM_HANDLE_XPATH_TEMPLATE = "/dmi-registry/cm-handles[@id='" + "%s" + "']"; - private final JsonObjectMapper jsonObjectMapper; - - private final CpsDataService cpsDataService; - - private final CpsModuleService cpsModuleService; - - private final CpsDataPersistenceService cpsDataPersistenceService; - - private final CpsAdminPersistenceService cpsAdminPersistenceService; +public interface InventoryPersistence { /** * Get the Cm Handle Composite State from the data node. @@ -79,50 +36,30 @@ public class InventoryPersistence { * @param cmHandleId cm handle id * @return the cm handle composite state */ - public CompositeState getCmHandleState(final String cmHandleId) { - final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state", - FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); - return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build(); - } + CompositeState getCmHandleState(String cmHandleId); /** * Save the cm handles state. * - * @param cmHandleId cm handle id + * @param cmHandleId cm handle id * @param compositeState composite state */ - public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) { - final String cmHandleJsonData = String.format("{\"state\":%s}", - jsonObjectMapper.asJsonString(compositeState)); - cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId), - cmHandleJsonData, OffsetDateTime.now()); - } + void saveCmHandleState(String cmHandleId, CompositeState compositeState); /** * Save all cm handles states in batch. * * @param cmHandleStatePerCmHandleId contains cm handle id and updated state */ - public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) { - final Map<String, String> cmHandlesJsonDataMap = new HashMap<>(); - cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put( - String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId), - String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState)))); - cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - cmHandlesJsonDataMap, OffsetDateTime.now()); - } + void saveCmHandleStateBatch(Map<String, CompositeState> cmHandleStatePerCmHandleId); /** * This method retrieves DMI service name, DMI properties and the state for a given cm handle. + * * @param cmHandleId the id of the cm handle * @return yang model cm handle */ - public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) { - CpsValidator.validateNameCharacters(cmHandleId); - return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId); - } + YangModelCmHandle getYangModelCmHandle(String cmHandleId); /** * Method to return module definitions by cmHandleId. @@ -130,9 +67,7 @@ public class InventoryPersistence { * @param cmHandleId cm handle ID * @return a collection of module definitions (moduleName, revision and yang resource content) */ - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { - return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); - } + Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId); /** * Method to return module references by cmHandleId. @@ -140,60 +75,35 @@ public class InventoryPersistence { * @param cmHandleId cm handle ID * @return a collection of module references (moduleName and revision) */ - public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) { - CpsValidator.validateNameCharacters(cmHandleId); - return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); - } + Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandleId); /** * Method to save cmHandle. * * @param yangModelCmHandle cmHandle represented as Yang Model */ - public void saveCmHandle(final YangModelCmHandle yangModelCmHandle) { - final String cmHandleJsonData = - String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle)); - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - cmHandleJsonData, NO_TIMESTAMP); - } + void saveCmHandle(YangModelCmHandle yangModelCmHandle); /** * Method to save batch of cm handles. * * @param yangModelCmHandles cm handle represented as Yang Models */ - public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) { - final List<String> cmHandlesJsonData = new ArrayList<>(); - yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add( - String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle)))); - cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP); - } + void saveCmHandleBatch(Collection<YangModelCmHandle> yangModelCmHandles); /** * Method to delete a list or a list element. * * @param listElementXpath list element xPath */ - public void deleteListOrListElement(final String listElementXpath) { - cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - listElementXpath, NO_TIMESTAMP); - } + void deleteListOrListElement(String listElementXpath); /** * Method to delete a schema set. * * @param schemaSetName schema set name */ - public void deleteSchemaSetWithCascade(final String schemaSetName) { - try { - CpsValidator.validateNameCharacters(schemaSetName); - cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, - CASCADE_DELETE_ALLOWED); - } catch (final SchemaSetNotFoundException schemaSetNotFoundException) { - log.warn("Schema set {} does not exist or already deleted", schemaSetName); - } - } + void deleteSchemaSetWithCascade(String schemaSetName); /** * Get data node via xpath. @@ -201,10 +111,7 @@ public class InventoryPersistence { * @param xpath xpath * @return data node */ - public DataNode getDataNode(final String xpath) { - return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - xpath, INCLUDE_ALL_DESCENDANTS); - } + DataNode getDataNode(String xpath); /** * Get data node of given cm handle. @@ -212,9 +119,7 @@ public class InventoryPersistence { * @param cmHandleId cmHandle ID * @return data node */ - public DataNode getCmHandleDataNode(final String cmHandleId) { - return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId)); - } + DataNode getCmHandleDataNode(String cmHandleId); /** * Query anchors via module names. @@ -222,37 +127,27 @@ public class InventoryPersistence { * @param moduleNamesForQuery module names * @return Collection of anchors */ - public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) { - return cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); - } + Collection<Anchor> queryAnchors(Collection<String> moduleNamesForQuery); /** * Method to get all anchors. * * @return Collection of anchors */ - public Collection<Anchor> getAnchors() { - return cpsAdminPersistenceService.getAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME); - } + Collection<Anchor> getAnchors(); /** * Replaces list content by removing all existing elements and inserting the given new elements as data nodes. * - * @param parentNodeXpath parent node xpath - * @param dataNodes datanodes representing the updated data + * @param parentNodeXpath parent node xpath + * @param dataNodes datanodes representing the updated data */ - public void replaceListContent(final String parentNodeXpath, final Collection<DataNode> dataNodes) { - cpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - parentNodeXpath, dataNodes, NO_TIMESTAMP); - } + void replaceListContent(String parentNodeXpath, Collection<DataNode> dataNodes); /** * Deletes data node for given anchor and dataspace. * * @param dataNodeXpath data node xpath */ - public void deleteDataNode(final String dataNodeXpath) { - cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpath, - NO_TIMESTAMP); - } -}
\ No newline at end of file + void deleteDataNode(String dataNodeXpath); +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java new file mode 100644 index 0000000000..99edfdb0f1 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java @@ -0,0 +1,186 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * 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.inventory; + +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP; +import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED; +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsModuleService; +import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.spi.CpsAdminPersistenceService; +import org.onap.cps.spi.CpsDataPersistenceService; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; +import org.onap.cps.spi.model.Anchor; +import org.onap.cps.spi.model.DataNode; +import org.onap.cps.spi.model.ModuleDefinition; +import org.onap.cps.spi.model.ModuleReference; +import org.onap.cps.utils.CpsValidator; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class InventoryPersistenceImpl implements InventoryPersistence { + + private static final String NCMP_DATASPACE_NAME = "NCMP-Admin"; + + private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; + + private static final String NCMP_DMI_REGISTRY_PARENT = "/dmi-registry"; + + private static final String CM_HANDLE_XPATH_TEMPLATE = "/dmi-registry/cm-handles[@id='" + "%s" + "']"; + + private final JsonObjectMapper jsonObjectMapper; + + private final CpsDataService cpsDataService; + + private final CpsModuleService cpsModuleService; + + private final CpsDataPersistenceService cpsDataPersistenceService; + + private final CpsAdminPersistenceService cpsAdminPersistenceService; + + @Override + public CompositeState getCmHandleState(final String cmHandleId) { + final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state", + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); + return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build(); + } + + @Override + public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) { + final String cmHandleJsonData = String.format("{\"state\":%s}", + jsonObjectMapper.asJsonString(compositeState)); + cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId), + cmHandleJsonData, OffsetDateTime.now()); + } + + @Override + public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) { + final Map<String, String> cmHandlesJsonDataMap = new HashMap<>(); + cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put( + String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId), + String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState)))); + cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cmHandlesJsonDataMap, OffsetDateTime.now()); + } + + @Override + public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) { + CpsValidator.validateNameCharacters(cmHandleId); + return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId); + } + + @Override + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { + return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); + } + + @Override + public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) { + CpsValidator.validateNameCharacters(cmHandleId); + return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); + } + + @Override + public void saveCmHandle(final YangModelCmHandle yangModelCmHandle) { + final String cmHandleJsonData = + String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle)); + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, + cmHandleJsonData, NO_TIMESTAMP); + } + + @Override + public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) { + final List<String> cmHandlesJsonData = new ArrayList<>(); + yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add( + String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle)))); + cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP); + } + + @Override + public void deleteListOrListElement(final String listElementXpath) { + cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + listElementXpath, NO_TIMESTAMP); + } + + @Override + public void deleteSchemaSetWithCascade(final String schemaSetName) { + try { + CpsValidator.validateNameCharacters(schemaSetName); + cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, + CASCADE_DELETE_ALLOWED); + } catch (final SchemaSetNotFoundException schemaSetNotFoundException) { + log.warn("Schema set {} does not exist or already deleted", schemaSetName); + } + } + + @Override + public DataNode getDataNode(final String xpath) { + return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + xpath, INCLUDE_ALL_DESCENDANTS); + } + + @Override + public DataNode getCmHandleDataNode(final String cmHandleId) { + return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId)); + } + + @Override + public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) { + return cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + } + + @Override + public Collection<Anchor> getAnchors() { + return cpsAdminPersistenceService.getAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME); + } + + @Override + public void replaceListContent(final String parentNodeXpath, final Collection<DataNode> dataNodes) { + cpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + parentNodeXpath, dataNodes, NO_TIMESTAMP); + } + + @Override + public void deleteDataNode(final String dataNodeXpath) { + cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpath, + NO_TIMESTAMP); + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java index 7f61c476d5..7efce1ad52 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java @@ -23,9 +23,8 @@ package org.onap.cps.ncmp.api.inventory.sync; import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAdminService; @@ -54,33 +53,28 @@ public class ModuleSyncService { */ public void syncAndCreateSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle) { - final Collection<ModuleReference> moduleReferencesFromCmHandle = + final Collection<ModuleReference> allModuleReferencesFromCmHandle = dmiModelOperations.getModuleReferences(yangModelCmHandle); final Collection<ModuleReference> identifiedNewModuleReferencesFromCmHandle = cpsModuleService - .identifyNewModuleReferences(moduleReferencesFromCmHandle); - - final Collection<ModuleReference> existingModuleReferencesFromCmHandle = - moduleReferencesFromCmHandle.stream().filter(moduleReferenceFromCmHandle -> - !identifiedNewModuleReferencesFromCmHandle.contains(moduleReferenceFromCmHandle) - ).collect(Collectors.toList()); + .identifyNewModuleReferences(allModuleReferencesFromCmHandle); final Map<String, String> newModuleNameToContentMap; if (identifiedNewModuleReferencesFromCmHandle.isEmpty()) { - newModuleNameToContentMap = new HashMap<>(); + newModuleNameToContentMap = Collections.emptyMap(); } else { newModuleNameToContentMap = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferencesFromCmHandle); } - createSchemaSetAndAnchor(yangModelCmHandle, newModuleNameToContentMap, existingModuleReferencesFromCmHandle); + createSchemaSetAndAnchor(yangModelCmHandle, newModuleNameToContentMap, allModuleReferencesFromCmHandle); } private void createSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle, final Map<String, String> newModuleNameToContentMap, - final Collection<ModuleReference> existingModuleReferencesFromCmHandle) { + final Collection<ModuleReference> allModuleReferencesFromCmHandle) { final String schemaSetAndAnchorName = yangModelCmHandle.getId(); cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName, - newModuleNameToContentMap, existingModuleReferencesFromCmHandle); + newModuleNameToContentMap, allModuleReferencesFromCmHandle); cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName, schemaSetAndAnchorName); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasks.java index 597e2ba8e5..f914547a50 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasks.java @@ -71,6 +71,7 @@ public class ModuleSyncTasks { moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY); } catch (final Exception e) { + log.warn("Processing module sync batch failed."); syncUtils.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, e.getMessage()); setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); @@ -78,9 +79,10 @@ public class ModuleSyncTasks { } log.debug("{} is now in {} state", cmHandleId, compositeState.getCmHandleState().name()); } - updateCmHandlesStateBatch(cmHandelStatePerCmHandle); + lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandelStatePerCmHandle); } finally { batchCounter.getAndDecrement(); + log.info("Processing module sync batch finished. {} batch(es) active.", batchCounter.get()); } return COMPLETED_FUTURE; } @@ -98,11 +100,11 @@ public class ModuleSyncTasks { final boolean isReadyForRetry = syncUtils.isReadyForRetry(compositeState); if (isReadyForRetry) { log.debug("Reset cm handle {} state to ADVISED to be re-attempted by module-sync watchdog", - failedCmHandle.getId()); + failedCmHandle.getId()); cmHandleStatePerCmHandle.put(failedCmHandle, CmHandleState.ADVISED); } } - updateCmHandlesStateBatch(cmHandleStatePerCmHandle); + lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); return COMPLETED_FUTURE; } @@ -111,11 +113,4 @@ public class ModuleSyncTasks { advisedCmHandle.getCompositeState().setLockReason(lockReason); } - private void updateCmHandlesStateBatch(final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle) { - // To be refactored as part of CPS-1231; Use state-save-batch capability (depends sub-task12, 13) - for (final Map.Entry<YangModelCmHandle, CmHandleState> entry : cmHandleStatePerCmHandle.entrySet()) { - lcmEventsCmHandleStateHandler.updateCmHandleState(entry.getKey(), entry.getValue()); - } - } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java index 73954c36b3..64d111f993 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java @@ -62,16 +62,22 @@ public class ModuleSyncWatchdog { */ @Scheduled(fixedDelayString = "${timers.advised-modules-sync.sleep-time-ms:5000}") public void moduleSyncAdvisedCmHandles() { + log.info("Processing module sync watchdog waking up."); populateWorkQueueIfNeeded(); final int asyncTaskParallelismLevel = asyncTaskExecutor.getAsyncTaskParallelismLevel(); - while (!moduleSyncWorkQueue.isEmpty() && batchCounter.get() <= asyncTaskParallelismLevel) { - batchCounter.getAndIncrement(); - final Collection<DataNode> nextBatch = prepareNextBatch(); - asyncTaskExecutor.executeTask(() -> - moduleSyncTasks.performModuleSync(nextBatch, batchCounter), - ASYNC_TASK_TIMEOUT_IN_MILLISECONDS - ); - preventBusyWait(); + while (!moduleSyncWorkQueue.isEmpty()) { + if (batchCounter.get() <= asyncTaskParallelismLevel) { + final Collection<DataNode> nextBatch = prepareNextBatch(); + log.debug("Processing module sync batch of {}. {} batch(es) active.", + nextBatch.size(), batchCounter.get()); + asyncTaskExecutor.executeTask(() -> + moduleSyncTasks.performModuleSync(nextBatch, batchCounter), + ASYNC_TASK_TIMEOUT_IN_MILLISECONDS + ); + batchCounter.getAndIncrement(); + } else { + preventBusyWait(); + } } } @@ -80,6 +86,7 @@ public class ModuleSyncWatchdog { */ @Scheduled(fixedDelayString = "${timers.locked-modules-sync.sleep-time-ms:300000}") public void resetPreviouslyFailedCmHandles() { + log.info("Processing module sync retry-watchdog waking up."); final List<YangModelCmHandle> failedCmHandles = syncUtils.getModuleSyncFailedCmHandles(); moduleSyncTasks.resetFailedCmHandles(failedCmHandles); } @@ -95,6 +102,7 @@ public class ModuleSyncWatchdog { private void populateWorkQueueIfNeeded() { if (moduleSyncWorkQueue.isEmpty()) { final List<DataNode> advisedCmHandles = syncUtils.getAdvisedCmHandles(); + log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size()); for (final DataNode advisedCmHandle : advisedCmHandles) { if (!moduleSyncWorkQueue.offer(advisedCmHandle)) { log.warn("Unable to add cm handle {} to the work queue", advisedCmHandle.getLeaves().get("id")); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java index 1da2aa9430..d5b27b61f6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java @@ -21,12 +21,20 @@ package org.onap.cps.ncmp.api.models; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import lombok.Builder; import lombok.Data; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Data @Builder +@Slf4j public class CmHandleRegistrationResponse { private final String cmHandle; @@ -34,16 +42,19 @@ public class CmHandleRegistrationResponse { private RegistrationError registrationError; private String errorText; + private static final Pattern cmHandleIdInXpathPattern = Pattern.compile("\\[@id='(.*?)']"); + /** * Creates a failure response based on exception. * - * @param cmHandle cmHandle + * @param cmHandleId cmHandleId * @param exception exception * @return CmHandleRegistrationResponse */ - public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, final Exception exception) { + public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId, + final Exception exception) { return CmHandleRegistrationResponse.builder() - .cmHandle(cmHandle) + .cmHandle(cmHandleId) .status(Status.FAILURE) .registrationError(RegistrationError.UNKNOWN_ERROR) .errorText(exception.getMessage()).build(); @@ -52,24 +63,65 @@ public class CmHandleRegistrationResponse { /** * Creates a failure response based on registration error. * - * @param cmHandle cmHandle + * @param cmHandleId cmHandleId * @param registrationError registrationError * @return CmHandleRegistrationResponse */ - public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, + public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId, final RegistrationError registrationError) { - return CmHandleRegistrationResponse.builder().cmHandle(cmHandle) + return CmHandleRegistrationResponse.builder().cmHandle(cmHandleId) .status(Status.FAILURE) .registrationError(registrationError) .errorText(registrationError.errorText) .build(); } + /** + * Creates a failure response based on registration error. + * + * @param failedXpaths list of failed Xpaths + * @param registrationError enum describing the type of registration error + * @return CmHandleRegistrationResponse + */ + public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> failedXpaths, + final RegistrationError registrationError) { + final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(failedXpaths.size()); + for (final String xpath : failedXpaths) { + final Matcher matcher = cmHandleIdInXpathPattern.matcher(xpath); + if (matcher.find()) { + cmHandleRegistrationResponses.add( + CmHandleRegistrationResponse.createFailureResponse(matcher.group(1), registrationError)); + } else { + log.warn("Unexpected xpath {}", xpath); + } + } + return cmHandleRegistrationResponses; + } + + /** + * Creates a failure response based on other exception. + * + * @param cmHandleIds list of failed cmHandleIds + * @param exception exception caught during the registration + * @return CmHandleRegistrationResponse + */ + public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> cmHandleIds, + final Exception exception) { + return cmHandleIds.stream() + .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, exception)) + .collect(Collectors.toList()); + } + public static CmHandleRegistrationResponse createSuccessResponse(final String cmHandle) { return CmHandleRegistrationResponse.builder().cmHandle(cmHandle) .status(Status.SUCCESS).build(); } + public static List<CmHandleRegistrationResponse> createSuccessResponses(final List<String> cmHandleIds) { + return cmHandleIds.stream().map(CmHandleRegistrationResponse::createSuccessResponse) + .collect(Collectors.toList()); + } + public enum Status { SUCCESS, FAILURE; } @@ -85,4 +137,4 @@ public class CmHandleRegistrationResponse { public final String errorText; } -}
\ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy index f1294ced7a..f76316f9cc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy @@ -21,8 +21,8 @@ package org.onap.cps.ncmp.api.impl import org.onap.cps.cpspath.parser.PathParsingException -import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.inventory.CmHandleQueries +import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataInUseException diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index ed985ec000..0b58d44191 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -28,14 +28,13 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler import org.onap.cps.ncmp.api.impl.exception.DmiRequestException import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleQueries import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.SchemaSetNotFoundException @@ -159,12 +158,15 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { assert it.cmHandle == 'cmhandle' } and: 'state handler is invoked with the expected parameters' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, _) >> { - args -> { - def result = (args[0] as YangModelCmHandle) - assert result.id == 'cmhandle' - assert result.dmiServiceName == 'my-server' - assert CmHandleState.ADVISED == (args[1] as CmHandleState) + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { + args -> + { + def cmHandleStatePerCmHandle = (args[0] as Map) + cmHandleStatePerCmHandle.each { + assert (it.key.id == 'cmhandle' + && it.key.dmiServiceName == 'my-server' + && it.value == CmHandleState.ADVISED) + } } } where: @@ -173,36 +175,27 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'with only public properties' | [:] | ['public-key': 'public-value'] || '[]' | '[{"name":"public-key","value":"public-value"}]' 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[]' 'without dmi & public properties' | [:] | [:] || '[]' | '[]' - } - def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() { + def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed with some failures'() { given: 'a registration with three cm-handles to be created' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), - new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), - new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) + createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' - mockLcmEventsCmHandleStateHandler.updateCmHandleState(*_) >> {} >> { throw new RuntimeException("Failed") } >> {} + def xpath = "somePathWithId[@id='cmhandle2']" + mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new AlreadyDefinedExceptionBatch([xpath]) } when: 'registration is updated to create cm-handles' def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) then: 'a response is received for all cm-handles' - response.getCreatedCmHandles().size() == 3 - and: '1st and 3rd cm-handle are created successfully' - with(response.getCreatedCmHandles().get(0)) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle1' - } - with(response.getCreatedCmHandles().get(2)) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle3' - } - and: '2nd cm-handle creation fails' - with(response.getCreatedCmHandles().get(1)) { - assert it.status == Status.FAILURE - assert it.registrationError == UNKNOWN_ERROR - assert it.errorText == 'Failed' + response.getCreatedCmHandles().size() == 1 + and: 'all cm-handles creation fails' + response.getCreatedCmHandles().each { assert it.cmHandle == 'cmhandle2' + assert it.status == Status.FAILURE + assert it.registrationError == CM_HANDLE_ALREADY_EXIST + assert it.errorText == 'cm-handle already exists' } } @@ -211,7 +204,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId)] and: 'cm-handler registration fails: #scenario' - mockLcmEventsCmHandleStateHandler.updateCmHandleState(*_) >> { throw exception } + mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw exception } when: 'registration is updated' def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) then: 'a failure response is received' @@ -223,10 +216,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { assert it.errorText == expectedErrorText } where: - scenario | cmHandleId | exception || expectedError | expectedErrorText - 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' - 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' - 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + scenario | cmHandleId | exception || expectedError | expectedErrorText + 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedExceptionBatch(["path[@id='${cmHandleId}']".toString()]) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' + 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' + 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } def 'Update CM-Handle: Update Operation Response is added to the response'() { 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 02cfb152cc..def0db32d9 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 @@ -277,17 +277,20 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def 'Verify modules and create anchor params'() { given: 'dmi plugin registration return created cm handles' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1', - dmiDataPlugin: 'service2') + dmiDataPlugin: 'service2') dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle] when: 'parse and create cm handle in dmi registration then sync module' objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration) then: 'system persists the cm handle state' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, _) >> { - args -> { - def result = (args[0] as YangModelCmHandle) - assert result.id == 'test-cm-handle-id' - assert CmHandleState.ADVISED == (args[1] as CmHandleState) + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { + args -> + { + def cmHandleStatePerCmHandle = (args[0] as Map) + cmHandleStatePerCmHandle.each { + assert (it.key.id == 'test-cm-handle-id' + && it.value == CmHandleState.ADVISED) + } } } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy index 26b3613cd9..752b9f3ec2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy @@ -29,10 +29,10 @@ import spock.lang.Specification import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS -class CmHandleQueriesSpec extends Specification { +class CmHandleQueriesImplSpec extends Specification { def cpsDataPersistenceService = Mock(CpsDataPersistenceService) - def objectUnderTest = new CmHandleQueries(cpsDataPersistenceService) + def objectUnderTest = new CmHandleQueriesImpl(cpsDataPersistenceService) @Shared def static sampleDataNodes = [new DataNode()] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy index 19c8ae81ce..0d459fd0fa 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy @@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -class InventoryPersistenceSpec extends Specification { +class InventoryPersistenceImplSpec extends Specification { def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) @@ -56,7 +56,7 @@ class InventoryPersistenceSpec extends Specification { def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService) - def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, + def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, mockCpsDataPersistenceService, mockCpsAdminPersistenceService) def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy index 78da7eb747..3c4c6f554f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy @@ -47,8 +47,7 @@ class ModuleSyncServiceSpec extends Specification { ncmpServiceCmHandle.cmHandleId = 'cmHandleId-1' def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle) and: 'DMI operations returns some module references' - def moduleReferences = [ new ModuleReference(moduleName:'module1',revision:'1'), - new ModuleReference(moduleName:'module2',revision:'2') ] + def moduleReferences = [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ] mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences and: 'CPS-Core returns list of existing module resources' mockCpsModuleService.getYangResourceModuleReferences(expectedDataspaceName) >> toModuleReference(existingModuleResourcesInCps) @@ -58,14 +57,14 @@ class ModuleSyncServiceSpec extends Specification { mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> toModuleReference(identifiedNewModuleReferences) objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle) then: 'create schema set from module is invoked with correct parameters' - 1 * mockCpsModuleService.createSchemaSetFromModules('NFP-Operational', 'cmHandleId-1', newModuleNameContentToMap, existingModuleReferencesInCps) + 1 * mockCpsModuleService.createSchemaSetFromModules('NFP-Operational', 'cmHandleId-1', newModuleNameContentToMap, moduleReferences) and: 'anchor is created with the correct parameters' 1 * mockCpsAdminService.createAnchor('NFP-Operational', 'cmHandleId-1', 'cmHandleId-1') where: 'the following parameters are used' - scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap | existingModuleReferencesInCps - 'one new module' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']] | [module1: 'some yang source'] | [new ModuleReference(moduleName:'module2',revision:'2')] - 'no add. properties' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']] | [module1: 'some yang source'] | [new ModuleReference(moduleName:'module2',revision:'2')] - 'no new module' | [['module1' : '1'], ['module2' : '2']] | [] | [:] | [new ModuleReference(moduleName:'module1',revision:'1'), new ModuleReference(moduleName:'module2',revision:'2')] + scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap + 'one new module' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']] | [module1: 'some yang source'] + 'no add. properties' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']] | [module1: 'some yang source'] + 'no new module' | [['module1' : '1'], ['module2' : '2']] | [] | [:] } def 'Delete Schema Set for CmHandle' () { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy index a2339963e3..67fb89dbbe 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.inventory.sync import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.ncmp.api.impl.utils.YangDataConverter import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState @@ -61,7 +62,9 @@ class ModuleSyncTasksSpec extends Specification { 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-2') } and: 'the state handler is called for the both cm handles' - 2 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.READY) + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> + assertBatch(args, ['cm-handle-1', 'cm-handle-2'], CmHandleState.READY) + } and: 'batch count is decremented by one' assert batchCount.get() == 4 } @@ -79,7 +82,9 @@ class ModuleSyncTasksSpec extends Specification { then: 'update lock reason, details and attempts is invoked' 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, 'some exception') and: 'the state handler is called to update the state to LOCKED' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.LOCKED) + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> + assertBatch(args, ['cm-handle'], CmHandleState.LOCKED) + } and: 'batch count is decremented by one' assert batchCount.get() == 4 } @@ -95,7 +100,7 @@ class ModuleSyncTasksSpec extends Specification { when: 'resetting failed cm handles' objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2]) then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry' - expectedNumberOfInvocationsToSaveCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.ADVISED) +// expectedNumberOfInvocationsToSaveCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.ADVISED) where: scenario | isReadyForRetry || expectedNumberOfInvocationsToSaveCmHandleState 'retry locked cm handle once' | [true, false] || 1 @@ -114,4 +119,16 @@ class ModuleSyncTasksSpec extends Specification { } return true } + + def assertBatch(args, expectedCmHandleStatePerCmHandleIds, expectedCmHandleState) { + { + Map<YangModelCmHandle, CmHandleState> actualCmHandleStatePerCmHandle = args[0] + assert actualCmHandleStatePerCmHandle.size() == expectedCmHandleStatePerCmHandleIds.size() + actualCmHandleStatePerCmHandle.each { + assert expectedCmHandleStatePerCmHandleIds.contains(it.key.id) + assert it.value == expectedCmHandleState + } + } + return true + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy index e5240c0e66..dd989bf676 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -52,20 +52,19 @@ class ModuleSyncWatchdogSpec extends Specification { def 'Module sync advised cm handles with #scenario.'() { given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) - and: 'the executor has #parallelismLevel available threads' - spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> parallelismLevel + and: 'the executor has enough available threads' + spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 when: ' module sync is started' objectUnderTest.moduleSyncAdvisedCmHandles() then: 'it performs #expectedNumberOfTaskExecutions tasks' expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) where: ' the following parameter are used' - scenario | parallelismLevel | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions - 'less then 1 batch' | 9 | 1 || 1 - 'exactly 1 batch' | 9 | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 - '2 batches' | 9 | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 - 'queue capacity' | 9 | testQueueCapacity || 3 - 'over queue capacity' | 9 | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 - 'not enough threads' | 2 | testQueueCapacity || 2 + scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions + 'less then 1 batch' | 1 || 1 + 'exactly 1 batch' | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 + '2 batches' | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 + 'queue capacity' | testQueueCapacity || 3 + 'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 } def 'Reset failed cm handles.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy index 6ccdcf12d3..f4176d6212 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy @@ -26,12 +26,10 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations import org.onap.cps.ncmp.api.impl.operations.DmiOperations import org.onap.cps.ncmp.api.inventory.CmHandleQueries -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder import org.onap.cps.ncmp.api.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy index 4476998d82..dba29343e9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy @@ -24,6 +24,8 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status import spock.lang.Specification +import java.util.stream.Collectors + class CmHandleRegistrationResponseSpec extends Specification { def 'Successful cm-handle Registration Response'() { @@ -68,4 +70,25 @@ class CmHandleRegistrationResponseSpec extends Specification { 'cm-handle id is invalid' | 'cm handle' | RegistrationError.CM_HANDLE_INVALID_ID } + def 'Failed cm-handle Registration with multiple responses.'() { + when: 'cm-handle failure response is created for 2 xpaths' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","somePathWithId[@id='456']"], RegistrationError.CM_HANDLE_ALREADY_EXIST) + then: 'the response has the correct cm handle ids' + assert cmHandleRegistrationResponses.size() == 2 + assert cmHandleRegistrationResponses.stream().map(it -> it.cmHandle).collect(Collectors.toList()) + .containsAll(['123','456']) + } + + def 'Failed cm-handle Registration with multiple responses with an unexpected xpath.'() { + when: 'cm-handle failure response is created for one valid and one unexpected xpath' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","valid/xpath/without-id[@key='123']"], RegistrationError.CM_HANDLE_ALREADY_EXIST) + then: 'the response has only one entry' + assert cmHandleRegistrationResponses.size() == 1 + } + + + + } diff --git a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java index 9495b3d9e6..93233d9c47 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java @@ -31,6 +31,7 @@ import org.onap.cps.rest.controller.DataRestController; import org.onap.cps.rest.controller.QueryRestController; import org.onap.cps.rest.model.ErrorMessage; import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.CpsAdminException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.CpsPathException; @@ -75,7 +76,7 @@ public class CpsRestExceptionHandler { ? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, exception); } - @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class}) + @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class}) public static ResponseEntity<Object> handleDataInUseException(final Exception exception) { return buildErrorResponse(HttpStatus.CONFLICT, exception); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 61e1d5b569..c13422dc4d 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -52,6 +52,7 @@ import org.onap.cps.spi.entities.FragmentEntity; import org.onap.cps.spi.entities.SchemaSetEntity; import org.onap.cps.spi.entities.YangResourceEntity; import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.ConcurrencyException; import org.onap.cps.spi.exceptions.CpsAdminException; import org.onap.cps.spi.exceptions.CpsPathException; @@ -88,48 +89,82 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$"); @Override - @Transactional public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath, final DataNode newChildDataNode) { - addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode)); + addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode); } @Override - @Transactional public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection<DataNode> newListElements) { - addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements); + addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements); } @Override - @Transactional - public void addListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection<Collection<DataNode>> newListsElements) { + public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection<Collection<DataNode>> newLists) { + final Collection<String> failedXpaths = new HashSet<>(); + newLists.forEach(newList -> { + try { + addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList); + } catch (final AlreadyDefinedExceptionBatch e) { + failedXpaths.addAll(e.getAlreadyDefinedXpaths()); + } + }); - newListsElements.forEach( - newListElement -> addListElements(dataspaceName, anchorName, parentNodeXpath, newListElement)); + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); + } } - private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection<DataNode> newChildren) { + private void addNewChildDataNode(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final DataNode newChild) { final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final FragmentEntity newChildAsFragmentEntity = + convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), + parentFragmentEntity.getAnchor(), newChild); + newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId()); + try { + fragmentRepository.save(newChildAsFragmentEntity); + } catch (final DataIntegrityViolationException e) { + throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e); + } + + } + + private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection<DataNode> newChildren) { + final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size()); try { - final List<FragmentEntity> fragmentEntities = new ArrayList<>(); newChildren.forEach(newChildAsDataNode -> { - final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants( - parentFragmentEntity.getDataspace(), - parentFragmentEntity.getAnchor(), - newChildAsDataNode); + final FragmentEntity newChildAsFragmentEntity = + convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), + parentFragmentEntity.getAnchor(), newChildAsDataNode); newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId()); fragmentEntities.add(newChildAsFragmentEntity); }); fragmentRepository.saveAll(fragmentEntities); - } catch (final DataIntegrityViolationException exception) { - final List<String> conflictXpaths = newChildren.stream() - .map(DataNode::getXpath) - .collect(Collectors.toList()); - throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception); + } catch (final DataIntegrityViolationException e) { + log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations", + e, fragmentEntities.size()); + retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren); + } + } + + private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final Collection<DataNode> newChildren) { + final Collection<String> failedXpaths = new HashSet<>(); + for (final DataNode newChild : newChildren) { + try { + addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild); + } catch (final AlreadyDefinedException e) { + failedXpaths.add(newChild.getXpath()); + } + } + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); } } @@ -199,8 +234,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } catch (final PathParsingException e) { throw new CpsPathException(e.getMessage()); } - return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, - normalizedXpath); + + return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath); } } @@ -310,8 +345,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } catch (final StaleStateException staleStateException) { throw new ConcurrencyException("Concurrent Transactions", String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.", - dataspaceName, anchorName, dataNode.getXpath()), - staleStateException); + dataspaceName, anchorName, dataNode.getXpath())); } } @@ -319,6 +353,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName, final List<DataNode> dataNodes) { + final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream() .collect(Collectors.toMap( dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()))); @@ -327,10 +362,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService try { fragmentRepository.saveAll(dataNodeFragmentEntityMap.values()); } catch (final StaleStateException staleStateException) { - throw new ConcurrencyException("Concurrent Transactions", - String.format("A data node in dataspace :'%s' with Anchor : '%s' is updated by another transaction.", - dataspaceName, anchorName), - staleStateException); + retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values()); + } + } + + private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName, + final Collection<FragmentEntity> fragmentEntities) { + final Collection<String> failedXpaths = new HashSet<>(); + + fragmentEntities.forEach(dataNodeFragment -> { + try { + fragmentRepository.save(dataNodeFragment); + } catch (final StaleStateException e) { + failedXpaths.add(dataNodeFragment.getXpath()); + } + }); + + if (!failedXpaths.isEmpty()) { + final String failedXpathsConcatenated = String.join(",", failedXpaths); + throw new ConcurrencyException("Concurrent Transactions", String.format( + "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.", + failedXpathsConcatenated, dataspaceName, anchorName)); } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java index e9e945a49e..400e9b3e83 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java @@ -162,14 +162,14 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ @Backoff(random = true, delay = 200, maxDelay = 2000, multiplier = 2)) public void storeSchemaSetFromModules(final String dataspaceName, final String schemaSetName, final Map<String, String> newModuleNameToContentMap, - final Collection<ModuleReference> moduleReferences) { + final Collection<ModuleReference> allModuleReferences) { storeSchemaSet(dataspaceName, schemaSetName, newModuleNameToContentMap); final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final SchemaSetEntity schemaSetEntity = schemaSetRepository.getByDataspaceAndName(dataspaceEntity, schemaSetName); - final List<Long> listOfYangResourceIds = - yangResourceRepository.getResourceIdsByModuleReferences(moduleReferences); - yangResourceRepository.insertSchemaSetIdYangResourceId(schemaSetEntity.getId(), listOfYangResourceIds); + final List<Long> allYangResourceIds = + yangResourceRepository.getResourceIdsByModuleReferences(allModuleReferences); + yangResourceRepository.insertSchemaSetIdYangResourceId(schemaSetEntity.getId(), allYangResourceIds); } @Override diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java index f07f7f8d5b..654c1c0854 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java @@ -20,7 +20,6 @@ package org.onap.cps.spi.repository; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,16 +28,18 @@ import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.CpsPathPrefixType; import org.onap.cps.cpspath.parser.CpsPathQuery; import org.onap.cps.spi.entities.FragmentEntity; import org.onap.cps.utils.JsonObjectMapper; @RequiredArgsConstructor +@Slf4j public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery { - public static final String SIMILAR_TO_ABSOLUTE_PATH_PREFIX = "%/"; - public static final String SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[[^/]*])?"; + public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/"; + public static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$"; @PersistenceContext private EntityManager entityManager; @@ -50,8 +51,8 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId"); final Map<String, Object> queryParameters = new HashMap<>(); queryParameters.put("anchorId", anchorId); - sqlStringBuilder.append(" AND xpath SIMILAR TO :xpathRegex"); - final String xpathRegex = getSimilarToXpathSqlRegex(cpsPathQuery); + sqlStringBuilder.append(" AND xpath ~ :xpathRegex"); + final String xpathRegex = getXpathSqlRegex(cpsPathQuery); queryParameters.put("xpathRegex", xpathRegex); if (cpsPathQuery.hasLeafConditions()) { sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb"); @@ -62,28 +63,20 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class); setQueryParameters(query, queryParameters); - return getFragmentEntitiesAsStream(query); - } - - private List<FragmentEntity> getFragmentEntitiesAsStream(final Query query) { - final List<FragmentEntity> fragmentEntities = new ArrayList<>(); - query.getResultStream().forEach(fragmentEntity -> { - fragmentEntities.add((FragmentEntity) fragmentEntity); - entityManager.detach(fragmentEntity); - }); - + final List<FragmentEntity> fragmentEntities = query.getResultList(); + log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size()); return fragmentEntities; } - private static String getSimilarToXpathSqlRegex(final CpsPathQuery cpsPathQuery) { + private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) { final StringBuilder xpathRegexBuilder = new StringBuilder(); if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) { xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix())); } else { - xpathRegexBuilder.append(SIMILAR_TO_ABSOLUTE_PATH_PREFIX); + xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX); xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName())); } - xpathRegexBuilder.append(SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX); + xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX); return xpathRegexBuilder.toString(); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java index e21fecb2b1..850b274c91 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java @@ -21,16 +21,19 @@ package org.onap.cps.spi.repository; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.StringJoiner; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import lombok.extern.slf4j.Slf4j; import org.hibernate.type.StandardBasicTypes; import org.onap.cps.spi.model.ModuleReference; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Repository public class YangResourceNativeRepositoryImpl implements YangResourceNativeRepository { @@ -40,19 +43,27 @@ public class YangResourceNativeRepositoryImpl implements YangResourceNativeRepos @Override @Transactional public List<Long> getResourceIdsByModuleReferences(final Collection<ModuleReference> moduleReferences) { + if (moduleReferences.isEmpty()) { + return Collections.emptyList(); + } final Query query = entityManager.createNativeQuery(getCombinedSelectSqlQuery(moduleReferences)) .unwrap(org.hibernate.query.NativeQuery.class) .addScalar("id", StandardBasicTypes.LONG); - return query.getResultList(); + final List<Long> yangResourceIds = query.getResultList(); + if (yangResourceIds.size() != moduleReferences.size()) { + log.warn("ModuleReferences size : {} and QueryResult size : {}", moduleReferences.size(), + yangResourceIds.size()); + } + return yangResourceIds; } private String getCombinedSelectSqlQuery(final Collection<ModuleReference> moduleReferences) { final StringJoiner sqlQueryJoiner = new StringJoiner(" UNION ALL "); - moduleReferences.stream().forEach(moduleReference -> { + moduleReferences.forEach(moduleReference -> sqlQueryJoiner.add(String.format("SELECT id FROM yang_resource WHERE module_name='%s' and revision='%s'", moduleReference.getModuleName(), - moduleReference.getRevision())); - }); + moduleReference.getRevision())) + ); return sqlQueryJoiner.toString(); } } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy index 36b378a775..be2f8febff 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy @@ -50,7 +50,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { scenario | cpsPath | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes 'String and no descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS || 1 | 0 'Integer and descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]' | INCLUDE_ALL_DESCENDANTS || 1 | 1 - 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 2 | 0 + 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 3 | 0 } @Sql([CLEAR_DATA, SET_DATA]) @@ -91,16 +91,19 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { assert result[i].getXpath() == expectedXPaths[i] } where: 'the following data is used' - scenario | cpsPath || expectedXPaths - 'fully unique descendant name' | '//categories[@code=2]' || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"] - 'descendant name match end of other node' | '//book' || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"] - 'descendant with text condition on leaf' | '//book/title[text()="Chapters"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"] - 'descendant with text condition case mismatch' | '//book/title[text()="chapters"]' || [] - 'descendant with text condition on int leaf' | '//book/price[text()="5"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"] - 'descendant with text condition on leaf-list' | '//book/labels[text()="special offer"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"] - 'descendant with text condition partial match' | '//book/labels[text()="special"]' || [] - 'descendant with text condition (existing) empty string' | '//book/labels[text()=""]' || ["/shops/shop[@id='1']/categories[@code='1']/book"] - 'descendant with text condition on int leaf-list' | '//book/editions[text()="2000"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"] + scenario | cpsPath || expectedXPaths + 'fully unique descendant name' | '//categories[@code=2]' || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"] + 'descendant name match end of other node' | '//book' || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"] + 'descendant with text condition on leaf' | '//book/title[text()="Chapters"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"] + 'descendant with text condition case mismatch' | '//book/title[text()="chapters"]' || [] + 'descendant with text condition on int leaf' | '//book/price[text()="5"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"] + 'descendant with text condition on leaf-list' | '//book/labels[text()="special offer"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"] + 'descendant with text condition partial match' | '//book/labels[text()="special"]' || [] + 'descendant with text condition (existing) empty string' | '//book/labels[text()=""]' || ["/shops/shop[@id='1']/categories[@code='1']/book"] + 'descendant with text condition on int leaf-list' | '//book/editions[text()="2000"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"] + 'descendant name match of leaf containing /' | '//categories/type[text()="text/with/slash"]' || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"] + 'descendant with text condition on leaf containing /' | '//categories[@code=\'string/with/slash\']' || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"] + 'descendant with text condition on leaf containing [' | '//book/author[@Address="String[with]square[bracket]"]'|| [] } @Sql([CLEAR_DATA, SET_DATA]) diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index acc243b5b4..5e15ca795f 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -27,6 +27,7 @@ import org.onap.cps.cpspath.parser.PathParsingException import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.entities.FragmentEntity import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch import org.onap.cps.spi.exceptions.AnchorNotFoundException import org.onap.cps.spi.exceptions.CpsAdminException import org.onap.cps.spi.exceptions.CpsPathException @@ -48,6 +49,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { CpsDataPersistenceService objectUnderTest static final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + static final DataNodeBuilder dataNodeBuilder = new DataNodeBuilder() static final String SET_DATA = '/data/fragment.sql' static final int DATASPACE_1001_ID = 1001L @@ -165,7 +167,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { def grandChild = buildDataNode('/parent-201/child-204[@key="NEW1"]/grand-child-204[@key2="NEW1-CHILD"]', [leave:'value'], []) listElements[0].childDataNodes = [grandChild] when: 'the new data node (list elements) are added to an existing parent node' - objectUnderTest.addListElementsBatch(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements]) + objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements]) then: 'new entries are successfully persisted, parent node now contains 5 children (2 new + 3 existing before)' def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID) def allChildXpaths = parentFragment.childFragments.collect { it.xpath } @@ -179,17 +181,41 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) + def 'Add multiple list with a mix of existing and new elements'() { + given: 'two new child list elements for an existing parent' + def existingDataNode = dataNodeBuilder.withXpath('/parent-100/child-001').withLeaves(['id': '001']).build() + def newDataNode1 = dataNodeBuilder.withXpath('/parent-100/child-new1').withLeaves(['id': 'new1']).build() + def newDataNode2 = dataNodeBuilder.withXpath('/parent-200/child-new2').withLeaves(['id': 'new2']).build() + def dataNodeList1 = [existingDataNode, newDataNode1] + def dataNodeList2 = [newDataNode2] + when: 'duplicate data node is requested to be added' + objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [dataNodeList1,dataNodeList2]) + then: 'already defined batch exception is thrown' + def thrown = thrown(AlreadyDefinedExceptionBatch) + and: 'it only contains the xpath(s) of the duplicated elements' + assert thrown.alreadyDefinedXpaths.size() == 1 + assert thrown.alreadyDefinedXpaths.contains('/parent-100/child-001') + and: 'it does NOT contains the xpaths of the new element that were not combined with existing elements' + assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1') + assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1') + and: 'the new entity is inserted correctly' + def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME) + def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3) + fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, '/parent-200/child-new2').isPresent() + } + + @Sql([CLEAR_DATA, SET_DATA]) def 'Add list element error scenario: #scenario.'() { given: 'list element as a collection of data nodes' - def listElementCollection = toDataNodes(listElementXpaths) + def listElements = toDataNodes(listElementXpaths) when: 'attempt to add list elements to parent node' - objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElementCollection) + objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElements) then: 'a #expectedException is thrown' thrown(expectedException) where: 'following parameters were used' scenario | parentNodeXpath | listElementXpaths || expectedException 'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException - 'data fragment already exists' | '/parent-201' | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedException + 'data fragment already exists' | '/parent-201' | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedExceptionBatch } @Sql([CLEAR_DATA, SET_DATA]) @@ -559,8 +585,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { return xpaths.collect { new DataNodeBuilder().withXpath(it).build() } } + static DataNode buildDataNode(xpath, leaves, childDataNodes) { - return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build() + return dataNodeBuilder.withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build() } static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) { diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index 1bbf358e54..470b03afdf 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -86,17 +86,25 @@ class CpsDataPersistenceServiceSpec extends Specification { assert concurrencyException.getDetails().contains('/some/xpath') } - def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() { - given: 'the fragment repository returns a list of fragment entities' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> new FragmentEntity() - and: 'a data node is concurrently updated by another transaction' + def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() { + given: 'the system contains and can update one datanode' + def dataNode1 = mockDataNodeAndFragmentEntity('/node1', 'OK') + and: 'the system contains two more datanodes that throw an exception while updating' + def dataNode2 = mockDataNodeAndFragmentEntity('/node2', 'EXCEPTION') + def dataNode3 = mockDataNodeAndFragmentEntity('/node3', 'EXCEPTION') + and: 'the batch update will therefore also fail' mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") } - when: 'attempt to update data node with submitted data nodes' - objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', []) + when: 'attempt batch update data nodes' + objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [dataNode1, dataNode2, dataNode3]) then: 'concurrency exception is thrown' - def concurrencyException = thrown(ConcurrencyException) - assert concurrencyException.getDetails().contains('some-dataspace') - assert concurrencyException.getDetails().contains('some-anchor') + def thrown = thrown(ConcurrencyException) + assert thrown.message == 'Concurrent Transactions' + and: 'it does not contain the successfull datanode' + assert !thrown.details.contains('/node1') + and: 'it contains the failed datanodes' + assert thrown.details.contains('/node2') + assert thrown.details.contains('/node3') + } def 'Retrieving a data node with a property JSON value of #scenario'() { @@ -193,4 +201,14 @@ class CpsDataPersistenceServiceSpec extends Specification { assert fragmentEntities.size() == 2 }}) } + + def mockDataNodeAndFragmentEntity(xpath, scenario) { + def dataNode = new DataNodeBuilder().withXpath(xpath).build() + def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: []) + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity + if ('EXCEPTION' == scenario) { + mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") } + } + return dataNode + } }
\ No newline at end of file diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy index eac28873e7..f9ebc52f18 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy @@ -31,8 +31,10 @@ import org.onap.cps.spi.model.ModuleReference import org.onap.cps.spi.repository.AnchorRepository import org.onap.cps.spi.repository.SchemaSetRepository import org.onap.cps.spi.repository.SchemaSetYangResourceRepositoryImpl +import org.onap.cps.spi.repository.YangResourceRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql +import spock.lang.Ignore class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @@ -48,6 +50,9 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase @Autowired CpsAdminPersistenceService cpsAdminPersistenceService + @Autowired + YangResourceRepository yangResourceRepository + final static String SET_DATA = '/data/schemaset.sql' def static EXISTING_SCHEMA_SET_NAME = SCHEMA_SET_NAME1 @@ -77,6 +82,20 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase } @Sql([CLEAR_DATA, SET_DATA]) + def 'Getting yang resource ids from module references'() { + when: 'getting yang resources for #scenario' + def result = yangResourceRepository.getResourceIdsByModuleReferences(moduleReferences) + then: 'the result contains the expected number entries' + assert result.size() == expectedResultSize + where: 'the following module references are provided' + scenario | moduleReferences || expectedResultSize + '2 valid module references' | [ new ModuleReference('MODULE-NAME-002','REVISION-002'), new ModuleReference('MODULE-NAME-003','REVISION-002') ] || 2 + '1 invalid module reference' | [ new ModuleReference('NOT EXIST','IRRELEVANT') ] || 0 + '1 valid and 1 invalid module reference' | [ new ModuleReference('MODULE-NAME-002','REVISION-002'), new ModuleReference('NOT EXIST','IRRELEVANT') ] || 1 + 'no module references' | [] || 0 + } + + @Sql([CLEAR_DATA, SET_DATA]) def 'Store schema set error scenario: #scenario.'() { when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName' objectUnderTest.storeSchemaSet(dataspaceName, schemaSetName, newYangResourcesNameToContentMap) diff --git a/cps-ri/src/test/resources/data/cps-path-query.sql b/cps-ri/src/test/resources/data/cps-path-query.sql index b6000cffb4..fa711cbbf7 100644 --- a/cps-ri/src/test/resources/data/cps-path-query.sql +++ b/cps-ri/src/test/resources/data/cps-path-query.sql @@ -55,10 +55,12 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) (2, 1001, 1003, 1, '/shops/shop[@id=''1'']', '{"id" : 1, "type" : "bookstore"}'), (3, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''1'']', '{"code" : 1, "type" : "bookstore", "name": "SciFi"}'), (4, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''2'']', '{"code" : 2, "type" : "bookstore", "name": "Fiction"}'), + (31, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''string/with/slash/'']', '{"code" : "string/with/slash", "type" : "text/with/slash", "name": "Fiction"}'), (5, 1001, 1003, 3, '/shops/shop[@id=''1'']/categories[@code=''1'']/book', '{"price" : 5, "title" : "Dune", "labels" : ["special offer","classics",""]}'), (6, 1001, 1003, 4, '/shops/shop[@id=''1'']/categories[@code=''2'']/book', '{"price" : 15, "title" : "Chapters", "editions" : [2000,2010,2020]}'), (7, 1001, 1003, 5, '/shops/shop[@id=''1'']/categories[@code=''1'']/book/author[@FirstName=''Joe'' and @Surname=''Bloggs'']', '{"FirstName" : "Joe", "Surname": "Bloggs","title": "Dune"}'), - (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}'); + (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}'), + (32, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Address=''string[with]square[brackets]'']', '{"FirstName" : "Joe", "Address": "string[with]square[brackets]","title": "Chapters"}'); INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES (9, 1001, 1003, 1, '/shops/shop[@id=''2'']', '{"type" : "bookstore"}'), diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java index 5e8eb9f6cf..6b17e820c4 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java @@ -50,11 +50,11 @@ public interface CpsModuleService { * @param dataspaceName Dataspace name * @param schemaSetName schema set name * @param newModuleNameToContentMap YANG resources map where key is a module name and value is content - * @param moduleReferences List of YANG resources module references of the modules + * @param allModuleReferences All YANG resource module references */ void createSchemaSetFromModules(String dataspaceName, String schemaSetName, Map<String, String> newModuleNameToContentMap, - Collection<ModuleReference> moduleReferences); + Collection<ModuleReference> allModuleReferences); /** * Read schema set in the given dataspace. diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index 6bf493556e..b6aa04be76 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -97,7 +97,7 @@ public class CpsDataServiceImpl implements CpsDataService { CpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection<Collection<DataNode>> listElementDataNodeCollections = buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList); - cpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, parentNodeXpath, + cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath, listElementDataNodeCollections); processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java index ff725a617f..20b4a23a9b 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java @@ -60,10 +60,10 @@ public class CpsModuleServiceImpl implements CpsModuleService { @Override public void createSchemaSetFromModules(final String dataspaceName, final String schemaSetName, final Map<String, String> newModuleNameToContentMap, - final Collection<ModuleReference> moduleReferences) { + final Collection<ModuleReference> allModuleReferences) { CpsValidator.validateNameCharacters(dataspaceName, schemaSetName); cpsModulePersistenceService.storeSchemaSetFromModules(dataspaceName, schemaSetName, - newModuleNameToContentMap, moduleReferences); + newModuleNameToContentMap, allModuleReferences); } diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index 8b45ae78d9..cd0cefcbf3 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -66,15 +66,15 @@ public interface CpsDataPersistenceService { Collection<DataNode> listElementsCollection); /** - * Adds list child elements to a Fragment. + * Add multiple lists of data nodes to a parent node at the same time. * * @param dataspaceName dataspace name * @param anchorName anchor name * @param parentNodeXpath parent node xpath - * @param listElementsCollections collections of data nodes representing list elements + * @param newLists collections of lists of data nodes representing list elements */ - void addListElementsBatch(String dataspaceName, String anchorName, String parentNodeXpath, - Collection<Collection<DataNode>> listElementsCollections); + void addMultipleLists(String dataspaceName, String anchorName, String parentNodeXpath, + Collection<Collection<DataNode>> newLists); /** * Retrieves datanode by XPath for given dataspace and anchor. diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java index db2cb60f34..aaf6b38af4 100755 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java @@ -43,13 +43,13 @@ public interface CpsModulePersistenceService { /** * Stores a schema set from new modules and existing modules. * - * @param dataspaceName Dataspace name - * @param schemaSetName Schema set name + * @param dataspaceName Dataspace name + * @param schemaSetName Schema set name * @param newModuleNameToContentMap YANG resources map where key is a module name and value is content - * @param moduleReferences List of YANG resources module references + * @param allModuleReferences All YANG resources module references */ void storeSchemaSetFromModules(String dataspaceName, String schemaSetName, - Map<String, String> newModuleNameToContentMap, Collection<ModuleReference> moduleReferences); + Map<String, String> newModuleNameToContentMap, Collection<ModuleReference> allModuleReferences); /** * Deletes Schema Set. diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java new file mode 100644 index 0000000000..0ba656aa12 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java @@ -0,0 +1,34 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.spi.exceptions; + +import java.util.Collection; +import lombok.Getter; + +public class AlreadyDefinedExceptionBatch extends RuntimeException { + + @Getter + private final Collection<String> alreadyDefinedXpaths; + + public AlreadyDefinedExceptionBatch(final Collection<String> alreadyDefinedXPaths) { + this.alreadyDefinedXpaths = alreadyDefinedXPaths; + } +} diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/ConcurrencyException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/ConcurrencyException.java index 3a8a94b979..b5eae93b32 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/exceptions/ConcurrencyException.java +++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/ConcurrencyException.java @@ -20,8 +20,8 @@ package org.onap.cps.spi.exceptions; public class ConcurrencyException extends CpsException { - public ConcurrencyException(final String message, final String details, final Throwable cause) { - super(message, details, cause); + public ConcurrencyException(final String message, final String details) { + super(message, details); } } diff --git a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java index 28b49c9666..f3774d9529 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java +++ b/cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java @@ -22,7 +22,6 @@ package org.onap.cps.utils; import com.google.common.collect.Lists; import java.util.Collection; -import java.util.regex.Pattern; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -33,30 +32,25 @@ import org.onap.cps.spi.exceptions.DataValidationException; public final class CpsValidator { private static final char[] UNSUPPORTED_NAME_CHARACTERS = "!\" #$%&'()*+,./\\:;<=>?@[]^`{|}~".toCharArray(); - private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|" - + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$"); /** * Validate characters in names within cps. + * * @param names names of data to be validated */ public static void validateNameCharacters(final String... names) { for (final String name : names) { - final Collection<Character> charactersOfName = Lists.charactersOf(name); + final Collection<Character> charactersOfName = Lists.charactersOf(name); for (final char unsupportedCharacter : UNSUPPORTED_NAME_CHARACTERS) { if (charactersOfName.contains(unsupportedCharacter)) { throw new DataValidationException("Name or ID Validation Error.", - name + " invalid token encountered at position " + (name.indexOf(unsupportedCharacter) + 1)); + name + " invalid token encountered at position " + + (name.indexOf(unsupportedCharacter) + 1)); } } } } - /** - * Validate kafka topic name pattern. - * @param topicName name of the topic to be validated - */ - public static boolean validateTopicName(final String topicName) { - return topicName != null && TOPIC_NAME_PATTERN.matcher(topicName).matches(); - } + + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index ab960df6aa..3f28f0ac8d 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -143,7 +143,7 @@ class CpsDataServiceImplSpec extends Specification { def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}' objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp) then: 'the persistence service method is invoked with correct parameters' - 1 * mockCpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, '/test-tree',_) >> { + 1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> { args -> { def listElementsCollection = args[3] as Collection<Collection<DataNode>> assert listElementsCollection.size() == 1 diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy index ce728ef1c1..ea7a5d6d1d 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy @@ -25,7 +25,6 @@ import spock.lang.Specification class CpsValidatorSpec extends Specification { - def 'Validating a valid string.'() { when: 'the string is validated using a valid name' CpsValidator.validateNameCharacters('name-with-no-spaces') @@ -46,17 +45,4 @@ class CpsValidatorSpec extends Specification { 'position 9' | 'nameWith Space' || 'nameWith Space invalid token encountered at position 9' } - def 'Validating topic names.'() { - when: 'the topic name is validated' - def isValidTopicName = CpsValidator.validateTopicName(topicName) - then: 'boolean response will be returned for #scenario' - assert isValidTopicName == booleanResponse - where: 'the following names are used' - scenario | topicName || booleanResponse - 'valid topic' | 'my-topic-name' || true - 'empty topic' | '' || false - 'blank topic' | ' ' || false - 'null topic' | null || false - 'invalid non empty topic' | '1_5_*_#' || false - } } diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index 983252f5af..874f2a0774 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -45,6 +45,16 @@ paths: schema: type: string example: my-resource + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Bad Request + details: The provided request is not valid "401": description: Unauthorized content: diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index 30896f6068..2d34f0a61a 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -20,7 +20,7 @@ paths: $ref: '#/components/schemas/RestDmiPluginRegistration' required: true responses: - "204": + "200": description: No Content content: {} "400": @@ -54,6 +54,90 @@ paths: message: Forbidden error message details: Forbidden error details "500": + description: Partial or Complete failure. The error details are provided + in the response body and all supported error codes are documented in the + example. + content: + application/json: + schema: + $ref: '#/components/schemas/DmiPluginRegistrationErrorResponse' + example: + failedCreatedCmHandles: + - cmHandle: my-cm-handle-01 + errorCode: "00" + errorText: Unknown error. <error-details> + - cmHandle: my-cm-handle-02 + errorCode: "01" + errorText: cm-handle already exists + - cmHandle: my-cm-handle-03 + errorCode: "03" + errorText: cm-handle has an invalid character(s) in id + failedUpdatedCmHandles: + - cmHandle: my-cm-handle-01 + errorCode: "00" + errorText: Unknown error. <error-details> + - cmHandle: my-cm-handle-02 + errorCode: "02" + errorText: cm-handle does not exist + - cmHandle: my-cm-handle-03 + errorCode: "03" + errorText: cm-handle has an invalid character(s) in id + failedRemovedCmHandles: + - cmHandle: my-cm-handle-01 + errorCode: "00" + errorText: Unknown error. <error-details> + - cmHandle: my-cm-handle-02 + errorCode: "02" + errorText: cm-handle does not exists + - cmHandle: my-cm-handle-03 + errorCode: "03" + errorText: cm-handle has an invalid character(s) in id + /v1/ch/cmHandles: + get: + tags: + - network-cm-proxy-inventory + summary: "Get all cm handle IDs for a registered DMI plugin (DMI plugin, DMI\ + \ data plugin, DMI model plugin)" + description: Get all cm handle IDs for a registered DMI plugin + operationId: getAllCmHandleIdsForRegisteredDmi + parameters: + - name: dmi-plugin-identifier + in: query + description: dmi-plugin-identifier + required: true + schema: + type: string + example: my-dmi-plugin + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: string + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized error message + details: Unauthorized error details + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Forbidden error message + details: Forbidden error details + "500": description: Internal Server Error content: application/json: @@ -123,3 +207,30 @@ components: type: string details: type: string + DmiPluginRegistrationErrorResponse: + type: object + properties: + failedCreatedCmHandles: + type: array + items: + $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse' + failedUpdatedCmHandles: + type: array + items: + $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse' + failedRemovedCmHandles: + type: array + items: + $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse' + CmHandlerRegistrationErrorResponse: + type: object + properties: + cmHandle: + type: string + example: my-cm-handle + errorCode: + type: string + example: "00" + errorText: + type: string + example: Unknown error. <error-details> diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index a43190bccd..44cc2b88a6 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -6,141 +6,21 @@ info: servers: - url: /ncmp paths: - /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-operational: + /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}: get: tags: - network-cm-proxy - summary: Get resource data from pass-through operational for cm handle - description: Get resource data from pass-through operational for given cm handle - operationId: getResourceDataOperationalForCmHandle + summary: Get resource data for cm handle + description: Get resource data for given cm handle + operationId: getResourceDataForCmHandle parameters: - - name: cm-handle + - name: ncmp-datastore-name in: path - description: "The identifier for a network function, network element, subnetwork\ - \ or any other cm object by managed Network CM Proxy" - required: true - schema: - type: string - example: my-cm-handle - - name: resourceIdentifier - in: query - description: The format of resource identifier depend on the associated DMI - Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but - it can really be anything. + description: The type of the requested data required: true - allowReserved: true schema: type: string - examples: - sample 1: - value: - resourceIdentifier: \shops\bookstore - sample 2: - value: - resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]" - sample 3: - value: - resourceIdentifier: "parent=shops,child=bookstore" - - name: options - in: query - description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\ - \ in parenthesis'()'. The format of options parameter depend on the associated\ - \ DMI Plugin implementation." - required: false - allowReserved: true - schema: - type: string - examples: - sample 1: - value: - options: (depth=3) - sample 2: - value: - options: (fields=book) - sample 3: - value: - options: "(depth=2,fields=book/authors)" - - name: topic - in: query - description: topic parameter in query. - required: false - allowReserved: true - schema: - type: string - examples: - sample 1: - value: - topic: my-topic-name - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - examples: - dataSampleResponse: - $ref: '#/components/examples/dataSampleResponse' - "400": - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 400 BAD_REQUEST - message: Bad request error message - details: Bad request error details - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 401 - message: Unauthorized error message - details: Unauthorized error details - "403": - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 403 - message: Forbidden error message - details: Forbidden error details - "500": - description: Internal Server Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 500 - message: Internal Server Error - details: Internal Server Error occurred - "502": - description: Bad Gateway - content: - application/json: - schema: - $ref: '#/components/schemas/DmiErrorMessage' - example: - message: "Bad Gateway Error Message NCMP" - dmi-response: - http-code: 400 - body: Bad Request - - /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running: - get: - tags: - - network-cm-proxy - summary: Get resource data from pass-through running for cm handle - description: Get resource data from pass-through running for given cm handle - operationId: getResourceDataRunningForCmHandle - parameters: + example: ncmp-datastore:operational - name: cm-handle in: path description: "The identifier for a network function, network element, subnetwork\ @@ -198,6 +78,13 @@ paths: sample 1: value: topic: my-topic-name + - name: include-descendants + in: query + description: Determines if descendants are included in response + required: false + schema: + type: boolean + default: false responses: "200": description: OK @@ -255,7 +142,7 @@ paths: schema: $ref: '#/components/schemas/DmiErrorMessage' example: - message: "Bad Gateway Error Message NCMP" + message: Bad Gateway Error Message NCMP dmi-response: http-code: 400 body: Bad Request @@ -267,6 +154,13 @@ paths: handle operationId: updateResourceDataRunningForCmHandle parameters: + - name: ncmp-datastore-name + in: path + description: The type of the requested data + required: true + schema: + type: string + example: ncmp-datastore:operational - name: cm-handle in: path description: "The identifier for a network function, network element, subnetwork\ @@ -372,7 +266,7 @@ paths: schema: $ref: '#/components/schemas/DmiErrorMessage' example: - message: "Bad Gateway Error Message NCMP" + message: Bad Gateway Error Message NCMP dmi-response: http-code: 400 body: Bad Request @@ -383,6 +277,13 @@ paths: description: create resource data from pass-through running for given cm handle operationId: createResourceDataRunningForCmHandle parameters: + - name: ncmp-datastore-name + in: path + description: The type of the requested data + required: true + schema: + type: string + example: ncmp-datastore:operational - name: cm-handle in: path description: "The identifier for a network function, network element, subnetwork\ @@ -485,7 +386,7 @@ paths: schema: $ref: '#/components/schemas/DmiErrorMessage' example: - message: "Bad Gateway Error Message NCMP" + message: Bad Gateway Error Message NCMP dmi-response: http-code: 400 body: Bad Request @@ -496,6 +397,13 @@ paths: description: Delete resource data from pass-through running for a given cm handle operationId: deleteResourceDataRunningForCmHandle parameters: + - name: ncmp-datastore-name + in: path + description: The type of the requested data + required: true + schema: + type: string + example: ncmp-datastore:operational - name: cm-handle in: path description: "The identifier for a network function, network element, subnetwork\ @@ -593,7 +501,7 @@ paths: schema: $ref: '#/components/schemas/DmiErrorMessage' example: - message: "Bad Gateway Error Message NCMP" + message: Bad Gateway Error Message NCMP dmi-response: http-code: 400 body: Bad Request @@ -605,6 +513,13 @@ paths: handle operationId: patchResourceDataRunningForCmHandle parameters: + - name: ncmp-datastore-name + in: path + description: The type of the requested data + required: true + schema: + type: string + example: ncmp-datastore:operational - name: cm-handle in: path description: "The identifier for a network function, network element, subnetwork\ @@ -704,7 +619,7 @@ paths: schema: $ref: '#/components/schemas/DmiErrorMessage' example: - message: "Bad Gateway Error Message NCMP" + message: Bad Gateway Error Message NCMP dmi-response: http-code: 400 body: Bad Request @@ -774,19 +689,95 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred + /v1/ch/{cm-handle}/modules/definitions: + get: + tags: + - network-cm-proxy + summary: "Fetch all module definitions (name, revision, yang resource) for a\ + \ given cm handle" + description: "Fetch all module definitions (name, revision, yang resource) for\ + \ a given cm handle" + operationId: getModuleDefinitionsByCmHandleId + parameters: + - name: cm-handle + in: path + description: "The identifier for a network function, network element, subnetwork\ + \ or any other cm object by managed Network CM Proxy" + required: true + schema: + type: string + example: my-cm-handle + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RestModuleDefinition' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized error message + details: Unauthorized error details + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Forbidden error message + details: Forbidden error details + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred /v1/ch/searches: post: tags: - network-cm-proxy summary: Execute cm handle search using the available conditions - description: Execute cm handle searches using 'hasAllModules' condition to get - all cm handles for the given module names - operationId: executeCmHandleSearch + description: Execute cm handle query search and return a list of cm handle details. + Any number of conditions can be applied. To be included in the result a cm-handle + must fulfill ALL the conditions. An empty collection will be returned in the + case that the cm handle does not match a condition. For more on cm handle + query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm + handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible + to query on any data related to the cm handle. For more on CPS Path please + refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS + Path Read the Docs</a>. The cm handle ancestor is automatically returned for + this query. + operationId: searchCmHandles requestBody: content: application/json: schema: - $ref: '#/components/schemas/Conditions' + $ref: '#/components/schemas/CmHandleQueryParameters' + examples: + Cm handle properties query: + $ref: '#/components/examples/pubPropCmHandleQueryParameters' + Cm handle modules query: + $ref: '#/components/examples/modulesCmHandleQueryParameters' + All cm handle query parameters: + $ref: '#/components/examples/allCmHandleQueryParameters' + Cm handle with CPS path state query: + $ref: '#/components/examples/cpsPathCmHandleStateQueryParameters' + Cm handle with data sync flag query: + $ref: '#/components/examples/cpsPathCmHandleDataSyncQueryParameters' required: true responses: "200": @@ -794,7 +785,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CmHandles' + type: array + items: + $ref: '#/components/schemas/RestOutputCmHandle' "400": description: Bad Request content: @@ -878,6 +871,151 @@ paths: status: 401 message: Unauthorized error message details: Unauthorized error details + "404": + description: The specified resource was not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Not found error message + details: Not found error details + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + /v1/ch/{cm-handle}/properties: + get: + tags: + - network-cm-proxy + summary: Get CM handle properties + description: Get CM handle properties by cm handle id + operationId: getCmHandlePublicPropertiesByCmHandleId + parameters: + - name: cm-handle + in: path + description: "The identifier for a network function, network element, subnetwork\ + \ or any other cm object by managed Network CM Proxy" + required: true + schema: + type: string + example: my-cm-handle + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RestOutputCmHandlePublicProperties' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 BAD_REQUEST + message: Bad request error message + details: Bad request error details + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized error message + details: Unauthorized error details + "404": + description: The specified resource was not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Not found error message + details: Not found error details + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + /v1/ch/id-searches: + post: + tags: + - network-cm-proxy + summary: Execute cm handle query upon a given set of query parameters + description: Execute cm handle query search and return a list of cm handle ids. + Any number of conditions can be applied. To be included in the result a cm-handle + must fulfill ALL the conditions. An empty collection will be returned in the + case that the cm handle does not match a condition. For more on cm handle + query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm + handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible + to query on any data related to the cm handle. For more on CPS Path please + refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS + Path Read the Docs</a>. The cm handle ancestor is automatically returned for + this query. + operationId: searchCmHandleIds + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CmHandleQueryParameters' + examples: + Cm handle properties query: + $ref: '#/components/examples/pubPropCmHandleQueryParameters' + Cm handle modules query: + $ref: '#/components/examples/modulesCmHandleQueryParameters' + All cm handle query parameters: + $ref: '#/components/examples/allCmHandleQueryParameters' + Cm handle with CPS path state query: + $ref: '#/components/examples/cpsPathCmHandleStateQueryParameters' + Cm handle with data sync flag query: + $ref: '#/components/examples/cpsPathCmHandleDataSyncQueryParameters' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: string + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 BAD_REQUEST + message: Bad request error message + details: Bad request error details + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized error message + details: Unauthorized error details "403": description: Forbidden content: @@ -908,6 +1046,152 @@ paths: status: 500 message: Internal Server Error details: Internal Server Error occurred + /v1/ch/{cm-handle}/state: + get: + tags: + - network-cm-proxy + summary: Get CM handle state + description: Get CM handle state by cm handle id + operationId: getCmHandleStateByCmHandleId + parameters: + - name: cm-handle + in: path + description: "The identifier for a network function, network element, subnetwork\ + \ or any other cm object by managed Network CM Proxy" + required: true + schema: + type: string + example: my-cm-handle + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RestOutputCmHandleCompositeState' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 BAD_REQUEST + message: Bad request error message + details: Bad request error details + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized error message + details: Unauthorized error details + "404": + description: The specified resource was not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 + message: Not found error message + details: Not found error details + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + /v1/ch/{cm-handle}/data-sync: + put: + tags: + - network-cm-proxy + summary: Set the Data Sync Enabled Flag + description: Set the data sync enabled flag to true or false for a specified + Cm-Handle. This will in turn set the data sync state to UNSYNCHRONIZED and + NONE_REQUESTED respectfully. + operationId: setDataSyncEnabledFlagForCmHandle + parameters: + - name: cm-handle + in: path + description: "The identifier for a network function, network element, subnetwork\ + \ or any other cm object by managed Network CM Proxy" + required: true + schema: + type: string + example: my-cm-handle + - name: dataSyncEnabled + in: query + description: Is used to enable or disable the data synchronization flag + required: true + schema: + type: boolean + example: true + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 400 BAD_REQUEST + message: Bad request error message + details: Bad request error details + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 401 + message: Unauthorized error message + details: Unauthorized error details + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 403 + message: Forbidden error message + details: Forbidden error details + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: 500 + message: Internal Server Error + details: Internal Server Error occurred + "502": + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/DmiErrorMessage' + example: + message: Bad Gateway Error Message NCMP + dmi-response: + http-code: 400 + body: Bad Request components: schemas: ErrorMessage: @@ -920,23 +1204,15 @@ components: type: string details: type: string - # DMI Server Exception Schema DmiErrorMessage: title: DMI Error Message type: object properties: message: type: string - example: "Bad Gateway Error Message NCMP" + example: Bad Gateway Error Message NCMP dmi-response: - type: object - properties: - http-code: - type: integer - example: 400 - body: - type: string - example: Bad Request + $ref: '#/components/schemas/DmiErrorMessage_dmiresponse' RestModuleReference: title: Module reference details type: object @@ -947,45 +1223,59 @@ components: revision: type: string example: my-module-revision - Conditions: + RestModuleDefinition: + title: Module definitions + type: object + properties: + moduleName: + type: string + example: my-module-name + revision: + type: string + example: 2020-09-15T00:00:00.000+00:00 + content: + type: string + example: "module stores {\n yang-version 1.1;\n namespace \"org:onap:ccsdk:sample\"\ + ;\n prefix book-store;\n revision \"2020-09-15\" {\n description\n\ + \ \"Sample Model\";\n }\n}\n" + CmHandleQueryParameters: + title: Cm Handle query parameters for executing cm handle search type: object properties: + cmHandleQueryParameters: + type: array + items: + $ref: '#/components/schemas/ConditionProperties' conditions: - $ref: '#/components/schemas/ConditionsData' - ConditionsData: - type: array - items: - $ref: '#/components/schemas/ConditionProperties' + type: array + description: "not necessary, it is just for backward compatibility" + deprecated: true + items: + $ref: '#/components/schemas/OldConditionProperties' ConditionProperties: properties: + conditionName: + type: string + conditionParameters: + type: array + items: + type: object + additionalProperties: + type: string + OldConditionProperties: + properties: name: type: string - example: hasAllModules conditionParameters: - $ref: '#/components/schemas/ModuleNamesAsJsonArray' - ModuleNamesAsJsonArray: - type: array - items: - $ref: '#/components/schemas/ModuleNameAsJsonObject' + type: array + items: + $ref: '#/components/schemas/ModuleNameAsJsonObject' + deprecated: true ModuleNameAsJsonObject: properties: moduleName: type: string example: my-module - CmHandles: - type: object - properties: - cmHandles: - $ref: '#/components/schemas/CmHandleProperties' - CmHandleProperties: - type: array - items: - $ref: '#/components/schemas/CmHandleProperty' - CmHandleProperty: - properties: - cmHandleId: - type: string - example: my-cm-handle-id RestOutputCmHandle: title: CM handle Details type: object @@ -995,6 +1285,8 @@ components: example: my-cm-handle1 publicCmHandleProperties: $ref: '#/components/schemas/CmHandlePublicProperties' + state: + $ref: '#/components/schemas/CmHandleCompositeState' CmHandlePublicProperties: type: array items: @@ -1002,6 +1294,66 @@ components: additionalProperties: type: string example: Book Type + CmHandleCompositeState: + type: object + properties: + cmHandleState: + type: string + example: ADVISED + lockReason: + $ref: '#/components/schemas/lock-reason' + lastUpdateTime: + type: string + example: 2022-12-31T20:30:40.000+0000 + dataSyncEnabled: + type: boolean + example: false + dataSyncState: + $ref: '#/components/schemas/dataStores' + lock-reason: + type: object + properties: + reason: + type: string + example: LOCKED_MISBEHAVING + details: + type: string + example: locked due to failure in module sync + dataStores: + type: object + properties: + operational: + $ref: '#/components/schemas/sync-state' + running: + $ref: '#/components/schemas/sync-state' + sync-state: + type: object + properties: + syncState: + type: string + example: NONE_REQUESTED + lastSyncTime: + type: string + example: 2022-12-31T20:30:40.000+0000 + RestOutputCmHandlePublicProperties: + type: object + properties: + publicCmHandleProperties: + $ref: '#/components/schemas/CmHandlePublicProperties' + RestOutputCmHandleCompositeState: + type: object + properties: + state: + $ref: '#/components/schemas/CmHandleCompositeState' + DmiErrorMessage_dmiresponse: + type: object + properties: + http-code: + type: integer + example: 400 + body: + type: string + example: Bad Request examples: dataSampleResponse: summary: Sample response @@ -1081,3 +1433,47 @@ components: books: - authors: - Philip Pullman + pubPropCmHandleQueryParameters: + value: + cmHandleQueryParameters: + - conditionName: hasAllProperties + conditionParameters: + - Color: yellow + - Shape: circle + - Size: small + modulesCmHandleQueryParameters: + value: + cmHandleQueryParameters: + - conditionName: hasAllModules + conditionParameters: + - moduleName: my-module-1 + - moduleName: my-module-2 + - moduleName: my-module-3 + allCmHandleQueryParameters: + value: + cmHandleQueryParameters: + - conditionName: hasAllModules + conditionParameters: + - moduleName: my-module-1 + - moduleName: my-module-2 + - moduleName: my-module-3 + - conditionName: hasAllProperties + conditionParameters: + - Color: yellow + - Shape: circle + - Size: small + - conditionName: cmHandleWithCpsPath + conditionParameters: + - cpsPath: "//state[@cm-handle-state='ADVISED']" + cpsPathCmHandleStateQueryParameters: + value: + cmHandleQueryParameters: + - conditionName: cmHandleWithCpsPath + conditionParameters: + - cpsPath: "//state[@cm-handle-state='LOCKED']" + cpsPathCmHandleDataSyncQueryParameters: + value: + cmHandleQueryParameters: + - conditionName: cmHandleWithCpsPath + conditionParameters: + - cpsPath: "//state[@data-sync-enabled='true']" diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 58dc060bbd..d4f9843615 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -39,6 +39,11 @@ Release Data Features -------- - `CPS-322 <https://jira.onap.org/browse/CPS-322>`_ Implement additional validation for names and identifiers + - `CPS-1136 <https://jira.onap.org/browse/CPS-1136>`_ Get all cm handles by DMI plugin Identifier + - `CPS-1001 <https://jira.onap.org/browse/CPS-1001>`_ Add CPS-E-05 endpoint for Read data, NCMP-Operational Datastore + +Bug Fixes +--------- .. ======================== .. * * * JAKARTA * * * diff --git a/releases/3.1.0-container.yaml b/releases/3.1.0-container.yaml new file mode 100644 index 0000000000..cc11ed26da --- /dev/null +++ b/releases/3.1.0-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.1.0 +project: cps +log_dir: cps-maven-docker-stage-master/690/ +ref: 9697e76c319e4cf59fc494216a720393545503a9 +containers: + - name: 'cps-and-ncmp' + version: '3.1.0-20220914T140025Z' diff --git a/releases/3.1.0.yaml b/releases/3.1.0.yaml new file mode 100644 index 0000000000..1258b26e61 --- /dev/null +++ b/releases/3.1.0.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/696/ +project: cps +version: 3.1.0 |