From: Toine Siebelink Date: Thu, 9 Jun 2022 16:51:03 +0000 (+0000) Subject: Merge "PoC Contract Stubs NCMP Rest Endpoints" X-Git-Tag: 3.1.0~103 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=708d2b566a8b8cdf48e1dfe337369c946cec0e02;hp=f9c5e6c865640ef07db8406baa95a5fafceb109a;p=cps.git Merge "PoC Contract Stubs NCMP Rest Endpoints" --- diff --git a/cps-application/src/main/java/org/onap/cps/Application.java b/cps-application/src/main/java/org/onap/cps/Application.java index ba707e964..79d5950f6 100644 --- a/cps-application/src/main/java/org/onap/cps/Application.java +++ b/cps-application/src/main/java/org/onap/cps/Application.java @@ -22,7 +22,9 @@ package org.onap.cps; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.retry.annotation.EnableRetry; +@EnableRetry @SpringBootApplication public class Application { diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index af886a142..3f005c9f7 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -86,7 +86,8 @@ app: ncmp: async-m2m: topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m} - + events: + topic: ${NCMP_EVENTS_TOPIC:ncmp-events} notification: data-updated: enabled: false diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 5fe47e4b0..248b1daa6 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -133,52 +133,7 @@ components: type: string example: my-property - Conditions: - type: object - properties: - conditions: - $ref: '#/components/schemas/ConditionsData' - ConditionsData: - type: array - items: - type: object - $ref: '#/components/schemas/ConditionProperties' - ConditionProperties: - properties: - name: - type: string - example: hasAllModules - conditionParameters: - $ref: '#/components/schemas/ModuleNamesAsJsonArray' - ModuleNamesAsJsonArray: - type: array - items: - type: object - $ref: '#/components/schemas/ModuleNameAsJsonObject' - example: [my-module-1, my-module-2, my-module-3] - ModuleNameAsJsonObject: - properties: - moduleName: - type: string - example: my-module - #Response Schemas - CmHandles: - type: object - properties: - cmHandles: - $ref: '#/components/schemas/CmHandleProperties' - CmHandleProperties: - type: array - items: - type: object - $ref: '#/components/schemas/CmHandleProperty' - CmHandleProperty: - properties: - cmHandleId: - type: string - example: my-cm-handle-id - RestModuleReference: type: object title: Module reference details @@ -190,15 +145,59 @@ components: type: string example: my-module-revision - CmHandleQueryRestParameters: + CmHandleQueryParameters: type: object title: Cm Handle query parameters for executing cm handle search properties: - publicCmHandleProperties: - type: object - additionalProperties: - type: string - example: Book Type + cmHandleQueryParameters: + type: array + items: + type: object + $ref: '#/components/schemas/ConditionProperties' + conditions: + deprecated: true + type: array + items: + type: object + $ref: '#/components/schemas/OldConditionProperties' + description: not necessary, it is just for backward compatibility + example: + 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" } + ConditionProperties: + properties: + conditionName: + type: string + conditionParameters: + type: array + items: + type: object + additionalProperties: + type: string + OldConditionProperties: + deprecated: true + properties: + name: + type: string + conditionParameters: + type: array + items: + type: object + $ref: '#/components/schemas/ModuleNameAsJsonObject' + ModuleNameAsJsonObject: + properties: + moduleName: + type: string + example: my-module RestOutputCmHandle: type: object diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index 318e6e66d..3259032f2 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -246,26 +246,28 @@ fetchModuleReferencesByCmHandle: 500: $ref: 'components.yaml#/components/responses/InternalServerError' -executeCmHandleSearch: +searchCmHandles: post: - description: Execute cm handle searches using 'hasAllModules' condition to get all cm handles for the given module names + description: Execute cm handle query search, to be included in the result a cm-handle must fulfill ALL the conditions listed here, if one of the given module names does not exists, return with an empty collection. tags: - network-cm-proxy summary: Execute cm handle search using the available conditions - operationId: executeCmHandleSearch + operationId: searchCmHandles requestBody: required: true content: application/json: schema: - $ref: 'components.yaml#/components/schemas/Conditions' + $ref: 'components.yaml#/components/schemas/CmHandleQueryParameters' responses: 200: description: OK content: application/json: schema: - $ref: 'components.yaml#/components/schemas/CmHandles' + type: array + items: + $ref: 'components.yaml#/components/schemas/RestOutputCmHandle' 400: $ref: 'components.yaml#/components/responses/BadRequest' 401: @@ -317,19 +319,19 @@ getCmHandlePropertiesById: 500: $ref: 'components.yaml#/components/responses/InternalServerError' -queryCmHandles: +searchCmHandleIds: post: - description: Execute cm handle query search + description: Execute cm handle query search, to be included in the result a cm-handle must fulfill ALL the conditions listed here, if one of the given module names does not exists, return with an empty collection. tags: - network-cm-proxy summary: Execute cm handle query upon a given set of query parameters - operationId: queryCmHandles + operationId: searchCmHandleIds requestBody: required: true content: application/json: schema: - $ref: 'components.yaml#/components/schemas/CmHandleQueryRestParameters' + $ref: 'components.yaml#/components/schemas/CmHandleQueryParameters' responses: 200: description: OK diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index b4082918f..81ebf05ed 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -36,7 +36,7 @@ paths: $ref: 'ncmp.yml#/fetchModuleReferencesByCmHandle' /v1/ch/searches: - $ref: 'ncmp.yml#/executeCmHandleSearch' + $ref: 'ncmp.yml#/searchCmHandles' /v1/ch/{cm-handle}: $ref: 'ncmp.yml#/retrieveCmHandleDetailsById' @@ -44,5 +44,5 @@ paths: /v1/ch/{cm-handle}/properties: $ref: 'ncmp.yml#/getCmHandlePropertiesById' - /v1/data/ch/searches: - $ref: 'ncmp.yml#/queryCmHandles' + /v1/ch/id-searches: + $ref: 'ncmp.yml#/searchCmHandleIds' 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 11517bcc9..ccb1e9bbb 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 @@ -28,9 +28,6 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH; import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,18 +45,12 @@ 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.mapper.RestOutputCmHandleStateMapper; -import org.onap.cps.ncmp.rest.model.CmHandleProperties; -import org.onap.cps.ncmp.rest.model.CmHandleProperty; import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties; -import org.onap.cps.ncmp.rest.model.CmHandleQueryRestParameters; -import org.onap.cps.ncmp.rest.model.CmHandles; -import org.onap.cps.ncmp.rest.model.ConditionProperties; -import org.onap.cps.ncmp.rest.model.Conditions; -import org.onap.cps.ncmp.rest.model.ModuleNameAsJsonObject; -import org.onap.cps.ncmp.rest.model.ModuleNamesAsJsonArray; +import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; import org.onap.cps.ncmp.rest.model.RestModuleReference; import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; 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; @@ -79,6 +70,8 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { 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 RestOutputCmHandleStateMapper restOutputCmHandleStateMapper; private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; @@ -212,30 +205,35 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Execute cm handle search. + * Query and return cm handles that match the given query parameters. * - * @param conditions the conditions - * @return cm handles returned from search. + * @param cmHandleQueryParameters the cm handle query parameters + * @return collection of cm handles */ @Override - public ResponseEntity executeCmHandleSearch(final Conditions conditions) { - final List conditionProperties = - conditions.getConditions().stream().collect(Collectors.toList()); - final CmHandles cmHandles = new CmHandles(); - cmHandles.setCmHandles(toCmHandleProperties(processConditions(conditionProperties))); - return ResponseEntity.ok(cmHandles); + public ResponseEntity> searchCmHandles( + final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryApiParameters cmHandleQueryApiParameters = + deprecationHelper.mapOldConditionProperties(cmHandleQueryParameters); + final Set cmHandles = networkCmProxyDataService + .executeCmHandleSearch(cmHandleQueryApiParameters); + final List outputCmHandles = + cmHandles.stream().map(this::toRestOutputCmHandle).collect(Collectors.toList()); + return ResponseEntity.ok(outputCmHandles); } /** - * Query and return cm handles that match the given query parameters. + * Query and return cm handle ids that match the given query parameters. * - * @param cmHandleQueryRestParameters the cm handle query parameters + * @param cmHandleQueryParameters the cm handle query parameters * @return collection of cm handle ids */ - public ResponseEntity> queryCmHandles( - final CmHandleQueryRestParameters cmHandleQueryRestParameters) { - final Set cmHandleIds = networkCmProxyDataService.queryCmHandles( - jsonObjectMapper.convertToValueType(cmHandleQueryRestParameters, CmHandleQueryApiParameters.class)); + @Override + public ResponseEntity> searchCmHandleIds( + final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryApiParameters cmHandleQueryApiParameters = + jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); + final Set cmHandleIds = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters); return ResponseEntity.ok(List.copyOf(cmHandleIds)); } @@ -281,41 +279,6 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { return new ResponseEntity<>(restModuleReferences, HttpStatus.OK); } - private Collection processConditions(final List conditionProperties) { - for (final ConditionProperties conditionProperty : conditionProperties) { - if (conditionProperty.getName().equals("hasAllModules")) { - return executeCmHandleSearchesForModuleNames(conditionProperty); - } else { - log.warn("Unrecognized condition name {}.", conditionProperty.getName()); - } - } - log.warn("No valid conditions found {}.", conditionProperties); - return Collections.emptyList(); - } - - private Collection executeCmHandleSearchesForModuleNames(final ConditionProperties conditionProperties) { - return networkCmProxyDataService - .executeCmHandleHasAllModulesSearch(getModuleNames(conditionProperties.getConditionParameters())); - } - - private Collection getModuleNames(final ModuleNamesAsJsonArray moduleNamesAsJsonArray) { - final Collection moduleNames = new ArrayList<>(moduleNamesAsJsonArray.size()); - for (final ModuleNameAsJsonObject moduleNameAsJsonObject : moduleNamesAsJsonArray) { - moduleNames.add(moduleNameAsJsonObject.getModuleName()); - } - return moduleNames; - } - - private CmHandleProperties toCmHandleProperties(final Collection cmHandleIdentifiers) { - final CmHandleProperties cmHandleProperties = new CmHandleProperties(); - for (final String cmHandleIdentifier : cmHandleIdentifiers) { - final CmHandleProperty cmHandleProperty = new CmHandleProperty(); - cmHandleProperty.setCmHandleId(cmHandleIdentifier); - cmHandleProperties.add(cmHandleProperty); - } - return cmHandleProperties; - } - private RestOutputCmHandle toRestOutputCmHandle(final NcmpServiceCmHandle ncmpServiceCmHandle) { final RestOutputCmHandle restOutputCmHandle = new RestOutputCmHandle(); final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java index 93aa2858c..5adbb252a 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutor.java @@ -40,11 +40,7 @@ public class CpsNcmpTaskExecutor { public void executeTask(final Supplier taskSupplier, final int timeOutInMillis) { CompletableFuture.supplyAsync(taskSupplier::get) .orTimeout(timeOutInMillis, MILLISECONDS) - .whenCompleteAsync( - (responseAsJson, throwable) -> { - handleTaskCompletion(throwable); - } - ); + .whenCompleteAsync((responseAsJson, throwable) -> handleTaskCompletion(throwable)); } private void handleTaskCompletion(final Throwable throwable) { diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java new file mode 100644 index 000000000..fc992da41 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/DeprecationHelper.java @@ -0,0 +1,73 @@ +/* + * ============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.ArrayList; +import java.util.Collections; +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; +import org.onap.cps.ncmp.api.models.ConditionApiProperties; +import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class DeprecationHelper { + + private final JsonObjectMapper jsonObjectMapper; + + /** + * Convert the old condition properties to the new schema. + * !!! remove it after the old condition removed !!! + * it only works for module names + * + * @param cmHandleQueryParameters the original input parameter + */ + @Deprecated //this method wil be removed in Release 12 (No Name know yet) + public CmHandleQueryApiParameters mapOldConditionProperties( + final CmHandleQueryParameters cmHandleQueryParameters) { + final CmHandleQueryApiParameters cmHandleQueryApiParameters = + jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); + if (cmHandleQueryParameters.getConditions() != null + && cmHandleQueryApiParameters.getCmHandleQueryParameters().isEmpty()) { + cmHandleQueryApiParameters.setCmHandleQueryParameters(new ArrayList<>()); + cmHandleQueryParameters.getConditions().parallelStream().forEach( + oldConditionProperty -> { + if (oldConditionProperty.getConditionParameters() != null + && oldConditionProperty.getName() != null) { + final ConditionApiProperties conditionApiProperties = new ConditionApiProperties(); + conditionApiProperties.setConditionName(oldConditionProperty.getName()); + conditionApiProperties.setConditionParameters(new ArrayList<>()); + oldConditionProperty.getConditionParameters().parallelStream().forEach( + oldConditionParameter -> + conditionApiProperties.getConditionParameters().add(Collections + .singletonMap("moduleName", oldConditionParameter.getModuleName())) + ); + cmHandleQueryApiParameters.getCmHandleQueryParameters().add(conditionApiProperties); + } + } + ); + } + + return cmHandleQueryApiParameters; + } +} 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 60ea736d7..036928fe3 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 @@ -29,6 +29,7 @@ import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.ncmp.rest.mapper.RestOutputCmHandleStateMapper import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.util.DeprecationHelper import spock.lang.Shared import java.time.OffsetDateTime @@ -86,6 +87,9 @@ class NetworkCmProxyControllerSpec extends Specification { @SpringBean CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy() + @SpringBean + DeprecationHelper stubbedDeprecationHelper = Stub() + @Value('${rest.api.ncmp-base-path}/v1') def ncmpBasePathV1 @@ -239,8 +243,14 @@ class NetworkCmProxyControllerSpec extends Specification { given: 'an endpoint and json data' def searchesEndpoint = "$ncmpBasePathV1/ch/searches" String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json') - and: 'the service method is invoked with module names and returns two cm handle ids' - mockNetworkCmProxyDataService.executeCmHandleHasAllModulesSearch(['module1', 'module2']) >> ['some-cmhandle-id1', 'some-cmhandle-id2'] + 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'] + def cmHandle2 = new NcmpServiceCmHandle() + cmHandle2.cmHandleId = 'some-cmhandle-id2' + cmHandle2.publicProperties = [color:'green'] + mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) @@ -248,7 +258,7 @@ class NetworkCmProxyControllerSpec extends Specification { then: 'response status returns OK' response.status == HttpStatus.OK.value() and: 'the expected response content is returned' - response.contentAsString == '{"cmHandles":[{"cmHandleId":"some-cmhandle-id1"},{"cmHandleId":"some-cmhandle-id2"}]}' + response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]' } def 'Get Cm Handle details by Cm Handle id.'() { @@ -290,31 +300,38 @@ class NetworkCmProxyControllerSpec extends Specification { given: 'an endpoint and json data' def searchesEndpoint = "$ncmpBasePathV1/ch/searches" String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json') + 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'] + def cmHandel2 = new NcmpServiceCmHandle() + cmHandel2.cmHandleId = 'some-cmhandle-id2' + cmHandel2.publicProperties = [color:'green'] + mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) .content(jsonString)).andReturn().response then: 'an empty cm handle identifier is returned' - response.contentAsString == '{"cmHandles":[]}' + response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]' } def 'Query for cm handles matching query parameters'() { given: 'an endpoint and json data' - def searchesEndpoint = "$ncmpBasePathV1/data/ch/searches" - String jsonString = '{"publicCmHandleProperties": {"name": "Contact", "value": "newemailforstore@bookstore.com"}}' + def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" and: 'the service method is invoked with module names and returns cm handle ids' - 1 * mockNetworkCmProxyDataService.queryCmHandles(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2'] + 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(jsonString)).andReturn().response + .content('{}')).andReturn().response then: 'cm handle ids are returned' response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]' } def 'Query for cm handles with invalid request payload'() { when: 'the searches api is invoked' - def searchesEndpoint = "$ncmpBasePathV1/data/ch/searches" + def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" def invalidInputData = '{invalidJson}' def response = mvc.perform(post(searchesEndpoint) .contentType(MediaType.APPLICATION_JSON) 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 45ed3d307..1563c75b3 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 @@ -31,6 +31,7 @@ import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper import org.onap.cps.ncmp.rest.mapper.RestOutputCmHandleStateMapper import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor +import org.onap.cps.ncmp.rest.util.DeprecationHelper import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException @@ -47,7 +48,6 @@ 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_GATEWAY 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 @@ -75,6 +75,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean CpsNcmpTaskExecutor stubbedCpsTaskExecutor = Stub() + @SpringBean + DeprecationHelper stubbedDeprecationHelper = Stub() + @Value('${rest.api.ncmp-base-path}') def basePathNcmp diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/DeprecationHelperSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/DeprecationHelperSpec.groovy new file mode 100644 index 000000000..8c212d353 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/DeprecationHelperSpec.groovy @@ -0,0 +1,54 @@ +/* + * ============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 com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters +import org.onap.cps.ncmp.api.models.ConditionApiProperties +import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters +import org.onap.cps.ncmp.rest.model.ConditionProperties +import org.onap.cps.ncmp.rest.model.ModuleNameAsJsonObject +import org.onap.cps.ncmp.rest.model.OldConditionProperties +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +class DeprecationHelperSpec extends Specification { + + DeprecationHelper deprecationHelper = new DeprecationHelper(new JsonObjectMapper(new ObjectMapper())) + + def 'Map deprecated condition properties - #scenario.'() { + given: 'a deprecated condition properties' + def cmHandleQueryParameters = new CmHandleQueryParameters() + cmHandleQueryParameters.conditions = oldConditionPropertiesArray + cmHandleQueryParameters.cmHandleQueryParameters = cmHandleQueryParametersArray + when: 'converted into the new format' + def result = deprecationHelper.mapOldConditionProperties(cmHandleQueryParameters) + then: 'result is the expected' + assert result == new CmHandleQueryApiParameters(cmHandleQueryParameters: expectedCmHandleQueryApiParametersArray) + where: + scenario | oldConditionPropertiesArray | cmHandleQueryParametersArray || expectedCmHandleQueryApiParametersArray + 'mapping old query' | [new OldConditionProperties(name: 'hasAllModule', conditionParameters: [new ModuleNameAsJsonObject(moduleName: 'module-1')])] | [] || [new ConditionApiProperties(conditionName: 'hasAllModule', conditionParameters: [[moduleName:'module-1']])] + 'old condition is null' | null | [] || [] + 'old condition parameters is null' | [new OldConditionProperties(name: 'hasAllModule', conditionParameters: null)] | [] || [] + 'old condition name is null' | [new OldConditionProperties(name: null, conditionParameters: [new ModuleNameAsJsonObject(moduleName: 'module-1')])] | [] || [] + 'new query parameters are filled' | [new OldConditionProperties(name: 'hasAllModule', conditionParameters: [new ModuleNameAsJsonObject(moduleName: 'module-1')])] | [new ConditionProperties(conditionName: 'hasAllModule', conditionParameters: [[moduleName:'module-2']])] || [new ConditionApiProperties(conditionName: 'hasAllModule', conditionParameters: [[moduleName:'module-2']])] + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java new file mode 100644 index 000000000..f8d51feba --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java @@ -0,0 +1,35 @@ +/* + * ============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; + +import java.util.Collection; +import org.onap.cps.spi.model.CmHandleQueryParameters; +import org.onap.cps.spi.model.DataNode; + +public interface NetworkCmProxyCmHandlerQueryService { + /** + * Query and return cm handles that match the given query parameters. + * + * @param cmHandleQueryParameters the cm handle query parameters + * @return collection of cm handles + */ + Collection queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters); +} 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 7527ae5c5..ce850cc82 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 @@ -105,15 +105,6 @@ public interface NetworkCmProxyDataService { */ Collection getYangResourcesModuleReferences(String cmHandleId); - /** - * Query cm handle identifiers for the given collection of module names. - * - * @param moduleNames module names. - * @return a collection of cm handle identifiers. The schema set for each cm handle must include all the - * given module names - */ - Collection executeCmHandleHasAllModulesSearch(Collection moduleNames); - /** * Query cm handle details by cm handle's name. * @@ -134,7 +125,15 @@ public interface NetworkCmProxyDataService { * Query and return cm handles that match the given query parameters. * * @param cmHandleQueryApiParameters the cm handle query parameters + * @return collection of cm handles + */ + Set executeCmHandleSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters); + + /** + * Query and return cm handle ids that match the given query parameters. + * + * @param cmHandleQueryApiParameters the cm handle query parameters * @return collection of cm handle ids */ - Set queryCmHandles(CmHandleQueryApiParameters cmHandleQueryApiParameters); + Set executeCmHandleIdSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java new file mode 100644 index 000000000..ef6e953e2 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java @@ -0,0 +1,180 @@ +/* + * ============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.impl; + +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DATASPACE_NAME; +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DMI_REGISTRY_ANCHOR; +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; +import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateModuleNameConditionProperties; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService; +import org.onap.cps.spi.CpsAdminPersistenceService; +import org.onap.cps.spi.CpsDataPersistenceService; +import org.onap.cps.spi.model.Anchor; +import org.onap.cps.spi.model.CmHandleQueryParameters; +import org.onap.cps.spi.model.ConditionProperties; +import org.onap.cps.spi.model.DataNode; +import org.onap.cps.spi.model.DataNodeIdentifier; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCmHandlerQueryService { + + private static final String PROPERTY_QUERY_NAME = "hasAllProperties"; + private static final String MODULE_QUERY_NAME = "hasAllModules"; + private final CpsDataPersistenceService cpsDataPersistenceService; + private final CpsAdminPersistenceService cpsAdminPersistenceService; + private final JsonObjectMapper jsonObjectMapper; + + /** + * Query and return cm handles that match the given query parameters. + * + * @param cmHandleQueryParameters the cm handle query parameters + * @return collection of cm handles + */ + @Override + public Collection queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) { + + if (cmHandleQueryParameters.getCmHandleQueryParameters().isEmpty()) { + return getAllCmHandles(); + } + + final Collection amalgamatedQueryResultIdentifiers = new ArrayList<>(); + final Map amalgamatedQueryResults = new HashMap<>(); + + final boolean firstQuery = moduleNameQuery(cmHandleQueryParameters, + amalgamatedQueryResultIdentifiers, amalgamatedQueryResults); + + publicPropertyQuery(cmHandleQueryParameters, amalgamatedQueryResultIdentifiers, + amalgamatedQueryResults, firstQuery); + + final Collection filteredDataNodes = new ArrayList<>(); + amalgamatedQueryResultIdentifiers.forEach(amalgamatedQueryResultIdentifier -> + filteredDataNodes.add(amalgamatedQueryResults.get(amalgamatedQueryResultIdentifier)) + ); + + return filteredDataNodes; + } + + private void publicPropertyQuery(final CmHandleQueryParameters cmHandleQueryParameters, + final Collection amalgamatedQueryResultIdentifiers, + final Map amalgamatedQueryResults, + boolean firstQuery) { + for (final Map.Entry entry : + getPublicPropertyPairs(cmHandleQueryParameters.getCmHandleQueryParameters()).entrySet()) { + final String cmHandlePath = "//public-properties[@name='" + entry.getKey() + "' " + "and @value='" + + entry.getValue() + "']" + "/ancestor::cm-handles"; + + final Collection dataNodes = getDataNodes(cmHandlePath); + + if (firstQuery) { + firstQuery = false; + dataNodes.forEach(dataNode -> { + final DataNodeIdentifier dataNodeIdentifier = + jsonObjectMapper.convertToValueType(dataNode, DataNodeIdentifier.class); + amalgamatedQueryResultIdentifiers.add(dataNodeIdentifier); + amalgamatedQueryResults.put(dataNodeIdentifier, dataNode); + }); + } else { + final Collection singleConditionQueryDataNodeIdentifiers = new ArrayList<>(); + dataNodes.forEach(dataNode -> { + final DataNodeIdentifier dataNodeIdentifier = + jsonObjectMapper.convertToValueType(dataNode, DataNodeIdentifier.class); + singleConditionQueryDataNodeIdentifiers.add(dataNodeIdentifier); + amalgamatedQueryResults.put(dataNodeIdentifier, dataNode); + }); + amalgamatedQueryResultIdentifiers.retainAll(singleConditionQueryDataNodeIdentifiers); + } + + if (amalgamatedQueryResultIdentifiers.isEmpty()) { + break; + } + } + } + + private boolean moduleNameQuery(final CmHandleQueryParameters cmHandleQueryParameters, + final Collection amalgamatedQueryResultIdentifiers, + final Map amalgamatedQueryResults) { + boolean firstQuery = true; + if (!getModuleNames(cmHandleQueryParameters.getCmHandleQueryParameters()).isEmpty()) { + final Collection anchors = cpsAdminPersistenceService.queryAnchors("NFP-Operational", + getModuleNames(cmHandleQueryParameters.getCmHandleQueryParameters())); + anchors.forEach(anchor -> { + final List dataNodes = getDataNodes("//cm-handles[@id='" + anchor.getName() + "']"); + dataNodes.parallelStream().forEach(dataNode -> { + final DataNodeIdentifier dataNodeIdentifier = + jsonObjectMapper.convertToValueType(dataNode, DataNodeIdentifier.class); + amalgamatedQueryResultIdentifiers.add(dataNodeIdentifier); + amalgamatedQueryResults.put(dataNodeIdentifier, dataNode); + }); + }); + firstQuery = false; + } + return firstQuery; + } + + private List> getConditions(final List conditionProperties, + final String name) { + for (final ConditionProperties conditionProperty : conditionProperties) { + if (conditionProperty.getConditionName().equals(name)) { + return conditionProperty.getConditionParameters(); + } + } + return Collections.emptyList(); + } + + private List getModuleNames(final List conditionProperties) { + final List result = new ArrayList<>(); + getConditions(conditionProperties, MODULE_QUERY_NAME).parallelStream().forEach( + conditionProperty -> { + validateModuleNameConditionProperties(conditionProperty); + result.add(conditionProperty.get("moduleName")); + } + ); + return result; + } + + private Map getPublicPropertyPairs(final List conditionProperties) { + final Map result = new HashMap<>(); + getConditions(conditionProperties, PROPERTY_QUERY_NAME).forEach(result::putAll); + return result; + } + + private Collection getAllCmHandles() { + return getDataNodes("//public-properties/ancestor::cm-handles"); + } + + private List getDataNodes(final String cmHandlePath) { + return cpsDataPersistenceService.queryDataNodes( + NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlePath, INCLUDE_ALL_DESCENDANTS); + } +} 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 717cae565..d1f72a5ef 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 @@ -30,12 +30,11 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPER import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP; import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED; +import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters; -import com.google.common.base.Strings; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -45,10 +44,13 @@ import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; import org.onap.cps.ncmp.api.impl.operations.DmiOperations; +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.InventoryPersistence; import org.onap.cps.ncmp.api.inventory.sync.ModuleSyncService; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; @@ -61,6 +63,7 @@ import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; +import org.onap.cps.spi.model.CmHandleQueryParameters; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; @@ -88,21 +91,23 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private final ModuleSyncService moduleSyncService; + private final NetworkCmProxyCmHandlerQueryService networkCmProxyCmHandlerQueryService; + @Override public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { + final DmiPluginRegistration dmiPluginRegistration) { dmiPluginRegistration.validateDmiPluginRegistration(); final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); dmiPluginRegistrationResponse.setRemovedCmHandles( - parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles())); + parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles())); if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) { dmiPluginRegistrationResponse.setCreatedCmHandles( - parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)); + parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)); } if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) { dmiPluginRegistrationResponse.setUpdatedCmHandles( - networkCmProxyDataServicePropertyHandler - .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles())); + networkCmProxyDataServicePropertyHandler + .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles())); } return dmiPluginRegistrationResponse; } @@ -154,28 +159,35 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } /** - * Retrieve cm handle identifiers for the given list of module names. + * Retrieve cm handles with details for the given query parameters. * - * @param moduleNames module names. - * @return a collection of anchor identifiers + * @param cmHandleQueryApiParameters cm handle query parameters + * @return cm handles with details */ @Override - public Collection executeCmHandleHasAllModulesSearch(final Collection moduleNames) { - return cpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNames); - } + public Set executeCmHandleSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { - @Override - public Set queryCmHandles(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + final CmHandleQueryParameters cmHandleQueryParameters = jsonObjectMapper.convertToValueType( + cmHandleQueryApiParameters, CmHandleQueryParameters.class); - cmHandleQueryApiParameters.getPublicProperties().forEach((key, value) -> { - if (Strings.isNullOrEmpty(key)) { - throw new DataValidationException("Invalid Query Parameter.", - "Missing property name - please supply a valid name."); - } - }); + validateCmHandleQueryParameters(cmHandleQueryParameters); - return cpsAdminService.queryCmHandles(jsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, - org.onap.cps.spi.model.CmHandleQueryParameters.class)); + return networkCmProxyCmHandlerQueryService.queryCmHandles(cmHandleQueryParameters).stream() + .map(dataNode -> YangDataConverter + .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())) + .map(YangDataConverter::convertYangModelCmHandleToNcmpServiceCmHandle).collect(Collectors.toSet()); + } + + /** + * Retrieve cm handle ids for the given query parameters. + * + * @param cmHandleQueryApiParameters cm handle query parameters + * @return cm handle ids + */ + @Override + public Set executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + return executeCmHandleSearch(cmHandleQueryApiParameters).stream().map(NcmpServiceCmHandle::getCmHandleId) + .collect(Collectors.toSet()); } /** @@ -187,16 +199,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService @Override public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) { CpsValidator.validateNameCharacters(cmHandleId); - final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle(); - final YangModelCmHandle yangModelCmHandle = - inventoryPersistence.getYangModelCmHandle(cmHandleId); - final List dmiProperties = yangModelCmHandle.getDmiProperties(); - final List publicProperties = yangModelCmHandle.getPublicProperties(); - ncmpServiceCmHandle.setCmHandleId(yangModelCmHandle.getId()); - ncmpServiceCmHandle.setCompositeState(yangModelCmHandle.getCompositeState()); - setDmiProperties(dmiProperties, ncmpServiceCmHandle); - setPublicProperties(publicProperties, ncmpServiceCmHandle); - return ncmpServiceCmHandle; + return YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle( + inventoryPersistence.getYangModelCmHandle(cmHandleId)); } /** @@ -212,7 +216,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService inventoryPersistence.getYangModelCmHandle(cmHandleId); final List yangModelPublicProperties = yangModelCmHandle.getPublicProperties(); final Map cmHandlePublicProperties = new HashMap<>(); - asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties); + YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties); return cmHandlePublicProperties; } @@ -223,7 +227,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService * @return cm-handle registration response for create cm-handle requests. */ public List parseAndCreateCmHandlesInDmiRegistrationAndSyncModules( - final DmiPluginRegistration dmiPluginRegistration) { + final DmiPluginRegistration dmiPluginRegistration) { List cmHandleRegistrationResponses = new ArrayList<>(); try { cmHandleRegistrationResponses = dmiPluginRegistration.getCreatedCmHandles().stream() @@ -231,52 +235,47 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService YangModelCmHandle.toYangModelCmHandle( dmiPluginRegistration.getDmiPlugin(), dmiPluginRegistration.getDmiDataPlugin(), - dmiPluginRegistration.getDmiModelPlugin(), cmHandle) + dmiPluginRegistration.getDmiModelPlugin(), + CmHandleState.ADVISED, + cmHandle) ) - .map(this::registerAndSyncNewCmHandle) + .map(this::registerNewCmHandle) .collect(Collectors.toList()); } catch (final DataValidationException dataValidationException) { cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createFailureResponse(dmiPluginRegistration - .getCreatedCmHandles().stream() - .map(NcmpServiceCmHandle::getCmHandleId).findFirst().orElse(null), - RegistrationError.CM_HANDLE_INVALID_ID)); + .getCreatedCmHandles().stream() + .map(NcmpServiceCmHandle::getCmHandleId).findFirst().orElse(null), + RegistrationError.CM_HANDLE_INVALID_ID)); } return cmHandleRegistrationResponses; } - protected void syncModulesAndCreateAnchor(final YangModelCmHandle yangModelCmHandle) { - final String schemaSetName = moduleSyncService.syncAndCreateSchemaSet(yangModelCmHandle); - final String anchorName = yangModelCmHandle.getId(); - cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, - anchorName); - } - protected List parseAndRemoveCmHandlesInDmiRegistration( - final List tobeRemovedCmHandles) { + final List tobeRemovedCmHandles) { final List cmHandleRegistrationResponses = - new ArrayList<>(tobeRemovedCmHandles.size()); + new ArrayList<>(tobeRemovedCmHandles.size()); for (final String cmHandle : tobeRemovedCmHandles) { try { CpsValidator.validateNameCharacters(cmHandle); deleteSchemaSetWithCascade(cmHandle); cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - "/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP); + "/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP); cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandle)); } catch (final DataNodeNotFoundException dataNodeNotFoundException) { log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}", - cmHandle, dataNodeNotFoundException.getMessage()); + cmHandle, dataNodeNotFoundException.getMessage()); cmHandleRegistrationResponses.add(CmHandleRegistrationResponse - .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST)); + .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST)); } catch (final DataValidationException dataValidationException) { log.error("Unable to de-register cm-handle id: {}, caused by: {}", - cmHandle, dataValidationException.getMessage()); + cmHandle, dataValidationException.getMessage()); cmHandleRegistrationResponses.add(CmHandleRegistrationResponse - .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_INVALID_ID)); + .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_INVALID_ID)); } catch (final Exception exception) { log.error("Unable to de-register cm-handle id : {} , caused by : {}", - cmHandle, exception.getMessage()); + cmHandle, exception.getMessage()); cmHandleRegistrationResponses.add( - CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception)); + CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception)); } } return cmHandleRegistrationResponses; @@ -285,47 +284,24 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private void deleteSchemaSetWithCascade(final String schemaSetName) { try { cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, - CASCADE_DELETE_ALLOWED); + CASCADE_DELETE_ALLOWED); } catch (final SchemaSetNotFoundException schemaSetNotFoundException) { log.warn("Schema set {} does not exist or already deleted", schemaSetName); } } - private void setDmiProperties(final List dmiProperties, - final NcmpServiceCmHandle ncmpServiceCmHandle) { - final Map dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size()); - asPropertiesMap(dmiProperties, dmiPropertiesMap); - ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap); - } - - private void setPublicProperties(final List publicProperties, - final NcmpServiceCmHandle ncmpServiceCmHandle) { - final Map publicPropertiesMap = new LinkedHashMap<>(); - asPropertiesMap(publicProperties, publicPropertiesMap); - ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap); - } - - private void asPropertiesMap(final List properties, - final Map propertiesMap) { - for (final YangModelCmHandle.Property property: properties) { - propertiesMap.put(property.getName(), property.getValue()); - } - } - - private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) { + private CmHandleRegistrationResponse registerNewCmHandle(final YangModelCmHandle yangModelCmHandle) { try { final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}", - jsonObjectMapper.asJsonString(yangModelCmHandle)); + jsonObjectMapper.asJsonString(yangModelCmHandle)); cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - cmHandleJsonData, NO_TIMESTAMP); - syncModulesAndCreateAnchor(yangModelCmHandle); + cmHandleJsonData, NO_TIMESTAMP); return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId()); } catch (final AlreadyDefinedException alreadyDefinedException) { return CmHandleRegistrationResponse.createFailureResponse( - yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST); + yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST); } catch (final Exception exception) { return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception); } } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsPublisher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsPublisher.java new file mode 100644 index 000000000..52ac4685e --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsPublisher.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.api.impl.event; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.ncmp.cmhandle.lcm.event.NcmpEvent; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.SendResult; +import org.springframework.stereotype.Service; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; + +/** + * NcmpEventsPublisher to publish the NcmpEvents on event of CREATE, UPDATE and DELETE. + */ + +@Slf4j +@Service +@RequiredArgsConstructor +public class NcmpEventsPublisher { + + private final KafkaTemplate ncmpEventKafkaTemplate; + + /** + * NCMP Event publisher. + * + * @param topicName valid topic name + * @param eventKey message key + * @param ncmpEvent message payload + */ + public void publishEvent(final String topicName, final String eventKey, final NcmpEvent ncmpEvent) { + final ListenableFuture> ncmpEventFuture = + ncmpEventKafkaTemplate.send(topicName, eventKey, ncmpEvent); + + ncmpEventFuture.addCallback(new ListenableFutureCallback<>() { + @Override + public void onFailure(final Throwable throwable) { + log.error("Unable to publish event to topic : {} due to {}", topicName, throwable.getMessage()); + } + + @Override + public void onSuccess(final SendResult result) { + log.debug("Successfully published event to topic : {} , NcmpEvent : {}", + result.getRecordMetadata().topic(), result.getProducerRecord().value()); + } + }); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java new file mode 100644 index 000000000..82ea00eb3 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java @@ -0,0 +1,124 @@ +/* + * ============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.impl.utils; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.inventory.CompositeState; +import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder; +import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.model.DataNode; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class YangDataConverter { + + /** + * This method convert yang model cm handle to ncmp service cm handle. + * @param yangModelCmHandle the yang model of the cm handle + * @return ncmp service cm handle + */ + public static NcmpServiceCmHandle convertYangModelCmHandleToNcmpServiceCmHandle( + final YangModelCmHandle yangModelCmHandle) { + final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle(); + final List dmiProperties = yangModelCmHandle.getDmiProperties(); + final List publicProperties = yangModelCmHandle.getPublicProperties(); + ncmpServiceCmHandle.setCmHandleId(yangModelCmHandle.getId()); + ncmpServiceCmHandle.setCompositeState(yangModelCmHandle.getCompositeState()); + setDmiProperties(dmiProperties, ncmpServiceCmHandle); + setPublicProperties(publicProperties, ncmpServiceCmHandle); + return ncmpServiceCmHandle; + } + + /** + * This method convert yang model cm handle properties to simple map. + * @param properties the yang model cm handle properties + * @param propertiesMap the String, String map for the results + */ + public static void asPropertiesMap(final List properties, + final Map propertiesMap) { + for (final YangModelCmHandle.Property property : properties) { + propertiesMap.put(property.getName(), property.getValue()); + } + } + + /** + * This method convert cm handle data node to yang model cm handle. + * @param cmHandleDataNode the datanode of the cm handle + * @param cmHandleId the id of the cm handle + * @return yang model cm handle + */ + public static YangModelCmHandle convertCmHandleToYangModel(final DataNode cmHandleDataNode, + final String cmHandleId) { + final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle(); + ncmpServiceCmHandle.setCmHandleId(cmHandleId); + populateCmHandleDetails(cmHandleDataNode, ncmpServiceCmHandle); + return YangModelCmHandle.toYangModelCmHandle( + (String) cmHandleDataNode.getLeaves().get("dmi-service-name"), + (String) cmHandleDataNode.getLeaves().get("dmi-data-service-name"), + (String) cmHandleDataNode.getLeaves().get("dmi-model-service-name"), + ncmpServiceCmHandle.getCompositeState().getCmHandleState(), + ncmpServiceCmHandle + ); + } + + private static void populateCmHandleDetails(final DataNode cmHandleDataNode, + final NcmpServiceCmHandle ncmpServiceCmHandle) { + final Map dmiProperties = new LinkedHashMap<>(); + final Map publicProperties = new LinkedHashMap<>(); + final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder(); + CompositeState compositeState = compositeStateBuilder.build(); + for (final DataNode childDataNode: cmHandleDataNode.getChildDataNodes()) { + if (childDataNode.getXpath().contains("/additional-properties[@name=")) { + addProperty(childDataNode, dmiProperties); + } else if (childDataNode.getXpath().contains("/public-properties[@name=")) { + addProperty(childDataNode, publicProperties); + } else if (childDataNode.getXpath().endsWith("/state")) { + compositeState = compositeStateBuilder.fromDataNode(childDataNode).build(); + } + } + ncmpServiceCmHandle.setDmiProperties(dmiProperties); + ncmpServiceCmHandle.setPublicProperties(publicProperties); + ncmpServiceCmHandle.setCompositeState(compositeState); + } + + private static void addProperty(final DataNode propertyDataNode, final Map propertiesAsMap) { + propertiesAsMap.put(String.valueOf(propertyDataNode.getLeaves().get("name")), + String.valueOf(propertyDataNode.getLeaves().get("value"))); + } + + private static void setDmiProperties(final List dmiProperties, + final NcmpServiceCmHandle ncmpServiceCmHandle) { + final Map dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size()); + asPropertiesMap(dmiProperties, dmiPropertiesMap); + ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap); + } + + private static void setPublicProperties(final List publicProperties, + final NcmpServiceCmHandle ncmpServiceCmHandle) { + final Map publicPropertiesMap = new LinkedHashMap<>(); + asPropertiesMap(publicProperties, publicPropertiesMap); + ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap); + } +} 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 65e03f1f9..5b719054a 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 @@ -34,7 +34,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; +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.models.NcmpServiceCmHandle; import org.onap.cps.utils.CpsValidator; @@ -68,6 +70,8 @@ public class YangModelCmHandle { @JsonProperty("public-properties") private List publicProperties; + private static final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder(); + /** * Create a yangModelCmHandle. * @@ -80,6 +84,7 @@ public class YangModelCmHandle { public static YangModelCmHandle toYangModelCmHandle(final String dmiServiceName, final String dmiDataServiceName, final String dmiModelServiceName, + final CmHandleState cmHandleState, final NcmpServiceCmHandle ncmpServiceCmHandle) { CpsValidator.validateNameCharacters(ncmpServiceCmHandle.getCmHandleId()); final YangModelCmHandle yangModelCmHandle = new YangModelCmHandle(); @@ -90,7 +95,8 @@ public class YangModelCmHandle { yangModelCmHandle.setDmiProperties(asYangModelCmHandleProperties(ncmpServiceCmHandle.getDmiProperties())); yangModelCmHandle.setPublicProperties(asYangModelCmHandleProperties( ncmpServiceCmHandle.getPublicProperties())); - yangModelCmHandle.setCompositeState(ncmpServiceCmHandle.getCompositeState()); + compositeStateBuilder.withCmHandleState(cmHandleState); + yangModelCmHandle.setCompositeState(compositeStateBuilder.build()); return yangModelCmHandle; } 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 873a44913..2fc2dc5c1 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 @@ -21,13 +21,11 @@ package org.onap.cps.ncmp.api.inventory; import java.time.OffsetDateTime; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsDataService; +import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; -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; @@ -43,6 +41,8 @@ public class InventoryPersistence { private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; + private static final String XPATH_TO_CM_HANDLE = "/dmi-registry/cm-handles[@id='" + "%s" + "']"; + private final JsonObjectMapper jsonObjectMapper; private final CpsDataService cpsDataService; @@ -59,7 +59,7 @@ public class InventoryPersistence { */ public CompositeState getCmHandleState(final String cmHandleId) { final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state", + String.format(XPATH_TO_CM_HANDLE, cmHandleId) + "/state", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); return compositeStateBuilder.fromDataNode(stateAsDataNode).build(); } @@ -74,7 +74,7 @@ public class InventoryPersistence { final String cmHandleJsonData = String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState)); cpsDataService.replaceNodeTree(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - "/dmi-registry/cm-handles[@id='" + cmHandleId + "']", + String.format(XPATH_TO_CM_HANDLE, cmHandleId), cmHandleJsonData, OffsetDateTime.now()); } @@ -92,55 +92,20 @@ public class InventoryPersistence { } /** - * This method retrieves DMI service name and DMI properties for a given cm handle. + * 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); - final DataNode cmHandleDataNode = getCmHandleDataNode(cmHandleId); - final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle(); - ncmpServiceCmHandle.setCmHandleId(cmHandleId); - populateCmHandleDetails(cmHandleDataNode, ncmpServiceCmHandle); - return YangModelCmHandle.toYangModelCmHandle( - (String) cmHandleDataNode.getLeaves().get("dmi-service-name"), - (String) cmHandleDataNode.getLeaves().get("dmi-data-service-name"), - (String) cmHandleDataNode.getLeaves().get("dmi-model-service-name"), - ncmpServiceCmHandle - ); + return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId); } private DataNode getCmHandleDataNode(final String cmHandle) { - final String xpathForDmiRegistryToFetchCmHandle = "/dmi-registry/cm-handles[@id='" + cmHandle + "']"; return cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - xpathForDmiRegistryToFetchCmHandle, + String.format(XPATH_TO_CM_HANDLE, cmHandle), FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); } - private static void populateCmHandleDetails(final DataNode cmHandleDataNode, - final NcmpServiceCmHandle ncmpServiceCmHandle) { - final Map dmiProperties = new LinkedHashMap<>(); - final Map publicProperties = new LinkedHashMap<>(); - final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder(); - CompositeState compositeState = compositeStateBuilder.build(); - for (final DataNode childDataNode: cmHandleDataNode.getChildDataNodes()) { - if (childDataNode.getXpath().contains("/additional-properties[@name=")) { - addProperty(childDataNode, dmiProperties); - } else if (childDataNode.getXpath().contains("/public-properties[@name=")) { - addProperty(childDataNode, publicProperties); - } else if (childDataNode.getXpath().endsWith("/state")) { - compositeState = compositeStateBuilder.fromDataNode(childDataNode).build(); - } - } - ncmpServiceCmHandle.setDmiProperties(dmiProperties); - ncmpServiceCmHandle.setPublicProperties(publicProperties); - ncmpServiceCmHandle.setCompositeState(compositeState); - } - - private static void addProperty(final DataNode propertyDataNode, final Map propertiesAsMap) { - propertiesAsMap.put(String.valueOf(propertyDataNode.getLeaves().get("name")), - String.valueOf(propertyDataNode.getLeaves().get("value"))); - } - } 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 1d00f0dc6..58e2bf345 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 @@ -28,6 +28,7 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; @@ -42,13 +43,14 @@ public class ModuleSyncService { private final DmiModelOperations dmiModelOperations; private final CpsModuleService cpsModuleService; + private final CpsAdminService cpsAdminService; + /** * This method registers a cm handle and initiates modules sync. * * @param yangModelCmHandle the yang model of cm handle. - * @return schemaSetName the name of the schema set (same as cm handle name). */ - public String syncAndCreateSchemaSet(final YangModelCmHandle yangModelCmHandle) { + public void syncAndCreateSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle) { final Collection moduleReferencesFromCmHandle = dmiModelOperations.getModuleReferences(yangModelCmHandle); @@ -68,17 +70,17 @@ public class ModuleSyncService { newModuleNameToContentMap = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferencesFromCmHandle); } - return createSchemaSet(yangModelCmHandle, existingModuleReferencesFromCmHandle, newModuleNameToContentMap); + createSchemaSetAndAnchor(yangModelCmHandle, newModuleNameToContentMap, existingModuleReferencesFromCmHandle); } - private String createSchemaSet(final YangModelCmHandle yangModelCmHandle, - final Collection existingModuleReferencesFromCmHandle, - final Map newModuleNameToContentMap) { - final String schemaSetName = yangModelCmHandle.getId(); - cpsModuleService - .createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, + private void createSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle, + final Map newModuleNameToContentMap, + final Collection existingModuleReferencesFromCmHandle) { + final String schemaSetAndAnchorName = yangModelCmHandle.getId(); + cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName, newModuleNameToContentMap, existingModuleReferencesFromCmHandle); - return schemaSetName; + 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/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java index 2187ec61c..bcc7daa39 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 @@ -51,7 +51,7 @@ public class ModuleSyncWatchdog { final String cmHandleId = advisedCmHandle.getId(); final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); try { - moduleSyncService.syncAndCreateSchemaSet(advisedCmHandle); + moduleSyncService.syncAndCreateSchemaSetAndAnchor(advisedCmHandle); compositeState.setCmHandleState(CmHandleState.READY); } catch (final Exception e) { compositeState.setCmHandleState(CmHandleState.LOCKED); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java index 3f584ed15..bf6600d97 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleQueryApiParameters.java @@ -20,22 +20,24 @@ package org.onap.cps.ncmp.api.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Collections; -import java.util.Map; +import java.util.List; import javax.validation.Valid; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -@Setter @Getter -@JsonInclude(Include.NON_NULL) +@Setter +@EqualsAndHashCode +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) public class CmHandleQueryApiParameters { - - @JsonProperty("publicCmHandleProperties") + @JsonProperty("cmHandleQueryParameters") @Valid - private Map publicProperties = Collections.emptyMap(); - + private List cmHandleQueryParameters = Collections.emptyList(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ConditionApiProperties.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ConditionApiProperties.java new file mode 100644 index 000000000..9f6d64e16 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ConditionApiProperties.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.api.models; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.validation.Valid; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class ConditionApiProperties { + @JsonProperty("conditionName") + private String conditionName = ""; + + @JsonProperty("conditionParameters") + @Valid + private List> conditionParameters = Collections.emptyList(); +} 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 new file mode 100644 index 000000000..a1ad9af19 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy @@ -0,0 +1,149 @@ +/* + * ============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.impl + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService +import org.onap.cps.spi.CpsAdminPersistenceService +import org.onap.cps.spi.CpsDataPersistenceService +import org.onap.cps.spi.model.Anchor +import org.onap.cps.spi.model.CmHandleQueryParameters +import org.onap.cps.spi.model.ConditionProperties +import org.onap.cps.spi.model.DataNode +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +import java.util.stream.Collectors + +class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { + + def cpsDataPersistenceService = Mock(CpsDataPersistenceService) + def cpsAdminPersistenceService = Mock(CpsAdminPersistenceService) + + NetworkCmProxyCmHandlerQueryService objectUnderTest = new NetworkCmProxyCmHandlerQueryServiceImpl( + cpsDataPersistenceService, cpsAdminPersistenceService, new JsonObjectMapper(new ObjectMapper()) + ) + + def 'Retrieve cm handles with public properties when #scenario.'() { + given: 'a condition property' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def conditionProperties = new ConditionProperties() + conditionProperties.conditionName = 'hasAllProperties' + conditionProperties.conditionParameters = publicProperties + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'mock services' + mockResponses() + when: 'the service is invoked' + def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct expected cm handles are returned' + returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toList()) == expectedCmHandleIds + where: 'the following data is used' + scenario | publicProperties || expectedCmHandleIds + 'single matching property' | [['Contact' : 'newemailforstore@bookstore.com']] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + 'public property dont match' | [['wont_match' : 'wont_match']] || [] + '2 properties, only one match (and)' | [['Contact' : 'newemailforstore@bookstore.com'], ['Contact2': 'newemailforstore2@bookstore.com']] || ['PNFDemo4'] + '2 properties, no match (and)' | [['Contact' : 'newemailforstore@bookstore.com'], ['Contact2': '']] || [] + } + + def 'Retrieve cm handles with module names when #scenario.'() { + given: 'a condition property' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def conditionProperties = new ConditionProperties() + conditionProperties.conditionName = 'hasAllModules' + conditionProperties.conditionParameters = moduleNames + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'mock services' + mockResponses() + when: 'the service is invoked' + def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct expected cm handles are returned' + returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toList()) == expectedCmHandleIds + where: 'the following data is used' + scenario | moduleNames || expectedCmHandleIds + 'single matching module name' | [['moduleName' : 'MODULE-NAME-001']] || ['PNFDemo2', 'PNFDemo3', 'PNFDemo'] + 'module name dont match' | [['moduleName' : 'MODULE-NAME-004']] || [] + '2 module names, only one match (and)' | [['moduleName' : 'MODULE-NAME-002'], ['moduleName': 'MODULE-NAME-003']] || ['PNFDemo4'] + '2 module names, no match (and)' | [['moduleName' : 'MODULE-NAME-002'], ['moduleName': 'MODULE-NAME-004']] || [] + } + + def 'Retrieve cm handles with combined queries when #scenario.'() { + given: 'condition properties' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def conditionProperties1 = new ConditionProperties() + conditionProperties1.conditionName = 'hasAllProperties' + conditionProperties1.conditionParameters = publicProperties + def conditionProperties2 = new ConditionProperties() + conditionProperties2.conditionName = 'hasAllModules' + conditionProperties2.conditionParameters = moduleNames + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties1,conditionProperties2]) + and: 'mock services' + mockResponses() + when: 'the service is invoked' + def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct expected cm handles are returned' + returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toList()) == expectedCmHandleIds + where: 'the following data is used' + scenario | moduleNames | publicProperties || expectedCmHandleIds + 'particularly intersect' | [['moduleName' : 'MODULE-NAME-001']] | [['Contact' : 'newemailforstore@bookstore.com']] || ['PNFDemo2', 'PNFDemo'] + 'empty intersect' | [['moduleName' : 'MODULE-NAME-004']] | [['Contact' : 'newemailforstore@bookstore.com']] || [] + 'total intersect' | [['moduleName' : 'MODULE-NAME-002']] | [['Contact2' : 'newemailforstore2@bookstore.com']] || ['PNFDemo4'] + } + + def 'Retrieve cm handles when the query is empty.'() { + given: 'mock services' + mockResponses() + when: 'the service is invoked' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct expected cm handles are returned' + returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toList()) == ['PNFDemo', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] + } + + void mockResponses() { + def pNFDemo = new DataNode(xpath: 'cmHandle/id[\'PNFDemo\']', leaves: ['id':'PNFDemo']) + def pNFDemo2 = new DataNode(xpath: 'cmHandle/id[\'PNFDemo2\']', leaves: ['id':'PNFDemo2']) + def pNFDemo3 = new DataNode(xpath: 'cmHandle/id[\'PNFDemo3\']', leaves: ['id':'PNFDemo3']) + def pNFDemo4 = new DataNode(xpath: 'cmHandle/id[\'PNFDemo4\']', leaves: ['id':'PNFDemo4']) + + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact\' and @value=\'newemailforstore@bookstore.com\']/ancestor::cm-handles', _) + >> [pNFDemo, pNFDemo2, pNFDemo4] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'wont_match\' and @value=\'wont_match\']/ancestor::cm-handles', _) + >> [] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact2\' and @value=\'newemailforstore2@bookstore.com\']/ancestor::cm-handles', _) + >> [pNFDemo4] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact2\' and @value=\'\']/ancestor::cm-handles', _) + >> [] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties/ancestor::cm-handles', _) + >> [pNFDemo, pNFDemo2, pNFDemo3, pNFDemo4] + cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo\']', _) >> [pNFDemo] + cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo2\']', _) >> [pNFDemo2] + cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo3\']', _) >> [pNFDemo3] + cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo4\']', _) >> [pNFDemo4] + + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-001']) >> [new Anchor(name: 'PNFDemo2'), new Anchor(name: 'PNFDemo3'), new Anchor(name: 'PNFDemo')] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-004']) >> [] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-003', 'MODULE-NAME-002']) >> [new Anchor(name: 'PNFDemo4')] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-002', 'MODULE-NAME-003']) >> [new Anchor(name: 'PNFDemo4')] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-004', 'MODULE-NAME-002']) >> [] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-002', 'MODULE-NAME-004']) >> [] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-002']) >> [new Anchor(name: 'PNFDemo2'), new Anchor(name: 'PNFDemo4')] + } +} 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 f56aea7f3..e9d02dfc7 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 @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService @@ -64,9 +65,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler) def mockInventoryPersistence = Mock(InventoryPersistence) def mockModuleSyncService = Mock(ModuleSyncService) + def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandlerQueryService) def noTimestamp = null - def objectUnderTest = getObjectUnderTestWithModelSyncDisabled() + def objectUnderTest = getObjectUnderTest() def 'DMI Registration: Create, Update & Delete operations are processed in the right order'() { given: 'a registration with operations of all three types' @@ -164,20 +166,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { } and: 'save list elements is invoked with the expected parameters' interaction { - def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}""" + def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","state":{"cm-handle-state":"ADVISED"},"additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}""" 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', expectedJsonData, noTimestamp) } - then: 'model sync is invoked with expected parameters' - 1 * objectUnderTest.syncModulesAndCreateAnchor(_) >> { YangModelCmHandle yangModelCmHandle -> - { - assert yangModelCmHandle.id == 'cmhandle' - assert yangModelCmHandle.dmiServiceName == 'my-server' - assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getPublicProperties()) == expectedPublicProperties - assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getDmiProperties()) == expectedDmiProperties - - } - } where: scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]' @@ -233,8 +225,6 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { assert it.registrationError == expectedError assert it.errorText == expectedErrorText } - and: 'model-sync is not invoked' - 0 * objectUnderTest.syncModulesAndCreateAnchor(_) where: scenario | cmHandleId | exception || expectedError | expectedErrorText 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' @@ -242,28 +232,6 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } - def 'Create CM-Handle Error Handling: Model Sync fails'() { - given: 'objects under test without disabled model sync' - def objectUnderTest = getObjectUnderTest() - and: 'a registration without cm-handle properties' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle')] - and: 'cm-handler models sync fails' - objectUnderTest.syncModulesAndCreateAnchor(*_) >> { throw new RuntimeException('Model-Sync failed') } - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a failure response is received' - response.getCreatedCmHandles().size() == 1 - with(response.getCreatedCmHandles().get(0)) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.registrationError == UNKNOWN_ERROR - assert it.errorText == 'Model-Sync failed' - } - and: 'cm-handle is registered' - 1 * mockCpsDataService.saveListElements(*_) - } - def 'Update CM-Handle: Update Operation Response is added to the response'() { given: 'a registration to update CmHandles' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', @@ -379,14 +347,9 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'an unexpected exception' | 'cmhandle' | new RuntimeException("Failed") || UNKNOWN_ERROR | 'Failed' } - def getObjectUnderTestWithModelSyncDisabled() { - def objectUnderTest = getObjectUnderTest() - objectUnderTest.syncModulesAndCreateAnchor(*_) >> null - return objectUnderTest - } - def getObjectUnderTest() { return Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, - mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockModuleSyncService)) + mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, + mockModuleSyncService, stubbedNetworkCmProxyCmHandlerQueryService)) } } 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 55a1a8d1f..6ba2a2c27 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 @@ -22,15 +22,19 @@ package org.onap.cps.ncmp.api.impl -import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService 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.InventoryPersistence +import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters +import org.onap.cps.ncmp.api.models.ConditionApiProperties import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.ncmp.api.inventory.sync.ModuleSyncService +import org.onap.cps.spi.model.CmHandleQueryParameters +import org.onap.cps.spi.model.ConditionProperties import spock.lang.Shared import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL @@ -61,6 +65,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) def mockModuleSyncService = Mock(ModuleSyncService) def mockDmiPluginRegistration = Mock(DmiPluginRegistration) + def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandlerQueryService) def NO_TOPIC = null def NO_REQUEST_ID = null @@ -70,7 +75,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, - mockCpsModuleService, mockCpsAdminService, nullNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockModuleSyncService) + mockCpsModuleService, mockCpsAdminService, nullNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, + mockModuleSyncService, mockCpsCmHandlerQueryService) def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']" @@ -160,13 +166,6 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { 0 * mockCpsModuleService.getYangResourcesModuleReferences(*_) } - def 'Get cm handle identifiers for the given module names.'() { - when: 'execute a cm handle search for the given module names' - objectUnderTest.executeCmHandleHasAllModulesSearch(['some-module-name']) - then: 'get anchor identifiers is invoked with the expected parameters' - 1 * mockCpsAdminService.queryAnchorNames('NFP-Operational', ['some-module-name']) - } - def 'Get a cm handle.'() { given: 'the system returns a yang modelled cm handle' def dmiServiceName = 'some service name' @@ -241,8 +240,28 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { then: 'validate params for creating anchor and list elements' 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', '{"cm-handles":[{"id":"some-cm-handle-id",' + - '"additional-properties":[],"public-properties":[]}]}', null) - 1 * mockCpsAdminService.createAnchor('NFP-Operational', null, - 'some-cm-handle-id') + '"state":{"cm-handle-state":"ADVISED"},' + + '"additional-properties":[],"public-properties":[]}]}', null) + } + + def 'Execute cm handle id search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'valid CmHandleQueryParameters input' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def conditionProperties = new ConditionProperties() + conditionProperties.conditionName = 'hasAllModules' + conditionProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryParameters.cmHandleQueryParameters = [conditionProperties] + and: 'query cm handle method return with a data node list' + mockCpsCmHandlerQueryService.queryCmHandles(cmHandleQueryParameters) >> [new DataNode(leaves: [id: 'cm-handle-id-1'])] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result == ['cm-handle-id-1'] as Set } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy index aa6bf1a78..31f179ab2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy @@ -20,68 +20,26 @@ package org.onap.cps.ncmp.api.impl.async -import org.apache.kafka.clients.consumer.ConsumerConfig -import org.apache.kafka.clients.producer.ProducerConfig -import org.apache.kafka.common.serialization.StringSerializer +import com.fasterxml.jackson.databind.ObjectMapper +import org.apache.kafka.clients.consumer.KafkaConsumer import org.mapstruct.factory.Mappers +import org.onap.cps.ncmp.api.utils.MessagingSpec import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent -import org.springframework.kafka.core.DefaultKafkaProducerFactory -import org.springframework.kafka.core.KafkaTemplate -import org.springframework.kafka.support.serializer.JsonSerializer -import org.testcontainers.containers.KafkaContainer -import org.testcontainers.spock.Testcontainers -import org.testcontainers.utility.DockerImageName - -import java.time.Duration -import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper -import org.apache.kafka.clients.consumer.KafkaConsumer -import org.apache.kafka.common.serialization.StringDeserializer -import org.onap.cps.ncmp.utils.TestUtils; -import org.springframework.boot.test.context.SpringBootTest import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.annotation.DirtiesContext -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import spock.lang.Specification +import org.testcontainers.spock.Testcontainers + +import java.time.Duration -@SpringBootTest(classes = [NcmpAsyncRequestResponseEventProducer, NcmpAsyncRequestResponseEventConsumer]) +@SpringBootTest(classes = [NcmpAsyncRequestResponseEventProducer, NcmpAsyncRequestResponseEventConsumer, ObjectMapper, JsonObjectMapper]) @Testcontainers @DirtiesContext -class NcmpAsyncRequestResponseEventProducerIntegrationSpec extends Specification { - - static kafkaTestContainer = new KafkaContainer( - DockerImageName.parse('confluentinc/cp-kafka:6.2.1') - ) - - static { - Runtime.getRuntime().addShutdownHook(new Thread(kafkaTestContainer::stop)) - } - - def setupSpec() { - kafkaTestContainer.start() - } - - def producerConfigProperties = [ - (ProducerConfig.BOOTSTRAP_SERVERS_CONFIG) : kafkaTestContainer.getBootstrapServers().split(',')[0], - (ProducerConfig.RETRIES_CONFIG) : 0, - (ProducerConfig.BATCH_SIZE_CONFIG) : 16384, - (ProducerConfig.LINGER_MS_CONFIG) : 1, - (ProducerConfig.BUFFER_MEMORY_CONFIG) : 33554432, - (ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG) : StringSerializer, - (ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG) : JsonSerializer - ] - - def consumerConfigProperties = [ - (ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG) : kafkaTestContainer.getBootstrapServers().split(',')[0], - (ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG) : StringDeserializer, - (ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG): StringDeserializer, - (ConsumerConfig.AUTO_OFFSET_RESET_CONFIG) : 'earliest', - (ConsumerConfig.GROUP_ID_CONFIG) : 'test' - ] - - def kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory(producerConfigProperties)) +class NcmpAsyncRequestResponseEventProducerIntegrationSpec extends MessagingSpec { @SpringBean NcmpAsyncRequestResponseEventProducer cpsAsyncRequestResponseEventProducerService = @@ -96,9 +54,10 @@ class NcmpAsyncRequestResponseEventProducerIntegrationSpec extends Specification new NcmpAsyncRequestResponseEventConsumer(cpsAsyncRequestResponseEventProducerService, ncmpAsyncRequestResponseEventMapper) - def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + @Autowired + JsonObjectMapper jsonObjectMapper - def kafkaConsumer = new KafkaConsumer<>(getConsumerConfigProperties()) + def kafkaConsumer = new KafkaConsumer<>(consumerConfigProperties('test')) def 'Consume and forward valid message'() { given: 'consumer has a subscription' @@ -118,9 +77,4 @@ class NcmpAsyncRequestResponseEventProducerIntegrationSpec extends Specification NcmpAsyncRequestResponseEvent).getForwardedEvent().getEventId()) } - @DynamicPropertySource - static void registerKafkaProperties(DynamicPropertyRegistry dynamicPropertyRegistry) { - dynamicPropertyRegistry.add('spring.kafka.bootstrap-servers', kafkaTestContainer::getBootstrapServers) - } - } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsPublisherSpec.groovy new file mode 100644 index 000000000..774a46558 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsPublisherSpec.groovy @@ -0,0 +1,84 @@ +/* + * ============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.impl.event + +import com.fasterxml.jackson.databind.ObjectMapper +import org.apache.kafka.clients.consumer.KafkaConsumer +import org.onap.cps.ncmp.api.utils.MessagingSpec +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.onap.ncmp.cmhandle.lcm.event.Event +import org.onap.ncmp.cmhandle.lcm.event.NcmpEvent +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers + +import java.time.Duration + +@SpringBootTest(classes = [NcmpEventsPublisher, ObjectMapper, JsonObjectMapper]) +@Testcontainers +@DirtiesContext +class NcmpEventsPublisherSpec extends MessagingSpec { + + def kafkaConsumer = new KafkaConsumer<>(consumerConfigProperties('ncmp-group')) + + def testTopic = 'ncmp-events-test' + + @SpringBean + NcmpEventsPublisher ncmpEventsPublisher = new NcmpEventsPublisher(kafkaTemplate) + + @Autowired + JsonObjectMapper jsonObjectMapper + + + def 'Produce and Consume Ncmp Event'() { + given: 'event key and event data' + def eventKey = 'ncmp' + def eventData = new NcmpEvent(eventId: 'test-uuid', + eventCorrelationId: 'cmhandle-as-correlationid', + eventSchema: URI.create('org.onap.ncmp.cmhandle.lcm.event:v1'), + eventSource: URI.create('org.onap.ncmp'), + eventTime: '2022-12-31T20:30:40.000+0000', + eventType: 'org.onap.ncmp.cmhandle.lcm.event', + event: new Event(cmHandleId: 'cmhandle-test', cmhandleState: 'READY', operation: 'CREATE', cmhandleProperties: [['publicProperty1': 'value1'], ['publicProperty2': 'value2']])) + and: 'we have an expected NcmpEvent' + def expectedJsonString = TestUtils.getResourceFileContent('expectedNcmpEvent.json') + def expectedNcmpEvent = jsonObjectMapper.convertJsonString(expectedJsonString, NcmpEvent.class) + and: 'consumer has a subscription' + kafkaConsumer.subscribe([testTopic] as List) + when: 'an event is published' + ncmpEventsPublisher.publishEvent(testTopic, eventKey, eventData) + and: 'topic is polled' + def records = kafkaConsumer.poll(Duration.ofMillis(1500)) + then: 'no exception is thrown' + noExceptionThrown() + and: 'poll returns one record' + assert records.size() == 1 + and: 'record key matches the expected event key' + def record = records.iterator().next() + assert eventKey == record.key + and: 'record matches the expected event' + assert expectedNcmpEvent == jsonObjectMapper.convertJsonString(record.value, NcmpEvent.class) + + } +} 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/InventoryPersistenceSpec.groovy index b638eecd4..a2ebcb5d8 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/InventoryPersistenceSpec.groovy @@ -88,7 +88,7 @@ class InventoryPersistenceSpec extends Specification { where: 'the following parameters are used' scenario | childDataNodes || expectedDmiProperties || expectedPublicProperties || expectedCompositeState 'no properties' | [] || [] || [] || null - 'DMI and public properties' | childDataNodesForCmHandleWithAllProperties || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")] || null + 'DMI and public properties' | childDataNodesForCmHandleWithAllProperties || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")] || null 'just DMI properties' | childDataNodesForCmHandleWithDMIProperties || [new YangModelCmHandle.Property("name1", "value1")] || [] || null 'just public properties' | childDataNodesForCmHandleWithPublicProperties || [] || [new YangModelCmHandle.Property("name2", "value2")] || null 'with state details' | childDataNodesForCmHandleWithState || [] || [] || CmHandleState.ADVISED @@ -105,7 +105,7 @@ class InventoryPersistenceSpec extends Specification { def "Handling missing service names as null CPS-1043."() { given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes' - def dataNode = new DataNode(childDataNodes:[], leaves: [:]) + def dataNode = new DataNode(childDataNodes:[], leaves: ["cm-handle-state":"ADVISED"]) mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode when: 'retrieving the yang modelled cm handle' def result = objectUnderTest.getYangModelCmHandle(cmHandleId) 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 37fdbeeb2..8050a571a 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 @@ -20,9 +20,11 @@ package org.onap.cps.ncmp.api.inventory.sync +import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.model.ModuleReference import spock.lang.Specification @@ -32,8 +34,9 @@ class ModuleSyncServiceSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockDmiModelOperations = Mock(DmiModelOperations) + def mockCpsAdminService = Mock(CpsAdminService) - def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService) + def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, mockCpsAdminService) def expectedDataspaceName = 'NFP-Operational' @@ -42,7 +45,7 @@ class ModuleSyncServiceSpec extends Specification { def ncmpServiceCmHandle = new NcmpServiceCmHandle() def dmiServiceName = 'some service name' ncmpServiceCmHandle.cmHandleId = 'cmHandleId-1' - def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '' , '', ncmpServiceCmHandle) + def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '' , '', CmHandleState.ADVISED, ncmpServiceCmHandle) and: 'DMI operations returns some module references' def moduleReferences = [ new ModuleReference(moduleName:'module1',revision:'1'), new ModuleReference(moduleName:'module2',revision:'2') ] @@ -50,17 +53,19 @@ class ModuleSyncServiceSpec extends Specification { and: 'CPS-Core returns list of existing module resources' mockCpsModuleService.getYangResourceModuleReferences(expectedDataspaceName) >> toModuleReference(existingModuleResourcesInCps) and: 'DMI-Plugin returns resource(s) for "new" module(s)' - mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('module1', '1')]) >> yangResourceToContentMap + mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('module1', '1')]) >> newModuleNameContentToMap when: 'module sync is triggered' mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> toModuleReference(identifiedNewModuleReferences) - def result = objectUnderTest.syncAndCreateSchemaSet(yangModelCmHandle) - then: 'the resulting schema set name is the same as the cm handle id' - assert result == 'cmHandleId-1' + objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle) + then: 'create schema set from module is invoked with correct parameters' + 1 * mockCpsModuleService.createSchemaSetFromModules('NFP-Operational', 'cmHandleId-1', newModuleNameContentToMap, existingModuleReferencesInCps) + 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 | yangResourceToContentMap - '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']] | [] | [:] + 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')] } def toModuleReference(moduleReferenceAsMap) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy index bcfe47fd5..97bea096a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy @@ -52,7 +52,7 @@ class ModuleSyncSpec extends Specification { then: 'the inventory persistence cm handle returns a composite state for the first cm handle' 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle') >> compositeState1 and: 'module sync service syncs the first cm handle and creates a schema set' - 1 * mockModuleSyncService.syncAndCreateSchemaSet(yangModelCmHandle1) + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle1) and: 'the composite state cm handle state is now READY' assert compositeState1.getCmHandleState() == CmHandleState.READY and: 'the first cm handle state is updated' @@ -60,7 +60,7 @@ class ModuleSyncSpec extends Specification { then: 'the inventory persistence cm handle returns a composite state for the second cm handle' mockInventoryPersistence.getCmHandleState('some-cm-handle-2') >> compositeState2 and: 'module sync service syncs the second cm handle and creates a schema set' - 1 * mockModuleSyncService.syncAndCreateSchemaSet(yangModelCmHandle2) + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle2) and: 'the composite state cm handle state is now READY' assert compositeState2.getCmHandleState() == CmHandleState.READY and: 'the second cm handle state is updated' @@ -78,7 +78,7 @@ class ModuleSyncSpec extends Specification { then: 'the inventory persistence cm handle returns a composite state for the cm handle' 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle') >> compositeState and: 'module sync service attempts to sync the cm handle and throws an exception' - 1 * mockModuleSyncService.syncAndCreateSchemaSet(*_) >> { throw new Exception('some exception') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } and: 'the composite state cm handle state is now LOCKED' assert compositeState.getCmHandleState() == CmHandleState.LOCKED and: 'update lock reason, details and attempts is invoked' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy index 7bbc3d753..cdfcf59ef 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy @@ -21,6 +21,8 @@ package org.onap.cps.ncmp.api.models import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.ncmp.cmhandle.lcm.event.Event import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA @@ -35,7 +37,7 @@ class YangModelCmHandleSpec extends Specification { ncmpServiceCmHandle.dmiProperties = [myDmiProperty:'value1'] ncmpServiceCmHandle.publicProperties = [myPublicProperty:'value2'] when: 'it is converted to a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle('','','', ncmpServiceCmHandle) + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle('','','', CmHandleState.ADVISED, ncmpServiceCmHandle) then: 'the result has the right size' assert objectUnderTest.dmiProperties.size() == 1 and: 'the DMI property in the result has the correct name and value' @@ -48,7 +50,7 @@ class YangModelCmHandleSpec extends Specification { def 'Resolve DMI service name: #scenario and #requiredService service require.'() { given: 'a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')) + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, CmHandleState.ADVISED, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')) expect: assert objectUnderTest.resolveDmiServiceName(requiredService) == expectedService where: diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy index 964826be1..b3ea3b870 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy @@ -20,6 +20,8 @@ package org.onap.cps.ncmp.api.utils +import org.onap.cps.ncmp.api.inventory.CmHandleState + import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle @@ -33,7 +35,7 @@ class DmiServiceUrlBuilderSpec extends Specification { @Shared YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', - 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')) + 'dmiDataServiceName', 'dmiModuleServiceName', CmHandleState.ADVISED , new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')) NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties() diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy new file mode 100644 index 000000000..097834afc --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy @@ -0,0 +1,71 @@ +/* + * ============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.utils + +import org.apache.kafka.common.serialization.StringDeserializer +import org.apache.kafka.common.serialization.StringSerializer +import org.springframework.kafka.core.DefaultKafkaProducerFactory +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.kafka.support.serializer.JsonSerializer +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.KafkaContainer +import org.testcontainers.utility.DockerImageName +import spock.lang.Specification + +class MessagingSpec extends Specification { + + static { + Runtime.getRuntime().addShutdownHook(new Thread(kafkaTestContainer::stop)) + } + + def setupSpec() { + kafkaTestContainer.start() + } + + static kafkaTestContainer = new KafkaContainer(DockerImageName.parse('confluentinc/cp-kafka:6.2.1')) + + def producerConfigProperties() { + return [('bootstrap.servers'): kafkaTestContainer.getBootstrapServers().split(',')[0], + ('retries') : 0, + ('batch-size') : 16384, + ('linger.ms') : 1, + ('buffer.memory') : 33554432, + ('key.serializer') : StringSerializer, + ('value.serializer') : JsonSerializer] + } + + def consumerConfigProperties(consumerGroupId) { + return [('bootstrap.servers') : kafkaTestContainer.getBootstrapServers().split(',')[0], + ('key.deserializer') : StringDeserializer, + ('value.deserializer'): StringDeserializer, + ('auto.offset.reset') : 'earliest', + ('group.id') : consumerGroupId + ] + } + + def kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory(producerConfigProperties())) + + @DynamicPropertySource + static void registerKafkaProperties(DynamicPropertyRegistry dynamicPropertyRegistry) { + dynamicPropertyRegistry.add('spring.kafka.bootstrap-servers', kafkaTestContainer::getBootstrapServers) + } +} diff --git a/cps-ncmp-service/src/test/resources/expectedNcmpEvent.json b/cps-ncmp-service/src/test/resources/expectedNcmpEvent.json new file mode 100644 index 000000000..903bc3aab --- /dev/null +++ b/cps-ncmp-service/src/test/resources/expectedNcmpEvent.json @@ -0,0 +1,21 @@ +{ + "eventId": "test-uuid", + "eventCorrelationId": "cmhandle-as-correlationid", + "eventTime": "2022-12-31T20:30:40.000+0000", + "eventSource": "org.onap.ncmp", + "eventType": "org.onap.ncmp.cmhandle.lcm.event", + "eventSchema": "org.onap.ncmp.cmhandle.lcm.event:v1", + "event": { + "cmHandleId": "cmhandle-test", + "operation": "CREATE", + "cmhandle-state": "READY", + "cmhandle-properties": [ + { + "publicProperty1": "value1" + }, + { + "publicProperty2": "value2" + } + ] + } +} \ No newline at end of file diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java index 2e7bb7e96..20a39f98e 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java @@ -23,11 +23,12 @@ package org.onap.cps.spi.impl; import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import javax.transaction.Transactional; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.onap.cps.spi.CpsAdminPersistenceService; import org.onap.cps.spi.entities.AnchorEntity; import org.onap.cps.spi.entities.DataspaceEntity; @@ -35,26 +36,25 @@ import org.onap.cps.spi.entities.SchemaSetEntity; import org.onap.cps.spi.entities.YangResourceModuleReference; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.DataspaceInUseException; +import org.onap.cps.spi.exceptions.DataspaceNotFoundException; import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.CmHandleQueryParameters; import org.onap.cps.spi.repository.AnchorRepository; import org.onap.cps.spi.repository.DataspaceRepository; -import org.onap.cps.spi.repository.ModuleReferenceRepository; import org.onap.cps.spi.repository.SchemaSetRepository; import org.onap.cps.spi.repository.YangResourceRepository; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; +@Slf4j @Component -@AllArgsConstructor +@RequiredArgsConstructor public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceService { private final DataspaceRepository dataspaceRepository; private final AnchorRepository anchorRepository; private final SchemaSetRepository schemaSetRepository; private final YangResourceRepository yangResourceRepository; - private final ModuleReferenceRepository moduleReferenceRepository; @Override public void createDataspace(final String dataspaceName) { @@ -117,7 +117,13 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic @Override public Collection queryAnchors(final String dataspaceName, final Collection inputModuleNames) { - validateDataspaceAndModuleNames(dataspaceName, inputModuleNames); + try { + validateDataspaceAndModuleNames(dataspaceName, inputModuleNames); + } catch (DataspaceNotFoundException | ModuleNamesNotFoundException e) { + log.info("Module search encountered unknown dataspace or modulename, treating this as nothing found"); + return Collections.emptySet(); + } + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final Collection anchorEntities = anchorRepository .getAnchorsByDataspaceIdAndModuleNames(dataspaceEntity.getId(), inputModuleNames, inputModuleNames.size()); @@ -136,11 +142,6 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic anchorRepository.delete(anchorEntity); } - @Override - public Set queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) { - return moduleReferenceRepository.queryCmHandles(cmHandleQueryParameters); - } - private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) { final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index ded234bb4..117cb43b4 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 @@ -48,6 +48,8 @@ import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.entities.AnchorEntity; import org.onap.cps.spi.entities.DataspaceEntity; 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.ConcurrencyException; import org.onap.cps.spi.exceptions.CpsAdminException; @@ -60,6 +62,8 @@ import org.onap.cps.spi.repository.DataspaceRepository; import org.onap.cps.spi.repository.FragmentRepository; import org.onap.cps.spi.utils.SessionManager; import org.onap.cps.utils.JsonObjectMapper; +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -85,33 +89,33 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override @Transactional public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final DataNode newChildDataNode) { - addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode)); + final DataNode newChildDataNode) { + addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode)); } @Override @Transactional public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection newListElements) { + final Collection newListElements) { addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements); } private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection newChildren) { + final Collection newChildren) { final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); try { for (final DataNode newChildAsDataNode : newChildren) { final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants( - parentFragmentEntity.getDataspace(), - parentFragmentEntity.getAnchor(), - newChildAsDataNode); + parentFragmentEntity.getDataspace(), + parentFragmentEntity.getAnchor(), + newChildAsDataNode); newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId()); fragmentRepository.save(newChildAsFragmentEntity); } } catch (final DataIntegrityViolationException exception) { final List conflictXpaths = newChildren.stream() - .map(DataNode::getXpath) - .collect(Collectors.toList()); + .map(DataNode::getXpath) + .collect(Collectors.toList()); throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception); } } @@ -121,7 +125,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, - dataNode); + dataNode); try { fragmentRepository.save(fragmentEntity); } catch (final DataIntegrityViolationException exception) { @@ -139,13 +143,13 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService * @return a Fragment built from current DataNode */ private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { + final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); final Builder childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { final FragmentEntity childFragment = - convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(), - childDataNode); + convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(), + childDataNode); childFragmentsImmutableSetBuilder.add(childFragment); } parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build()); @@ -153,24 +157,24 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, final DataNode dataNode) { + final AnchorEntity anchorEntity, final DataNode dataNode) { return FragmentEntity.builder() - .dataspace(dataspaceEntity) - .anchor(anchorEntity) - .xpath(dataNode.getXpath()) - .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves())) - .build(); + .dataspace(dataspaceEntity) + .anchor(anchorEntity) + .xpath(dataNode.getXpath()) + .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves())) + .build(); } @Override public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath, - final FetchDescendantsOption fetchDescendantsOption) { + final FetchDescendantsOption fetchDescendantsOption) { final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); return toDataNode(fragmentEntity, fetchDescendantsOption); } private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName, - final String xpath) { + final String xpath) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); if (isRootXpath(xpath)) { @@ -189,7 +193,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public List queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath, - final FetchDescendantsOption fetchDescendantsOption) { + final FetchDescendantsOption fetchDescendantsOption) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); final CpsPathQuery cpsPathQuery; @@ -199,15 +203,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService throw new CpsPathException(e.getMessage()); } List fragmentEntities = - fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); + fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = ancestorXpaths.isEmpty() - ? Collections.emptyList() : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() + : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); } return fragmentEntities.stream() - .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption)) - .collect(Collectors.toUnmodifiableList()); + .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption)) + .collect(Collectors.toUnmodifiableList()); } @Override @@ -222,16 +226,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void lockAnchor(final String sessionId, final String dataspaceName, - final String anchorName, final Long timeoutInMilliseconds) { + final String anchorName, final Long timeoutInMilliseconds) { sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds); } private static Set processAncestorXpath(final List fragmentEntities, - final CpsPathQuery cpsPathQuery) { + final CpsPathQuery cpsPathQuery) { final Set ancestorXpath = new HashSet<>(); final Pattern pattern = - Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) - + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*"); + Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) + + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*"); for (final FragmentEntity fragmentEntity : fragmentEntities) { final Matcher matcher = pattern.matcher(fragmentEntity.getXpath()); if (matcher.matches()) { @@ -242,31 +246,42 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private DataNode toDataNode(final FragmentEntity fragmentEntity, - final FetchDescendantsOption fetchDescendantsOption) { + final FetchDescendantsOption fetchDescendantsOption) { final List childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption); Map leaves = new HashMap<>(); if (fragmentEntity.getAttributes() != null) { leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class); } return new DataNodeBuilder() - .withXpath(fragmentEntity.getXpath()) - .withLeaves(leaves) - .withChildDataNodes(childDataNodes).build(); + .withModuleNamePrefix(getFirstModuleName(fragmentEntity)) + .withXpath(fragmentEntity.getXpath()) + .withLeaves(leaves) + .withChildDataNodes(childDataNodes).build(); + } + + private String getFirstModuleName(final FragmentEntity fragmentEntity) { + final SchemaSetEntity schemaSetEntity = fragmentEntity.getAnchor().getSchemaSet(); + final Map yangResourceNameToContent = + schemaSetEntity.getYangResources().stream().collect( + Collectors.toMap(YangResourceEntity::getName, YangResourceEntity::getContent)); + final SchemaContext schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) + .getSchemaContext(); + return schemaContext.getModules().iterator().next().getName(); } private List getChildDataNodes(final FragmentEntity fragmentEntity, - final FetchDescendantsOption fetchDescendantsOption) { + final FetchDescendantsOption fetchDescendantsOption) { if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) { return fragmentEntity.getChildFragments().stream() - .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption)) - .collect(Collectors.toUnmodifiableList()); + .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption)) + .collect(Collectors.toUnmodifiableList()); } return Collections.emptyList(); } @Override public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath, - final Map leaves) { + final Map leaves) { final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves)); fragmentRepository.save(fragmentEntity); @@ -280,19 +295,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService fragmentRepository.save(fragmentEntity); } 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); + String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.", + dataspaceName, anchorName, dataNode.getXpath()), + staleStateException); } } private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, - final DataNode newDataNode) { + final DataNode newDataNode) { existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves())); - final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments() - .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); + final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream() + .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); final Collection updatedChildFragments = new HashSet<>(); @@ -300,7 +315,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final FragmentEntity childFragment; if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) { childFragment = convertToFragmentWithAllDescendants( - existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild); + existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild); } else { childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath()); replaceDataNodeTree(childFragment, newDataNodeChild); @@ -318,14 +333,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); final String listElementXpathPrefix = getListElementXpathPrefix(newListElements); final Map existingListElementFragmentEntitiesByXPath = - extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix); + extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix); deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath); final Set updatedChildFragmentEntities = new HashSet<>(); for (final DataNode newListElement : newListElements) { final FragmentEntity existingListElementEntity = - existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath()); + existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath()); final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement, - existingListElementEntity); + existingListElementEntity); updatedChildFragmentEntities.add(entityToBeAdded); } @@ -338,8 +353,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService public void deleteDataNodes(final String dataspaceName, final String anchorName) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName) - .ifPresent( - anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity))); + .ifPresent( + anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity))); } @Override @@ -356,7 +371,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath, - final boolean onlySupportListNodeDeletion) { + final boolean onlySupportListNodeDeletion) { final String parentNodeXpath; FragmentEntity parentFragmentEntity = null; boolean targetDeleted = false; @@ -372,7 +387,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/')); final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE - .matcher(lastXpathElement).find(); + .matcher(lastXpathElement).find(); if (isListElement) { targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath); } else { @@ -385,9 +400,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } if (!targetDeleted) { final String additionalInformation = onlySupportListNodeDeletion - ? "The target is probably not a List." : ""; + ? "The target is probably not a List." : ""; throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(), - parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation); + parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation); } } @@ -398,7 +413,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return true; } if (parentFragmentEntity.getChildFragments() - .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) { + .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) { fragmentRepository.save(parentFragmentEntity); return true; } @@ -409,7 +424,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final String normalizedListXpath = CpsPathUtil.getNormalizedXpath(listXpath); final String deleteTargetXpathPrefix = normalizedListXpath + "["; if (parentFragmentEntity.getChildFragments() - .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) { + .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) { fragmentRepository.save(parentFragmentEntity); return true; } @@ -417,26 +432,26 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private static void deleteListElements( - final Collection fragmentEntities, - final Map existingListElementFragmentEntitiesByXPath) { + final Collection fragmentEntities, + final Map existingListElementFragmentEntitiesByXPath) { fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values()); } private static String getListElementXpathPrefix(final Collection newListElements) { if (newListElements.isEmpty()) { throw new CpsAdminException("Invalid list replacement", - "Cannot replace list elements with empty collection"); + "Cannot replace list elements with empty collection"); } final String firstChildNodeXpath = newListElements.iterator().next().getXpath(); return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1); } private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity, - final DataNode newListElement, - final FragmentEntity existingListElementEntity) { + final DataNode newListElement, + final FragmentEntity existingListElementEntity) { if (existingListElementEntity == null) { return convertToFragmentWithAllDescendants( - parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement); + parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement); } if (newListElement.getChildDataNodes().isEmpty()) { copyAttributesFromNewListElement(existingListElementEntity, newListElement); @@ -457,7 +472,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity, - final DataNode newListElement) { + final DataNode newListElement) { final FragmentEntity replacementFragmentEntity = FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString( newListElement.getLeaves())).build(); @@ -465,10 +480,10 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private static Map extractListElementFragmentEntitiesByXPath( - final Set childEntities, final String listElementXpathPrefix) { + final Set childEntities, final String listElementXpathPrefix) { return childEntities.stream() - .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix)) - .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity)); + .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix)) + .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity)); } private static boolean isRootXpath(final String xpath) { 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 3719256fc..cbeb1b76f 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 @@ -66,6 +66,7 @@ import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyIn import org.springframework.dao.DataIntegrityViolationException; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; +import org.springframework.retry.support.RetrySynchronizationManager; import org.springframework.stereotype.Component; @Slf4j @@ -219,9 +220,12 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ convertToDuplicatedYangResourceException( dataIntegrityViolationException, newYangResourceEntities); convertedException.ifPresent( - e -> log.warn( - "Cannot persist duplicated yang resource. " - + "System will attempt this method up to 5 times.", e)); + e -> { + int retryCount = RetrySynchronizationManager.getContext() == null ? 0 + : RetrySynchronizationManager.getContext().getRetryCount(); + log.warn("Cannot persist duplicated yang resource. System will attempt this method " + + "up to 5 times. Current retry count : {}", ++retryCount, e); + }); throw convertedException.isPresent() ? convertedException.get() : dataIntegrityViolationException; } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java index 4bc9dd960..5e4de7fec 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java @@ -21,8 +21,6 @@ package org.onap.cps.spi.repository; import java.util.Collection; -import java.util.Set; -import org.onap.cps.spi.model.CmHandleQueryParameters; import org.onap.cps.spi.model.ModuleReference; /** @@ -32,13 +30,4 @@ public interface ModuleReferenceQuery { Collection identifyNewModuleReferences( final Collection moduleReferencesToCheck); - - /** - * Query and return cm handles that match the given query parameters. - * - * @param cmHandleQueryParameters the cm handle query parameters - * @return collection of cm handle ids - */ - Set queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters); - } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java index f85dea3a7..681bbcdde 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java @@ -24,19 +24,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.spi.CpsDataPersistenceService; -import org.onap.cps.spi.FetchDescendantsOption; -import org.onap.cps.spi.model.CmHandleQueryParameters; -import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.ModuleReference; import org.springframework.transaction.annotation.Transactional; @@ -48,19 +41,17 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery { @PersistenceContext private EntityManager entityManager; - private final CpsDataPersistenceService cpsDataPersistenceService; - @Override @SneakyThrows public Collection identifyNewModuleReferences( - final Collection moduleReferencesToCheck) { + final Collection moduleReferencesToCheck) { if (moduleReferencesToCheck == null || moduleReferencesToCheck.isEmpty()) { return Collections.emptyList(); } final String tempTableName = "moduleReferencesToCheckTemp" - + UUID.randomUUID().toString().replace("-", ""); + + UUID.randomUUID().toString().replace("-", ""); createTemporaryTable(tempTableName); insertDataIntoTable(tempTableName, moduleReferencesToCheck); @@ -68,56 +59,6 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery { return identifyNewModuleReferencesForCmHandle(tempTableName); } - /** - * Query and return cm handles that match the given query parameters. - * - * @param cmHandleQueryParameters the cm handle query parameters - * @return collection of cm handle ids - */ - @Override - public Set queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) { - - if (cmHandleQueryParameters.getPublicProperties().entrySet().isEmpty()) { - return getAllCmHandles(); - } - - final Collection amalgamatedQueryResult = new ArrayList<>(); - int queryConditionCounter = 0; - for (final Map.Entry entry : cmHandleQueryParameters.getPublicProperties().entrySet()) { - final StringBuilder cmHandlePath = new StringBuilder(); - cmHandlePath.append("//public-properties[@name='").append(entry.getKey()).append("' "); - cmHandlePath.append("and @value='").append(entry.getValue()).append("']"); - cmHandlePath.append("/ancestor::cm-handles"); - - final Collection singleConditionQueryResult = - cpsDataPersistenceService.queryDataNodes("NCMP-Admin", - "ncmp-dmi-registry", String.valueOf(cmHandlePath), FetchDescendantsOption.OMIT_DESCENDANTS); - if (++queryConditionCounter == 1) { - amalgamatedQueryResult.addAll(singleConditionQueryResult); - } else { - amalgamatedQueryResult.retainAll(singleConditionQueryResult); - } - - if (amalgamatedQueryResult.isEmpty()) { - break; - } - } - - return extractCmHandleIds(amalgamatedQueryResult); - } - - private Set getAllCmHandles() { - final Collection cmHandles = cpsDataPersistenceService.queryDataNodes("NCMP-Admin", - "ncmp-dmi-registry", "//public-properties/ancestor::cm-handles", - FetchDescendantsOption.OMIT_DESCENDANTS); - return extractCmHandleIds(cmHandles); - } - - private Set extractCmHandleIds(final Collection cmHandles) { - return cmHandles.stream().map(cmHandle -> cmHandle.getLeaves().get("id").toString()) - .collect(Collectors.toSet()); - } - private void createTemporaryTable(final String tempTableName) { final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE " + tempTableName + "("); sqlStringBuilder.append(" id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,"); @@ -149,11 +90,11 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery { private Collection identifyNewModuleReferencesForCmHandle(final String tempTableName) { final String sql = String.format( - "SELECT %1$s.module_name, %1$s.revision" - + " FROM %1$s LEFT JOIN yang_resource" - + " ON yang_resource.module_name=%1$s.module_name" - + " AND yang_resource.revision=%1$s.revision" - + " WHERE yang_resource.module_name IS NULL;", tempTableName); + "SELECT %1$s.module_name, %1$s.revision" + + " FROM %1$s LEFT JOIN yang_resource" + + " ON yang_resource.module_name=%1$s.module_name" + + " AND yang_resource.revision=%1$s.revision" + + " WHERE yang_resource.module_name IS NULL;", tempTableName); @SuppressWarnings("unchecked") final List resultsAsObjects = entityManager.createNativeQuery(sql).getResultList(); diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy index 2de087fc2..e03735003 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy @@ -31,7 +31,6 @@ import org.onap.cps.spi.exceptions.DataspaceNotFoundException import org.onap.cps.spi.exceptions.SchemaSetNotFoundException import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException import org.onap.cps.spi.model.Anchor -import org.onap.cps.spi.model.CmHandleQueryParameters import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper @@ -45,7 +44,6 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { ObjectMapper objectMapper static final String SET_DATA = '/data/anchor.sql' - static final String SET_FRAGMENT_DATA = '/data/fragment.sql' static final String SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES = '/data/anchors-schemaset-modules.sql' static final String DATASPACE_WITH_NO_DATA = 'DATASPACE-002-NO-DATA' static final Integer DELETED_ANCHOR_ID = 3002 @@ -176,28 +174,17 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES]) def 'Query anchors that have #scenario.'() { when: 'all anchor are retrieved for the given dataspace name and module names' - def anchors = objectUnderTest.queryAnchors('dataspace-1', inputModuleNames) + def anchors = objectUnderTest.queryAnchors(inputDataspaceName, inputModuleNames) then: 'the expected anchors are returned' anchors.size() == expectedAnchors.size() anchors.containsAll(expectedAnchors) where: 'the following data is used' - scenario | inputModuleNames || expectedAnchors - 'one module' | ['module-name-1'] || [buildAnchor('anchor-2', 'dataspace-1', 'schema-set-2'), buildAnchor('anchor-1', 'dataspace-1', 'schema-set-1')] - 'two modules' | ['module-name-1', 'module-name-2'] || [buildAnchor('anchor-2', 'dataspace-1', 'schema-set-2'), buildAnchor('anchor-1', 'dataspace-1', 'schema-set-1')] - 'no anchors for all three modules' | ['module-name-1', 'module-name-2', 'module-name-3'] || [] - } - - @Sql([CLEAR_DATA, SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES]) - def 'Query all anchors for an #scenario.'() { - when: 'attempt to query anchors' - objectUnderTest.queryAnchors(dataspaceName, moduleNames) - then: 'the correct exception is thrown with the relevant details' - def thrownException = thrown(expectedException) - thrownException.details.contains(expectedMessageDetails) - where: 'the following data is used' - scenario | dataspaceName | moduleNames || expectedException | expectedMessageDetails | messageDoesNotContain - 'unknown dataspace' | 'db-does-not-exist' | ['does-not-matter'] || DataspaceNotFoundException | 'db-does-not-exist' | 'does-not-matter' - 'unknown module and known module' | 'dataspace-1' | ['module-name-1', 'module-does-not-exist'] || ModuleNamesNotFoundException | 'module-does-not-exist' | 'module-name-1' + scenario | inputDataspaceName | inputModuleNames || expectedAnchors + 'one module' | 'dataspace-1' | ['module-name-1'] || [buildAnchor('anchor-2', 'dataspace-1', 'schema-set-2'), buildAnchor('anchor-1', 'dataspace-1', 'schema-set-1')] + 'two modules' | 'dataspace-1' | ['module-name-1', 'module-name-2'] || [buildAnchor('anchor-2', 'dataspace-1', 'schema-set-2'), buildAnchor('anchor-1', 'dataspace-1', 'schema-set-1')] + 'no anchors for all three modules' | 'dataspace-1' | ['module-name-1', 'module-name-2', 'module-name-3'] || [] + 'unknown dataspace' | 'db-does-not-exist' | ['does-not-matter'] || [] + 'unknown module and known module' | 'dataspace-1' | ['module-name-1', 'module-does-not-exist'] || [] } def buildAnchor(def anchorName, def dataspaceName, def SchemaSetName) { @@ -225,21 +212,4 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { 'dataspace contains an anchor' | 'DATASPACE-001' || DataspaceInUseException | 'contains 2 anchor(s)' 'dataspace contains schemasets' | 'DATASPACE-003' || DataspaceInUseException | 'contains 1 schemaset(s)' } - - @Sql([CLEAR_DATA, SET_FRAGMENT_DATA]) - def 'Retrieve cm handle ids when #scenario.'() { - when: 'the service is invoked' - def cmHandleQueryParameters = new CmHandleQueryParameters() - cmHandleQueryParameters.setPublicProperties(publicProperties) - def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'the correct expected cm handles are returned' - returnedCmHandles == expectedCmHandleIds - where: 'the following data is used' - scenario | publicProperties || expectedCmHandleIds - 'single matching property' | ['Contact' : 'newemailforstore@bookstore.com'] || ['PNFDemo2', 'PNFDemo', 'PNFDemo4'] as Set - 'public property dont match' | ['wont_match' : 'wont_match'] || [] as Set - '2 properties, only one match (and)' | ['Contact' : 'newemailforstore@bookstore.com', 'Contact2': 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] as Set - '2 properties, no match (and)' | ['Contact' : 'newemailforstore@bookstore.com', 'Contact2': ''] || [] as Set - 'No public properties - return all cm handles' | [ : ] || ['PNFDemo3', 'PNFDemo', 'PNFDemo2', 'PNFDemo4'] as Set - } } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index a96b6aff9..bb80199d0 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 @@ -22,7 +22,11 @@ package org.onap.cps.spi.impl import com.fasterxml.jackson.databind.ObjectMapper import org.hibernate.StaleStateException import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.entities.AnchorEntity +import org.onap.cps.spi.entities.DataspaceEntity 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.ConcurrencyException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNodeBuilder @@ -31,6 +35,10 @@ import org.onap.cps.spi.repository.DataspaceRepository import org.onap.cps.spi.repository.FragmentRepository import org.onap.cps.spi.utils.SessionManager import org.onap.cps.utils.JsonObjectMapper +import org.onap.cps.yang.YangTextSchemaSourceSet +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.opendaylight.yangtools.yang.model.api.SchemaContext +import spock.lang.Shared import spock.lang.Specification class CpsDataPersistenceServiceSpec extends Specification { @@ -44,6 +52,25 @@ class CpsDataPersistenceServiceSpec extends Specification { def objectUnderTest = new CpsDataPersistenceServiceImpl( mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager) + @Shared + def NEW_RESOURCE_CONTENT = 'module stores {\n' + + ' yang-version 1.1;\n' + + ' namespace "org:onap:ccsdk:sample";\n' + + '\n' + + ' prefix book-store;\n' + + '\n' + + ' revision "2020-09-15" {\n' + + ' description\n' + + ' "Sample Model";\n' + + ' }' + + '}' + + @Shared + def yangResourceSet = [new YangResourceEntity(moduleName: 'moduleName', content: NEW_RESOURCE_CONTENT, + name: 'sampleYangResource' + )] as Set + + def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() { def parentXpath = '/parent-01' @@ -51,67 +78,68 @@ class CpsDataPersistenceServiceSpec extends Specification { def myAnchorName = 'my-anchor' given: 'data node object' - def submittedDataNode = new DataNodeBuilder() - .withXpath(parentXpath) - .withLeaves(['leaf-name': 'leaf-value']) - .build() + def submittedDataNode = new DataNodeBuilder() + .withXpath(parentXpath) + .withLeaves(['leaf-name': 'leaf-value']) + .build() and: 'fragment to be updated' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - def fragmentEntity = new FragmentEntity() - fragmentEntity.setXpath(parentXpath) - fragmentEntity.setChildFragments(Collections.emptySet()) - return fragmentEntity - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + def fragmentEntity = new FragmentEntity() + fragmentEntity.setXpath(parentXpath) + fragmentEntity.setChildFragments(Collections.emptySet()) + return fragmentEntity + } and: 'data node is concurrently updated by another transaction' - mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } when: 'attempt to update data node' - objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode) + objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode) then: 'concurrency exception is thrown' - def concurrencyException = thrown(ConcurrencyException) - assert concurrencyException.getDetails().contains(myDataspaceName) - assert concurrencyException.getDetails().contains(myAnchorName) - assert concurrencyException.getDetails().contains(parentXpath) + def concurrencyException = thrown(ConcurrencyException) + assert concurrencyException.getDetails().contains(myDataspaceName) + assert concurrencyException.getDetails().contains(myAnchorName) + assert concurrencyException.getDetails().contains(parentXpath) } def 'Retrieving a data node with a property JSON value of #scenario'() { given: 'a fragment with a property JSON value of #scenario' mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { new FragmentEntity(childFragments: Collections.emptySet(), - attributes: "{\"some attribute\": ${dataString}}") + attributes: "{\"some attribute\": ${dataString}}", + anchor: new AnchorEntity(schemaSet: new SchemaSetEntity(yangResources: yangResourceSet ))) } when: 'getting the data node represented by this fragment' - def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', - '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', + '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: 'the leaf is of the correct value and data type' - def attributeValue = dataNode.leaves.get('some attribute') - assert attributeValue == expectedValue - assert attributeValue.class == expectedDataClass + def attributeValue = dataNode.leaves.get('some attribute') + assert attributeValue == expectedValue + assert attributeValue.class == expectedDataClass where: 'the following Data Type is passed' - scenario | dataString || expectedValue | expectedDataClass - 'just numbers' | '15174' || 15174 | Integer - 'number with dot' | '15174.32' || 15174.32 | Double - 'number with 0 value after dot' | '15174.0' || 15174.0 | Double - 'number with 0 value before dot' | '0.32' || 0.32 | Double - 'number higher than max int' | '2147483648' || 2147483648 | Long - 'just text' | '"Test"' || 'Test' | String - 'number with exponent' | '1.2345e5' || 1.2345e5 | Double - 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double - 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String - 'number as String' | '"12345"' || '12345' | String + scenario | dataString || expectedValue | expectedDataClass + 'just numbers' | '15174' || 15174 | Integer + 'number with dot' | '15174.32' || 15174.32 | Double + 'number with 0 value after dot' | '15174.0' || 15174.0 | Double + 'number with 0 value before dot' | '0.32' || 0.32 | Double + 'number higher than max int' | '2147483648' || 2147483648 | Long + 'just text' | '"Test"' || 'Test' | String + 'number with exponent' | '1.2345e5' || 1.2345e5 | Double + 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double + 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String + 'number as String' | '"12345"' || '12345' | String } def 'Retrieving a data node with invalid JSON'() { given: 'a fragment with invalid JSON' - mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { - new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json') - } + mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> { + new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json') + } when: 'getting the data node represented by this fragment' - def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', - '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor', + '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) then: 'a data validation exception is thrown' - thrown(DataValidationException) + thrown(DataValidationException) } def 'start session'() { 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 d1a62209e..c406203c8 100644 --- a/cps-ri/src/test/resources/data/cps-path-query.sql +++ b/cps-ri/src/test/resources/data/cps-path-query.sql @@ -25,6 +25,28 @@ INSERT INTO DATASPACE (ID, NAME) VALUES INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES (2001, 'SCHEMA-SET-001', 1001); +INSERT INTO YANG_RESOURCE (ID, NAME, CONTENT, CHECKSUM, MODULE_NAME, REVISION) VALUES + (4001, 'TEST','', 'SAMPLECHECKSUM','TESTMODULENAME', 'SAMPLEREVISION'); + +UPDATE YANG_RESOURCE SET +content = 'module stores { + yang-version 1.1; + namespace "org:onap:ccsdk:sample"; + + prefix book-store; + + revision "2020-09-15" { + description + "Sample Model"; + } + } +' +where ID = 4001; + + +INSERT INTO SCHEMA_SET_YANG_RESOURCES (SCHEMA_SET_ID, YANG_RESOURCE_ID) VALUES + (2001, 4001); + INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (1003, 'ANCHOR-004', 1001, 2001); diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index 410654106..fd05900e2 100755 --- a/cps-ri/src/test/resources/data/fragment.sql +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -27,6 +27,27 @@ INSERT INTO DATASPACE (ID, NAME) VALUES INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES (2001, 'SCHEMA-SET-001', 1001); +INSERT INTO YANG_RESOURCE (ID, NAME, CONTENT, CHECKSUM, MODULE_NAME, REVISION) VALUES + (4001, 'TEST','', 'SAMPLECHECKSUM','TESTMODULENAME', 'SAMPLEREVISION'); + +UPDATE YANG_RESOURCE SET +content = 'module stores { + yang-version 1.1; + namespace "org:onap:ccsdk:sample"; + + prefix book-store; + + revision "2020-09-15" { + description + "Sample Model"; + } + } +' +where ID = 4001; + +INSERT INTO SCHEMA_SET_YANG_RESOURCES (SCHEMA_SET_ID, YANG_RESOURCE_ID) VALUES + (2001, 4001); + INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (3001, 'ANCHOR-001', 1001, 2001), (3003, 'ANCHOR-003', 1001, 2001), diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java index 2106f1584..ab3373248 100755 --- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java @@ -23,11 +23,9 @@ package org.onap.cps.api; import java.util.Collection; -import java.util.Set; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.CmHandleQueryParameters; /** * CPS Admin Service. @@ -102,12 +100,4 @@ public interface CpsAdminService { * given module names */ Collection queryAnchorNames(String dataspaceName, Collection moduleNames); - - /** - * Query and return cm handles that match the given query parameters. - * - * @param cmHandleQueryParameters the cm handle query parameters - * @return collection of cm handle ids - */ - Set queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters); } diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java index 93c96ec65..cde25a9f9 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java @@ -210,5 +210,4 @@ public interface CpsDataService { * @param timeoutInMilliseconds lock attempt timeout in milliseconds */ void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds); - } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java index 762754f9a..a67dfe503 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java @@ -24,20 +24,18 @@ package org.onap.cps.api.impl; import java.time.OffsetDateTime; import java.util.Collection; -import java.util.Set; import java.util.stream.Collectors; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsDataService; import org.onap.cps.spi.CpsAdminPersistenceService; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.CmHandleQueryParameters; import org.onap.cps.utils.CpsValidator; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component("CpsAdminServiceImpl") -@AllArgsConstructor(onConstructor = @__(@Lazy)) +@RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class CpsAdminServiceImpl implements CpsAdminService { private final CpsAdminPersistenceService cpsAdminPersistenceService; @@ -93,9 +91,4 @@ public class CpsAdminServiceImpl implements CpsAdminService { final Collection anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames); return anchors.stream().map(Anchor::getName).collect(Collectors.toList()); } - - @Override - public Set queryCmHandles(final CmHandleQueryParameters cmHandleQueryParameters) { - return cpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters); - } } diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java index 25167e844..db2d2b2d4 100755 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java @@ -23,10 +23,8 @@ package org.onap.cps.spi; import java.util.Collection; -import java.util.Set; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.CmHandleQueryParameters; /* Service for handling CPS admin data. @@ -76,7 +74,7 @@ public interface CpsAdminPersistenceService { /** * Query anchor names for the given module names in the provided dataspace. - * + * If dataspace or one of the given module names does not exists, return with an empty collection. * * @param dataspaceName dataspace name * @param moduleNames a collection of module names @@ -101,12 +99,4 @@ public interface CpsAdminPersistenceService { * @param anchorName anchor name */ void deleteAnchor(String dataspaceName, String anchorName); - - /** - * Query and return cm handles that match the given query parameters. - * - * @param cmHandleQueryParameters the cm handle query parameters - * @return collection of cm handle ids - */ - Set queryCmHandles(CmHandleQueryParameters cmHandleQueryParameters); } 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 fd660e675..b27a2976d 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 @@ -173,5 +173,4 @@ public interface CpsDataPersistenceService { * @param timeoutInMilliseconds lock attempt timeout in milliseconds */ void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds); - } diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java b/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java index ff4e62763..cf364db3a 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/CmHandleQueryParameters.java @@ -24,18 +24,18 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Collections; -import java.util.Map; +import java.util.List; import javax.validation.Valid; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @Setter @Getter -@JsonInclude(Include.NON_NULL) +@EqualsAndHashCode +@JsonInclude(Include.NON_EMPTY) public class CmHandleQueryParameters { - - @JsonProperty("publicCmHandleProperties") + @JsonProperty("cmHandleQueryParameters") @Valid - private Map publicProperties = Collections.emptyMap(); - + private List cmHandleQueryParameters = Collections.emptyList(); } diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/ConditionProperties.java b/cps-service/src/main/java/org/onap/cps/spi/model/ConditionProperties.java new file mode 100644 index 000000000..4eee7db13 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/spi/model/ConditionProperties.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.spi.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.validation.Valid; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class ConditionProperties { + @JsonProperty("conditionName") + private String conditionName = ""; + + @JsonProperty("conditionParameters") + @Valid + private List> conditionParameters = Collections.emptyList(); +} diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java index 43aa06b81..d80306bae 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java @@ -42,6 +42,7 @@ public class DataNode { private String anchorName; private ModuleReference moduleReference; private String xpath; + private String moduleNamePrefix; private Map leaves = Collections.emptyMap(); private Collection xpathsChildren; private Collection childDataNodes = Collections.emptySet(); diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java index 4a9957deb..f2bde03a0 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021 Bell Canada. All rights reserved. * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications 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. @@ -45,6 +46,7 @@ public class DataNodeBuilder { private NormalizedNode normalizedNodeTree; private String xpath; + private String moduleNamePrefix; private String parentNodeXpath = ""; private Map leaves = Collections.emptyMap(); private Collection childDataNodes = Collections.emptySet(); @@ -83,6 +85,17 @@ public class DataNodeBuilder { return this; } + /** + * To use module name for prefix for creating {@link DataNode}. + * + * @param moduleNamePrefix module name as prefix + * @return DataNodeBuilder + */ + public DataNodeBuilder withModuleNamePrefix(final String moduleNamePrefix) { + this.moduleNamePrefix = moduleNamePrefix; + return this; + } + /** * To use attributes for creating {@link DataNode}. * @@ -136,6 +149,7 @@ public class DataNodeBuilder { private DataNode buildFromAttributes() { final var dataNode = new DataNode(); dataNode.setXpath(xpath); + dataNode.setModuleNamePrefix(moduleNamePrefix); dataNode.setLeaves(leaves); dataNode.setChildDataNodes(childDataNodes); return dataNode; diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeIdentifier.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeIdentifier.java new file mode 100644 index 000000000..2bd2b774d --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeIdentifier.java @@ -0,0 +1,37 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.spi.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +public class DataNodeIdentifier { + private String dataspace; + private String schemaSetName; + private String anchorName; + private String xpath; +} diff --git a/cps-service/src/main/java/org/onap/cps/utils/CmHandleQueryRestParametersValidator.java b/cps-service/src/main/java/org/onap/cps/utils/CmHandleQueryRestParametersValidator.java new file mode 100644 index 000000000..c510a73af --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/CmHandleQueryRestParametersValidator.java @@ -0,0 +1,93 @@ +/* + * ============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.utils; + +import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.onap.cps.spi.exceptions.DataValidationException; +import org.onap.cps.spi.model.CmHandleQueryParameters; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CmHandleQueryRestParametersValidator { + + private static final List VALID_PROPERTY_NAMES = Arrays.asList("hasAllProperties", "hasAllModules"); + + /** + * Validate cm handle query parameters. + * @param cmHandleQueryParameters name of data to be validated + */ + public static void validateCmHandleQueryParameters(final CmHandleQueryParameters cmHandleQueryParameters) { + cmHandleQueryParameters.getCmHandleQueryParameters().forEach( + conditionApiProperty -> { + if (Strings.isNullOrEmpty(conditionApiProperty.getConditionName())) { + throwDataValidationException("Missing 'conditionName' - please supply a valid name."); + } + if (!VALID_PROPERTY_NAMES.contains(conditionApiProperty.getConditionName())) { + throwDataValidationException( + String.format("Wrong 'conditionName': %s - please supply a valid name.", + conditionApiProperty.getConditionName())); + } + if (conditionApiProperty.getConditionParameters().isEmpty()) { + throwDataValidationException( + "Empty 'conditionsParameters' - please supply a valid condition parameter."); + } + conditionApiProperty.getConditionParameters().forEach( + conditionParameter -> { + if (conditionParameter.isEmpty()) { + throwDataValidationException( + "Empty 'conditionsParameter' - please supply a valid condition parameter."); + } + if (conditionParameter.size() > 1) { + throwDataValidationException("Too many name in one 'conditionsParameter' -" + + " please supply one name in one condition parameter."); + } + conditionParameter.forEach((key, value) -> { + if (Strings.isNullOrEmpty(key)) { + throwDataValidationException( + "Missing 'conditionsParameterName' - please supply a valid name."); + } + }); + } + ); + } + ); + } + + /** + * Validate module name condition properties. + * @param conditionProperty name of data to be validated + */ + public static void validateModuleNameConditionProperties(final Map conditionProperty) { + if (conditionProperty.containsKey("moduleName") && !conditionProperty.get("moduleName").isEmpty()) { + return; + } + throwDataValidationException("Wrong module condition property. - please supply a valid condition property."); + } + + private static void throwDataValidationException(final String details) { + throw new DataValidationException("Invalid Query Parameter.", details); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java index 42719d9b3..ff5204ff6 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications (C) 2021 Nordix Foundation + * Modifications (C) 2021-2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ public class DataMapUtils { */ public static Map toDataMapWithIdentifier(final DataNode dataNode) { return ImmutableMap.builder() - .put(getNodeIdentifier(dataNode.getXpath()), toDataMap(dataNode)) + .put(getNodeIdentifierWithPrefix(dataNode.getXpath(), dataNode.getModuleNamePrefix()), toDataMap(dataNode)) .build(); } @@ -96,6 +96,13 @@ public class DataMapUtils { return toIndex > 0 ? xpath.substring(fromIndex, toIndex) : xpath.substring(fromIndex); } + private static String getNodeIdentifierWithPrefix(final String xpath, final String moduleNamePrefix) { + if (moduleNamePrefix != null) { + return moduleNamePrefix + ":" + getNodeIdentifier(xpath); + } + return getNodeIdentifier(xpath); + } + private static boolean isContainerNode(final String xpath) { return !isListElement(xpath); } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy index 33868ccf0..def99e21f 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy @@ -177,15 +177,6 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace') } - def 'Query CM Handles.'() { - given: 'a cm handle query' - def cmHandleQueryParameters = new CmHandleQueryParameters() - when: 'query cm handles is invoked' - objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'associated persistence service method is invoked with correct parameter' - 1 * mockCpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters) - } - def 'Delete dataspace with invalid dataspace id.'() { when: 'delete dataspace is invoked' objectUnderTest.deleteDataspace('some dataspace name') @@ -194,5 +185,4 @@ class CpsAdminServiceImplSpec extends Specification { and: 'associated persistence service method is not invoked' 0 * mockCpsAdminPersistenceService.deleteDataspace(_) } - } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy index ce54ead2a..16d4efc27 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy @@ -22,6 +22,7 @@ package org.onap.cps.spi.model import org.onap.cps.TestUtils import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.utils.DataMapUtils import org.onap.cps.utils.YangUtils import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.common.QName @@ -172,6 +173,22 @@ class DataNodeBuilderSpec extends Specification { 'NormalizedNode is an unsupported type' | 'not supported' | Mock(NormalizedNode) | 0 | [ ] } + def 'Use of adding the module name prefix attribute of data node.'() { + when: 'data node is built with a prefix' + def testDataNode = new DataNodeBuilder() + .withModuleNamePrefix('sampleModuleNamePrefix') + .withXpath(xPath) + .withLeaves(sampleLeaves) + .build() + then: 'the result when node request is a #scenario includes the correct prefix' + def result = new DataMapUtils().toDataMapWithIdentifier(testDataNode) + result.toString() == expectedResult + where: 'the following parameters are used' + scenario | xPath | sampleLeaves | expectedResult + 'list attribute' | '/test-tree/branch[@name=\'Right\']/nest' | [name: 'Big', birds: ['Owl']] | '{sampleModuleNamePrefix:nest={name=Big, birds=[Owl]}}' + 'container xpath' | '/test-tree/branch[@name=\'Left\']' | [name: 'Left'] | '{sampleModuleNamePrefix:branch={name=Left}}' + } + def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) { expectedLeavesMap.each { key, value -> { diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/CmHandleQueryRestParametersValidatorSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/CmHandleQueryRestParametersValidatorSpec.groovy new file mode 100644 index 000000000..645829b2a --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/utils/CmHandleQueryRestParametersValidatorSpec.groovy @@ -0,0 +1,91 @@ +/* + * ============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.utils + +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.model.CmHandleQueryParameters +import org.onap.cps.spi.model.ConditionProperties +import spock.lang.Specification + +class CmHandleQueryRestParametersValidatorSpec extends Specification { + def 'CM Handle Query validation: empty query.'() { + given: 'a cm handle query' + def cmHandleQueryParameters = new CmHandleQueryParameters() + when: 'validator is invoked' + CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters) + then: 'data validation exception is not thrown' + noExceptionThrown() + } + + def 'CM Handle Query validation: normal query.'() { + given: 'a cm handle query' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def condition = new ConditionProperties() + condition.conditionName = 'hasAllProperties' + condition.conditionParameters = [[key1:'value1'],[key2:'value2']] + cmHandleQueryParameters.cmHandleQueryParameters = [condition] + when: 'validator is invoked' + CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters) + then: 'data validation exception is not thrown' + noExceptionThrown() + } + + def 'CM Handle Query validation: #scenario.'() { + given: 'a cm handle query' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def condition = new ConditionProperties() + condition.conditionName = conditionName + condition.conditionParameters = conditionParameters + cmHandleQueryParameters.cmHandleQueryParameters = [condition] + when: 'validator is invoked' + CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + where: + scenario | conditionName | conditionParameters + 'empty properties' | 'hasAllProperties' | [[ : ]] + 'empty conditions' | 'hasAllProperties' | [] + 'wrong condition name' | 'wrong' | [] + 'no condition name' | '' | [] + 'too many properties' | 'hasAllProperties' | [[key1:'value1', key2:'value2']] + 'wrong properties' | 'hasAllProperties' | [['':'wrong']] + } + + def 'CM Handle Query validation: validate module name condition properties - valid query.'() { + given: 'a condition property' + def conditionProperty = [moduleName: 'value'] + when: 'validator is invoked' + CmHandleQueryRestParametersValidator.validateModuleNameConditionProperties(conditionProperty) + then: 'data validation exception is not thrown' + noExceptionThrown() + } + + def 'CM Handle Query validation: validate module name condition properties - #scenario.'() { + when: 'validator is invoked' + CmHandleQueryRestParametersValidator.validateModuleNameConditionProperties(conditionProperty) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + where: + scenario | conditionProperty + 'invalid value' | [moduleName: ''] + 'invalid name' | [wrongName: 'value'] + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy index 90563c0c1..24e8061b5 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2020 Nordix Foundation + * Modifications Copyright (C) 2020-2022 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,13 +29,13 @@ class DataMapUtilsSpec extends Specification { def noChildren = [] def dataNode = buildDataNode( - "/parent",[parentLeaf:'parentLeafValue', parentLeafList:['parentLeafListEntry1','parentLeafListEntry2']],[ - buildDataNode('/parent/child-list[@id=1]',[listElementLeaf:'listElement1leafValue'],noChildren), - buildDataNode('/parent/child-list[@id=2]',[listElementLeaf:'listElement2leafValue'],noChildren), - buildDataNode('/parent/child-object',[childLeaf:'childLeafValue'], - [buildDataNode('/parent/child-object/grand-child-object',[grandChildLeaf:'grandChildLeafValue'],noChildren)] - ), - ]) + "/parent",[parentLeaf:'parentLeafValue', parentLeafList:['parentLeafListEntry1','parentLeafListEntry2']],[ + buildDataNode('/parent/child-list[@id=1]',[listElementLeaf:'listElement1leafValue'],noChildren), + buildDataNode('/parent/child-list[@id=2]',[listElementLeaf:'listElement2leafValue'],noChildren), + buildDataNode('/parent/child-object',[childLeaf:'childLeafValue'], + [buildDataNode('/parent/child-object/grand-child-object',[grandChildLeaf:'grandChildLeafValue'],noChildren)] + ), + ]) static def buildDataNode(xpath, leaves, children) { return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(children).build() @@ -81,4 +81,16 @@ class DataMapUtilsSpec extends Specification { and: 'leaves for grandchild element is populated under its node identifier' parentNode.'child-object'.'grand-child-object'.grandChildLeaf == 'grandChildLeafValue' } + + def 'Adding prefix to data node identifier.'() { + when: 'a valid xPath is passed to the addPrefixToXpath method' + def result = new DataMapUtils().getNodeIdentifierWithPrefix(xPath,'sampleModuleName') + then: 'the correct modified node identifier is given' + assert result == expectedNodeIdentifier + where: 'the following parameters are used' + scenario | xPath | expectedNodeIdentifier + 'container xpath' | '/bookstore' | 'sampleModuleName:bookstore' + 'xpath contains list attribute' | '/bookstore/categories[@code=1]' | 'sampleModuleName:categories' + } } + diff --git a/csit/plans/cps/testplan.txt b/csit/plans/cps/testplan.txt index d4615e701..c0cf4512d 100644 --- a/csit/plans/cps/testplan.txt +++ b/csit/plans/cps/testplan.txt @@ -21,4 +21,4 @@ cps-admin cps-data cps-model-sync ncmp-passthrough -public-properties-query \ No newline at end of file +cm-handle-query \ No newline at end of file diff --git a/csit/tests/public-properties-query/public-properties-query.robot b/csit/tests/cm-handle-query/cm-handle-query.robot similarity index 61% rename from csit/tests/public-properties-query/public-properties-query.robot rename to csit/tests/cm-handle-query/cm-handle-query.robot index 3a640871b..3adc25362 100644 --- a/csit/tests/public-properties-query/public-properties-query.robot +++ b/csit/tests/cm-handle-query/cm-handle-query.robot @@ -32,20 +32,29 @@ Suite Setup Create Session CPS_URL http://${CPS_CORE_HOST}:${C ${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE= ${ncmpBasePath} /ncmp/v1 -${jsonMatchingQueryParameters} {"publicCmHandleProperties": {"Contact" : "newemailforstore@bookstore.com", "Contact2" : "storeemail2@bookstore.com"}} -${jsonMissingPropertyQueryParameters} {"publicCmHandleProperties": { "" : "doesnt matter"}} +${jsonModuleAndPropertyQueryParameters} {"cmHandleQueryParameters": [{"conditionName": "hasAllModules", "conditionParameters": [ {"moduleName": "iana-crypt-hash"} ]}, {"conditionName": "hasAllProperties", "conditionParameters": [ {"Contact": "newemailforstore@bookstore.com"} ]}]} +${jsonEmptyQueryParameters} {} +${jsonMissingPropertyQueryParameters} {"cmHandleQueryParameters": [{"conditionName": "hasAllProperties", "conditionParameters": [{"" : "doesnt matter"}]}]} *** Test Cases *** -Retrieve CM Handles where query parameters Match - ${uri}= Set Variable ${ncmpBasePath}/data/ch/searches +Retrieve CM Handle ids where query parameters Match (module and property query) + ${uri}= Set Variable ${ncmpBasePath}/ch/id-searches ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} - ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonMatchingQueryParameters} + ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonModuleAndPropertyQueryParameters} + ${responseJson}= Set Variable ${response.json()} + Should Be Equal As Strings ${response.status_code} 200 + Should Contain ${responseJson} PNFDemo + +Retrieve CM Handle ids where query parameters Match (empty query) + ${uri}= Set Variable ${ncmpBasePath}/ch/id-searches + ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} + ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonEmptyQueryParameters} ${responseJson}= Set Variable ${response.json()} Should Be Equal As Strings ${response.status_code} 200 Should Contain ${responseJson} PNFDemo Throw 400 when Structure of Request is Incorrect - ${uri}= Set Variable ${ncmpBasePath}/data/ch/searches + ${uri}= Set Variable ${ncmpBasePath}/ch/id-searches ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${jsonMissingPropertyQueryParameters} expected_status=400 Should Be Equal As Strings ${response} diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot index 55667c3c1..844b5d53f 100644 --- a/csit/tests/cps-data/cps-data.robot +++ b/csit/tests/cps-data/cps-data.robot @@ -1,6 +1,7 @@ # ============LICENSE_START======================================================= # Copyright (c) 2021 Pantheon.tech. # Modifications Copyright (C) 2022 Bell Canada. +# Modifications 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. @@ -46,7 +47,7 @@ Get Data Node by XPath ${params}= Create Dictionary xpath=/test-tree/branch[@name='Left']/nest ${headers}= Create Dictionary Authorization=${auth} ${response}= Get On Session CPS_URL ${uri} params=${params} headers=${headers} expected_status=200 - ${responseJson}= Set Variable ${response.json()['nest']} + ${responseJson}= Set Variable ${response.json()['test-tree:nest']} Should Be Equal As Strings ${responseJson['name']} Small diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index f2f477fe1..9edea3526 100755 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -112,6 +112,7 @@ services: KAFKA_BOOTSTRAP_SERVER: kafka:9092 notification.data-updated.enabled: 'true' NOTIFICATION_DATASPACE_FILTER_PATTERNS: '.*' + TIMERS_ADVISED-MODULES-SYNC_SLEEP-TIME-MS: 2000 restart: unless-stopped depends_on: - dbpostgresql