diff options
103 files changed, 5200 insertions, 1236 deletions
diff --git a/cps-application/pom.xml b/cps-application/pom.xml index 4ff3111f73..19710be80b 100644 --- a/cps-application/pom.xml +++ b/cps-application/pom.xml @@ -84,11 +84,6 @@ <!-- T E S T D E P E N D E N C I E S --> <dependency> - <groupId>org.springframework.security</groupId> - <artifactId>spring-security-test</artifactId> - <scope>test</scope> - </dependency> - <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <scope>test</scope> @@ -111,6 +106,7 @@ <dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit-junit5</artifactId> + <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index dd4576ec02..8ca6b0fe02 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -189,10 +189,10 @@ logging: cps: INFO ncmp: policy-executor: - enabled: true + enabled: false server: - address: "http://localhost" - port: "8785" + address: http://localhost + port: 8785 httpclient: all-services: maximumInMemorySizeInMegabytes: 16 diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index 6ed4db9b96..adef9031ab 100644 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -249,7 +249,7 @@ <dependency> <groupId>org.liquibase</groupId> <artifactId>liquibase-core</artifactId> - <version>4.28.0</version> + <version>4.29.0</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java index 11adc84a1f..183698c4f5 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java @@ -165,7 +165,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { } @Override - public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(final String cmHandle) { + public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId( + final String cmHandleReference) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index a9311d0861..112dddf61c 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -505,6 +505,14 @@ components: schema: type: string example: my-cm-handle + cmHandleReferenceInPath: + name: cm-handle + in: path + description: The identifier (cmHandle or alternate) for a network function, network element, subnetwork or any other cm object by managed Network CM Proxy + required: true + schema: + type: string + example: my-cm-handle-reference moduleNameInQuery: name: module-name in: query diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index 6972463e3c..adb2419c8a 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -27,7 +27,7 @@ resourceDataForCmHandle: operationId: getResourceDataForCmHandle parameters: - $ref: 'components.yaml#/components/parameters/datastoreName' - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' - $ref: 'components.yaml#/components/parameters/topicParamInQuery' @@ -60,7 +60,7 @@ resourceDataForCmHandle: operationId: createResourceDataRunningForCmHandle parameters: - $ref: 'components.yaml#/components/parameters/datastoreName' - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader' @@ -99,7 +99,7 @@ resourceDataForCmHandle: operationId: updateResourceDataRunningForCmHandle parameters: - $ref: 'components.yaml#/components/parameters/datastoreName' - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader' @@ -138,7 +138,7 @@ resourceDataForCmHandle: operationId: patchResourceDataRunningForCmHandle parameters: - $ref: 'components.yaml#/components/parameters/datastoreName' - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader' @@ -171,7 +171,7 @@ resourceDataForCmHandle: operationId: deleteResourceDataRunningForCmHandle parameters: - $ref: 'components.yaml#/components/parameters/datastoreName' - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' - $ref: 'components.yaml#/components/parameters/contentParamInHeader' - $ref: 'components.yaml#/components/parameters/authorizationParamInHeader' @@ -264,7 +264,7 @@ fetchModuleReferencesByCmHandle: summary: Fetch all module references (name and revision) for a given cm handle operationId: getModuleReferencesByCmHandle parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' responses: 200: description: OK @@ -289,7 +289,7 @@ getModuleDefinitions: description: Get module definitions (module name, revision, yang resource) with options to filter on module name and revision operationId: getModuleDefinitions parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' - $ref: 'components.yaml#/components/parameters/moduleNameInQuery' - $ref: 'components.yaml#/components/parameters/revisionInQuery' responses: @@ -354,7 +354,7 @@ retrieveCmHandleDetailsById: summary: Retrieve CM handle details operationId: retrieveCmHandleDetailsById parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' responses: 200: description: OK @@ -377,7 +377,7 @@ getCmHandlePropertiesById: summary: Get CM handle properties operationId: getCmHandlePublicPropertiesByCmHandleId parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' responses: 200: description: OK @@ -400,7 +400,7 @@ getCmHandleStateById: summary: Get CM handle state operationId: getCmHandleStateByCmHandleId parameters: - - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/cmHandleReferenceInPath' responses: 200: description: OK diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index af5f226a4f..42f709dcf5 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 @@ -148,7 +148,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Patch resource data. * * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") - * @param cmHandle cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @param resourceIdentifier resource identifier * @param requestBody the request body * @param contentType content type of body @@ -158,7 +158,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { @Override public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName, - final String cmHandle, + final String cmHandleReference, final String resourceIdentifier, final Object requestBody, final String contentType, @@ -168,7 +168,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final Object responseObject = networkCmProxyFacade .writeResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, PATCH, + cmHandleReference, resourceIdentifier, PATCH, jsonObjectMapper.asJsonString(requestBody), contentType, authorization); return ResponseEntity.ok(responseObject); } @@ -177,7 +177,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Create resource data for given cm-handle. * * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") - * @param cmHandle cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @param resourceIdentifier resource identifier * @param requestBody the request body * @param contentType content type of body @@ -186,14 +186,14 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ @Override public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName, - final String cmHandle, + final String cmHandleReference, final String resourceIdentifier, final Object requestBody, final String contentType, final String authorization) { validateDataStore(PASSTHROUGH_RUNNING, datastoreName); - networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandle, + networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandleReference, resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType, authorization); return new ResponseEntity<>(HttpStatus.CREATED); } @@ -202,7 +202,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Update resource data for given cm-handle. * * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") - * @param cmHandle cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @param resourceIdentifier resource identifier * @param requestBody the request body * @param contentType content type of the body @@ -212,14 +212,14 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { @Override public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName, - final String cmHandle, + final String cmHandleReference, final String resourceIdentifier, final Object requestBody, final String contentType, final String authorization) { validateDataStore(PASSTHROUGH_RUNNING, datastoreName); - networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandle, + networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandleReference, resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType, authorization); return new ResponseEntity<>(HttpStatus.OK); } @@ -228,7 +228,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * Delete resource data for a given cm-handle. * * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") - * @param cmHandle cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @param resourceIdentifier resource identifier * @param contentType content type of the body * @param authorization contents of Authorization header, or null if not present @@ -236,14 +236,14 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ @Override public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName, - final String cmHandle, + final String cmHandleReference, final String resourceIdentifier, final String contentType, final String authorization) { validateDataStore(PASSTHROUGH_RUNNING, datastoreName); - networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandle, + networkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle(cmHandleReference, resourceIdentifier, DELETE, NO_BODY, contentType, authorization); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -286,28 +286,28 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Search for Cm Handle and Properties by Name. * - * @param cmHandleId cm-handle identifier + * @param cmHandleReference cm-handle or alternate identifier * @return cm handle and its properties */ @Override - public ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleId) { + public ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleReference) { final NcmpServiceCmHandle ncmpServiceCmHandle - = networkCmProxyInventoryFacade.getNcmpServiceCmHandle(cmHandleId); + = networkCmProxyInventoryFacade.getNcmpServiceCmHandle(cmHandleReference); final RestOutputCmHandle restOutputCmHandle = toRestOutputCmHandle(ncmpServiceCmHandle); return ResponseEntity.ok(restOutputCmHandle); } /** - * Get Cm Handle Properties by Cm Handle Id. + * Get Cm Handle Properties by Cm Handle or alternate Identifier. * - * @param cmHandleId cm-handle identifier + * @param cmHandleReference cm-handle or alternate identifier * @return cm handle properties */ @Override public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { + final String cmHandleReference) { final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); - cmHandlePublicProperties.add(networkCmProxyInventoryFacade.getCmHandlePublicProperties(cmHandleId)); + cmHandlePublicProperties.add(networkCmProxyInventoryFacade.getCmHandlePublicProperties(cmHandleReference)); final RestOutputCmHandlePublicProperties restOutputCmHandlePublicProperties = new RestOutputCmHandlePublicProperties(); restOutputCmHandlePublicProperties.setPublicCmHandleProperties(cmHandlePublicProperties); @@ -317,13 +317,13 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Get Cm Handle State by Cm Handle Id. * - * @param cmHandleId cm-handle identifier + * @param cmHandleReference cm-handle or alternate identifier * @return cm handle state */ @Override public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId( - final String cmHandleId) { - final CompositeState cmHandleState = networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleId); + final String cmHandleReference) { + final CompositeState cmHandleState = networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleReference); final RestOutputCmHandleCompositeState restOutputCmHandleCompositeState = new RestOutputCmHandleCompositeState(); restOutputCmHandleCompositeState.setState( @@ -334,21 +334,23 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Return module definitions. * - * @param cmHandleId cm-handle identifier - * @param moduleName module name - * @param revision the revision of the module + * @param cmHandleReference cm handle or alternate id identifier + * @param moduleName module name + * @param revision the revision of the module * @return list of module definitions (module name, revision, yang resource content) */ @Override - public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitions(final String cmHandleId, + public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitions(final String cmHandleReference, final String moduleName, final String revision) { final Collection<ModuleDefinition> moduleDefinitions; if (StringUtils.hasText(moduleName)) { moduleDefinitions = - networkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, revision); + networkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule(cmHandleReference, + moduleName, revision); } else { - moduleDefinitions = networkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleId(cmHandleId); + moduleDefinitions = + networkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleReference(cmHandleReference); if (StringUtils.hasText(revision)) { log.warn("Ignoring revision filter as no module name is provided"); } @@ -363,12 +365,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Return module references for a cm handle. * - * @param cmHandle the cm handle + * @param cmHandleReference cm handle or alternate id identifier * @return module references for cm handle. Namespace will be always blank because restConf does not include this. */ - public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) { + public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandleReference) { final List<RestModuleReference> restModuleReferences = - networkCmProxyInventoryFacade.getYangResourcesModuleReferences(cmHandle).stream() + networkCmProxyInventoryFacade.getYangResourcesModuleReferences(cmHandleReference).stream() .map(ncmpRestInputMapper::toRestModuleReference) .collect(Collectors.toList()); return new ResponseEntity<>(restModuleReferences, HttpStatus.OK); diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java index d61d30dc2b..6910003c54 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java @@ -31,6 +31,7 @@ import org.onap.cps.ncmp.api.exceptions.DmiRequestException; import org.onap.cps.ncmp.api.exceptions.InvalidTopicException; import org.onap.cps.ncmp.api.exceptions.NcmpException; import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException; +import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException; import org.onap.cps.ncmp.api.exceptions.ServerNcmpException; import org.onap.cps.ncmp.rest.model.DmiErrorMessage; import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiResponse; @@ -84,8 +85,8 @@ public class NetworkCmProxyRestExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, exception); } - @ExceptionHandler({AlreadyDefinedException.class}) - public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) { + @ExceptionHandler({AlreadyDefinedException.class, PolicyExecutorException.class}) + public static ResponseEntity<Object> handleConflictExceptions(final Exception exception) { return buildErrorResponse(HttpStatus.CONFLICT, exception); } @@ -113,8 +114,6 @@ public class NetworkCmProxyRestExceptionHandler { } else { errorMessage.setDetails(CHECK_LOGS_FOR_DETAILS); } - errorMessage.setDetails( - exception instanceof CpsException ? ((CpsException) exception).getDetails() : CHECK_LOGS_FOR_DETAILS); return new ResponseEntity<>(errorMessage, status); } 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 9f5331dbc3..43403fa890 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -32,7 +32,6 @@ import groovy.json.JsonSlurper import org.mapstruct.factory.Mappers import org.onap.cps.TestUtils import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.data.models.CmResourceAddress import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle @@ -95,7 +94,7 @@ class NetworkCmProxyControllerSpec extends Specification { NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock() @SpringBean - AlternateIdMatcher mockalternateIdMatcher = Mock() + AlternateIdMatcher mockAlternateIdMatcher = Mock() @SpringBean ObjectMapper objectMapper = new ObjectMapper() @@ -285,17 +284,18 @@ class NetworkCmProxyControllerSpec extends Specification { assert response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"NONE","moduleSetTag":null,"alternateId":null,"dataProducerIdentifier":null},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":null,"moduleSetTag":"someModuleSetTag","alternateId":"someAlternateId","dataProducerIdentifier":"someDataProducerIdentifier"}]' } - def 'Get complete Cm Handle details by Cm Handle id.'() { - given: 'an endpoint and a cm handle' - def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle" + def 'Get complete Cm Handle details by Cm Handle Reference.'() { + given: 'an endpoint and a cm handle reference' + def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference" and: 'an existing ncmp service cm handle' def cmHandleId = 'some-cm-handle' + def alternateId = 'some-alternate-id' def dmiProperties = [prop: 'some DMI property'] def publicProperties = ["public prop": 'some public property'] def compositeState = compositeStateTestObject() - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, currentTrustLevel: TrustLevel.COMPLETE) - and: 'the service method is invoked with the cm handle id' - 1 * mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: alternateId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, currentTrustLevel: TrustLevel.COMPLETE) + and: 'the service method is invoked with the cm handle reference' + 1 * mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('some-cm-handle-reference') >> ncmpServiceCmHandle when: 'the cm handle details api is invoked' def response = mvc.perform( get(cmHandleDetailsEndpoint)).andReturn().response @@ -309,13 +309,13 @@ class NetworkCmProxyControllerSpec extends Specification { assert !response.contentAsString.contains("some DMI property") } - def 'Get Cm Handle public properties by Cm Handle id.'() { + def 'Get Cm Handle public properties by Cm Handle Reference.'() { given: 'a cm handle properties endpoint' - def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties" + def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/properties" and: 'some cm handle public properties' def publicProperties = ['public prop': 'some public property'] and: 'the service method is invoked with the cm handle id returning the cm handle public properties' - 1 * mockNetworkCmProxyInventoryFacade.getCmHandlePublicProperties('some-cm-handle') >> publicProperties + 1 * mockNetworkCmProxyInventoryFacade.getCmHandlePublicProperties('some-cm-handle-reference') >> publicProperties when: 'the cm handle properties api is invoked' def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' @@ -324,13 +324,13 @@ class NetworkCmProxyControllerSpec extends Specification { assertContainsPublicProperties(response) } - def 'Get Cm Handle composite state by Cm Handle id.'() { + def 'Get Cm Handle composite state by Cm Handle Reference.'() { given: 'a cm handle state endpoint' - def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state" + def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/state" and: 'some cm handle composite state' def compositeState = compositeStateTestObject() and: 'the service method is invoked with the cm handle id returning the cm handle composite state' - 1 * mockNetworkCmProxyInventoryFacade.getCmHandleCompositeState('some-cm-handle') >> compositeState + 1 * mockNetworkCmProxyInventoryFacade.getCmHandleCompositeState('some-cm-handle-reference') >> compositeState when: 'the cm handle state api is invoked' def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response then: 'the correct response is returned' @@ -416,12 +416,12 @@ class NetworkCmProxyControllerSpec extends Specification { def 'Getting module definitions filtering on #scenario'() { when: 'get module definition request is performed' def response = mvc.perform( - get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=" + moduleName + "&revision=" + revision)) + get("$ncmpBasePathV1/ch/some-cmhandle-reference/modules/definitions?module-name=" + moduleName + "&revision=" + revision)) .andReturn().response - then: 'ncmp service method to get definitions by cm handle is invoked when needed' - numberOfCallsToByCmHandleId * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleId('some-cmhandle') >> [] + then: 'ncmp service method to get definitions by cm handle reference is invoked when needed' + numberOfCallsToByCmHandleId * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleReference('some-cmhandle-reference') >> [] and: 'ncmp service method to get definitions by module is invoked when needed' - numberOfCallsToByModule * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', moduleName, revision) >> [] + numberOfCallsToByModule * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle-reference', moduleName, revision) >> [] and: 'response returns an OK http code' response.status == HttpStatus.OK.value() and: 'the correct message is logged when needed' diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index e6288ffbec..9d36d106c7 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -29,11 +29,13 @@ import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException import org.onap.cps.ncmp.api.exceptions.DmiRequestException import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException +import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException import org.onap.cps.ncmp.api.exceptions.ServerNcmpException import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade +import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.rest.util.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper @@ -120,25 +122,26 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { dataNodeBaseEndpointNcmpInventory = "$basePathNcmpInventory/v1" } - def 'Get request with #scenario exception returns correct HTTP Status with #scenario'() { + def 'Get request with #scenario exception returns correct HTTP Status with #scenario exception'() { when: 'an exception is thrown by the service' setupTestException(exception, NCMP) def response = performTestRequest(NCMP) then: 'an HTTP response is returned with correct message and details' assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails) where: - scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails - 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails - 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null - 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null - 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null - 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null - 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null - 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null - 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found' - 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists' - 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist' - 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs' + scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails + 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails + 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null + 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null + 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null + 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null + 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null + 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null + 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found' + 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists' + 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist' + 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs' + 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails) || CONFLICT | sampleErrorMessage | sampleErrorDetails } def 'Post request with exception returns correct HTTP Status.'() { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java new file mode 100644 index 0000000000..333c12271b --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java @@ -0,0 +1,42 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.exceptions; + +import lombok.Getter; + +/** + * Exception to be used when policy execution fails or does not allow to proceed. + */ +@Getter +public class PolicyExecutorException extends NcmpException { + + private static final long serialVersionUID = 6659897770659834798L; + + /** + * Constructor to form exception for policy executor responses. + * + * @param message response message + * @param details response details + */ + public PolicyExecutorException(final String message, final String details) { + super(message, details); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java index 794bc238f4..cde4eacbf2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java @@ -26,6 +26,7 @@ package org.onap.cps.ncmp.api.inventory; import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCmHandleQueryParameters; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -36,7 +37,6 @@ import org.onap.cps.ncmp.api.inventory.models.CompositeState; import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; -import org.onap.cps.ncmp.api.inventory.models.TrustLevel; import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; @@ -44,12 +44,13 @@ import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService; import org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions; import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; -import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelCacheConfig; +import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager; +import org.onap.cps.ncmp.impl.models.RequiredDmiService; +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Slf4j @@ -62,9 +63,8 @@ public class NetworkCmProxyInventoryFacade { private final ParameterizedCmHandleQueryService parameterizedCmHandleQueryService; private final InventoryPersistence inventoryPersistence; private final JsonObjectMapper jsonObjectMapper; - - @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_CM_HANDLE) - private final Map<String, TrustLevel> trustLevelPerCmHandle; + private final TrustLevelManager trustLevelManager; + private final AlternateIdMatcher alternateIdMatcher; /** * Registration of Created, Removed, Updated or Upgraded CM Handles. @@ -72,7 +72,6 @@ public class NetworkCmProxyInventoryFacade { * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( final DmiPluginRegistration dmiPluginRegistration) { return cmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration); @@ -102,36 +101,39 @@ public class NetworkCmProxyInventoryFacade { /** - * Retrieve module references for the given cm handle. + * Retrieve module references for the given cm handle reference. * - * @param cmHandleId cm handle identifier + * @param cmHandleReference cm handle or alternate id identifier * @return a collection of modules names and revisions */ - public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) { + public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId); } /** * Retrieve module definitions for the given cm handle. * - * @param cmHandleId cm handle identifier + * @param cmHandleReference cm handle or alternate id identifier * @return a collection of module definition (moduleName, revision and yang resource content) */ - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleReference(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId); } /** * Get module definitions for the given parameters. * - * @param cmHandleId cm-handle identifier - * @param moduleName module name - * @param moduleRevision the revision of the module + * @param cmHandleReference cm handle or alternate id identifier + * @param moduleName module name + * @param moduleRevision the revision of the module * @return list of module definitions (module name, revision, yang resource content) */ - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleId, + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleReference, final String moduleName, final String moduleRevision) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision); } @@ -146,9 +148,13 @@ public class NetworkCmProxyInventoryFacade { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = + final Collection<YangModelCmHandle> yangModelCmHandles = parameterizedCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters); - ncmpServiceCmHandles.forEach(this::applyCurrentTrustLevel); + final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = new ArrayList<>(yangModelCmHandles.size()); + for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) { + final NcmpServiceCmHandle ncmpServiceCmHandle = toNcmpServiceCmHandleWithTrustLevel(yangModelCmHandle); + ncmpServiceCmHandles.add(ncmpServiceCmHandle); + } return ncmpServiceCmHandles; } @@ -177,25 +183,25 @@ public class NetworkCmProxyInventoryFacade { } /** - * Retrieve cm handle details for a given cm handle. + * Retrieve cm handle details for a given cm handle reference. * - * @param cmHandleId cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @return cm handle details */ - public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleId) { - final NcmpServiceCmHandle ncmpServiceCmHandle = YangDataConverter.toNcmpServiceCmHandle( - inventoryPersistence.getYangModelCmHandle(cmHandleId)); - applyCurrentTrustLevel(ncmpServiceCmHandle); - return ncmpServiceCmHandle; + public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); + return toNcmpServiceCmHandleWithTrustLevel(yangModelCmHandle); } /** - * Get cm handle public properties for a given cm handle id. + * Get cm handle public properties for a given cm handle or alternate id. * - * @param cmHandleId cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @return cm handle public properties */ - public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) { + public Map<String, String> getCmHandlePublicProperties(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); return YangDataConverter.toPropertiesMap(yangModelCmHandle.getPublicProperties()); } @@ -203,16 +209,20 @@ public class NetworkCmProxyInventoryFacade { /** * Get cm handle composite state for a given cm handle id. * - * @param cmHandleId cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @return cm handle state */ - public CompositeState getCmHandleCompositeState(final String cmHandleId) { + public CompositeState getCmHandleCompositeState(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState(); } - private void applyCurrentTrustLevel(final NcmpServiceCmHandle ncmpServiceCmHandle) { - ncmpServiceCmHandle.setCurrentTrustLevel(trustLevelPerCmHandle.get(ncmpServiceCmHandle.getCmHandleId())); + private NcmpServiceCmHandle toNcmpServiceCmHandleWithTrustLevel(final YangModelCmHandle yangModelCmHandle) { + final NcmpServiceCmHandle ncmpServiceCmHandle = YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle); + final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA); + ncmpServiceCmHandle.setCurrentTrustLevel( + trustLevelManager.getEffectiveTrustLevel(dmiServiceName, ncmpServiceCmHandle.getCmHandleId())); + return ncmpServiceCmHandle; } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java index 8be57fed3d..301b8195e4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java @@ -45,12 +45,12 @@ import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor; import org.onap.cps.ncmp.impl.data.utils.DmiDataOperationsHelper; import org.onap.cps.ncmp.impl.dmi.DmiProperties; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; -import org.onap.cps.ncmp.impl.dmi.DmiServiceUrlTemplateBuilder; -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.models.DmiRequestBody; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; @@ -139,8 +139,7 @@ public class DmiDataOperations { final String requestId, final String authorization) { - final Set<String> cmHandlesIds - = getDistinctCmHandleIds(dataOperationRequest); + final Set<String> cmHandlesIds = getDistinctCmHandleIds(dataOperationRequest); final Collection<YangModelCmHandle> yangModelCmHandles = inventoryPersistence.getYangModelCmHandles(cmHandlesIds); @@ -171,7 +170,10 @@ public class DmiDataOperations { final String requestData, final String dataType, final String authorization) { - final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); + final CmResourceAddress cmResourceAddress = + new CmResourceAddress(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId); + + final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmResourceAddress.getResolvedCmHandleId()); policyExecutor.checkPermission(yangModelCmHandle, operationType, authorization, resourceId, requestData); @@ -213,7 +215,7 @@ public class DmiDataOperations { final String optionsParamInQuery, final String topicParamInQuery) { final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(DATA); - return DmiServiceUrlTemplateBuilder.newInstance() + return RestServiceUrlTemplateBuilder.newInstance() .fixedPathSegment("ch") .variablePathSegment("cmHandleId", yangModelCmHandle.getId()) .fixedPathSegment("data") @@ -228,7 +230,7 @@ public class DmiDataOperations { private UrlTemplateParameters getUrlTemplateParameters(final String dmiServiceName, final String requestId, final String topicParamInQuery) { - return DmiServiceUrlTemplateBuilder.newInstance() + return RestServiceUrlTemplateBuilder.newInstance() .fixedPathSegment("data") .queryParameter("requestId", requestId) .queryParameter("topic", topicParamInQuery) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java index b97088a5e0..5343a2e131 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java @@ -101,7 +101,7 @@ public class NetworkCmProxyFacade { /** * Write resource data for data store pass-through running using dmi for given cm-handle. * - * @param cmHandleId cm handle identifier + * @param cmHandleReference cm handle or alternate identifier * @param resourceIdentifier resource identifier * @param operationType required operation type * @param requestData request body to create resource @@ -109,13 +109,13 @@ public class NetworkCmProxyFacade { * @param authorization contents of Authorization header, or null if not present * @return {@code Object} return data */ - public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleId, + public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleReference, final String resourceIdentifier, final OperationType operationType, final String requestData, final String dataType, final String authorization) { - return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, + return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleReference, resourceIdentifier, operationType, requestData, dataType, authorization); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java index 8e7620ccea..b3aa848394 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java @@ -20,12 +20,29 @@ package org.onap.cps.ncmp.impl.data.policyexecutor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +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.data.models.OperationType; +import org.onap.cps.ncmp.api.exceptions.NcmpException; +import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException; +import org.onap.cps.ncmp.api.exceptions.ServerNcmpException; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; @Slf4j @Service @@ -41,7 +58,10 @@ public class PolicyExecutor { @Value("${ncmp.policy-executor.server.port:8080}") private String serverPort; - private static final String PAYLOAD_TYPE_PREFIX = "cm_"; + @Qualifier("policyExecutorWebClient") + private final WebClient policyExecutorWebClient; + + private final ObjectMapper objectMapper; /** * Use the Policy Executor to check permission for a cm write operation. @@ -58,17 +78,108 @@ public class PolicyExecutor { final String authorization, final String resourceIdentifier, final String changeRequestAsJson) { + log.trace("Policy Executor Enabled: {}", enabled); if (enabled) { - final String payloadType = PAYLOAD_TYPE_PREFIX + operationType.getOperationName(); - log.info("Policy Executor Enabled"); - log.info("Address : {}", serverAddress); - log.info("Port : {}", serverPort); - log.info("Authorization : {}", authorization); - log.info("Payload Type : {}", payloadType); - log.info("Target FDN : {}", yangModelCmHandle.getAlternateId()); - log.info("CM Handle Id : {}", yangModelCmHandle.getId()); - log.info("Resource Identifier : {}", resourceIdentifier); - log.info("Change Request (json) : {}", changeRequestAsJson); + final ResponseEntity<JsonNode> responseEntity = + getPolicyExecutorResponse(yangModelCmHandle, operationType, authorization, resourceIdentifier, + changeRequestAsJson); + + if (responseEntity == null) { + log.warn("No valid response from policy, ignored"); + return; + } + + if (responseEntity.getStatusCode().is2xxSuccessful()) { + if (responseEntity.getBody() == null) { + log.warn("No valid response body from policy, ignored"); + return; + } + processResponse(responseEntity.getBody()); + } else { + log.warn("Policy Executor invocation failed with status {}", + responseEntity.getStatusCode().value()); + throw new ServerNcmpException("Policy Executor invocation failed", "HTTP status code: " + + responseEntity.getStatusCode().value()); + } + } + } + + private Map<String, Object> getSingleRequestAsMap(final YangModelCmHandle yangModelCmHandle, + final OperationType operationType, + final String resourceIdentifier, + final String changeRequestAsJson) { + final Map<String, Object> data = new HashMap<>(4); + data.put("cmHandleId", yangModelCmHandle.getId()); + data.put("resourceIdentifier", resourceIdentifier); + data.put("targetIdentifier", yangModelCmHandle.getAlternateId()); + if (!OperationType.DELETE.equals(operationType)) { + try { + final Object changeRequestAsObject = objectMapper.readValue(changeRequestAsJson, Object.class); + data.put("cmChangeRequest", changeRequestAsObject); + } catch (final JsonProcessingException e) { + throw new NcmpException("Cannot convert Change Request data to Object", + "Invalid Json: " + changeRequestAsJson); + } } + + final Map<String, Object> request = new HashMap<>(2); + request.put("schema", getAssociatedPolicyDataSchemaName(operationType)); + request.put("data", data); + return request; + } + + private static String getAssociatedPolicyDataSchemaName(final OperationType operationType) { + return "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-" + operationType.getOperationName() + "-schema:1.0.0"; + } + + private Object createBodyAsObject(final List<Object> requests) { + final Map<String, Object> bodyAsMap = new HashMap<>(2); + bodyAsMap.put("decisionType", "allow"); + bodyAsMap.put("requests", requests); + return bodyAsMap; } + + private ResponseEntity<JsonNode> getPolicyExecutorResponse(final YangModelCmHandle yangModelCmHandle, + final OperationType operationType, + final String authorization, + final String resourceIdentifier, + final String changeRequestAsJson) { + final String serviceBaseUrl = serverAddress + ":" + serverPort; + + final Map<String, Object> requestAsMap = getSingleRequestAsMap(yangModelCmHandle, + operationType, + resourceIdentifier, + changeRequestAsJson); + + final Object bodyAsObject = createBodyAsObject(Collections.singletonList(requestAsMap)); + + final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance() + .fixedPathSegment("execute") + .createUrlTemplateParameters(serviceBaseUrl, ""); + + return policyExecutorWebClient.post() + .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) + .header(HttpHeaders.AUTHORIZATION, authorization) + .body(BodyInserters.fromValue(bodyAsObject)) + .retrieve() + .toEntity(JsonNode.class) + .block(); + } + + private static void processResponse(final JsonNode responseBody) { + final String decisionId = responseBody.path("decisionId").asText("unknown id"); + log.trace("Policy Executor Decision ID: {} ", decisionId); + final String decision = responseBody.path("decision").asText("unknown"); + if ("allow".equals(decision)) { + log.trace("Policy Executor allows the operation"); + } else { + log.warn("Policy Executor decision: {}", decision); + final String details = responseBody.path("message").asText(); + log.warn("Policy Executor message: {}", details); + final String message = "Policy Executor did not allow request. Decision #" + + decisionId + " : " + decision; + throw new PolicyExecutorException(message, details); + } + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java index 031cedc78b..8934c088a1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java @@ -24,8 +24,8 @@ import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.datajobs.DataJobResultService; import org.onap.cps.ncmp.impl.dmi.DmiProperties; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; -import org.onap.cps.ncmp.impl.dmi.DmiServiceUrlTemplateBuilder; -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.springframework.stereotype.Service; @Service @@ -41,7 +41,7 @@ public class DataJobResultServiceImpl implements DataJobResultService { final String dataProducerId, final String dataProducerJobId, final String destination) { - final UrlTemplateParameters urlTemplateParameters = DmiServiceUrlTemplateBuilder.newInstance() + final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance() .fixedPathSegment("cmwriteJob") .fixedPathSegment("dataProducer") .variablePathSegment("dataProducerId", dataProducerId) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java index fb17f066ce..1cfb8a9dff 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java @@ -24,8 +24,8 @@ import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.datajobs.DataJobStatusService; import org.onap.cps.ncmp.impl.dmi.DmiProperties; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; -import org.onap.cps.ncmp.impl.dmi.DmiServiceUrlTemplateBuilder; -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.springframework.stereotype.Service; /** @@ -54,7 +54,7 @@ public class DataJobStatusServiceImpl implements DataJobStatusService { private UrlTemplateParameters buildUrlParameters(final String dmiServiceName, final String dataProducerId, final String dataProducerJobId) { - return DmiServiceUrlTemplateBuilder.newInstance() + return RestServiceUrlTemplateBuilder.newInstance() .fixedPathSegment("cmwriteJob") .fixedPathSegment("dataProducer") .variablePathSegment("dataProducerId", dataProducerId) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java index 0d14dace5e..a118d53e7e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DmiSubJobRequestHandler.java @@ -33,9 +33,9 @@ import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteRequest; import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse; import org.onap.cps.ncmp.impl.dmi.DmiProperties; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; -import org.onap.cps.ncmp.impl.dmi.DmiServiceUrlTemplateBuilder; -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters; import org.onap.cps.ncmp.impl.models.RequiredDmiService; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -88,7 +88,7 @@ public class DmiSubJobRequestHandler { } private UrlTemplateParameters getUrlTemplateParameters(final String destination, final ProducerKey producerKey) { - return DmiServiceUrlTemplateBuilder.newInstance() + return RestServiceUrlTemplateBuilder.newInstance() .fixedPathSegment("cmwriteJob") .queryParameter("destination", destination) .createUrlTemplateParameters(producerKey.dmiServiceName(), dmiProperties.getDmiBasePath()); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java index c10132060d..a177272dff 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java @@ -34,6 +34,7 @@ import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.models.OperationType; import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException; import org.onap.cps.ncmp.impl.models.RequiredDmiService; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpHeaders; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java index 265d284239..4134a56ead 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiWebClientsConfiguration.java @@ -22,7 +22,7 @@ package org.onap.cps.ncmp.impl.dmi; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.config.DmiHttpClientConfig; -import org.onap.cps.ncmp.impl.utils.WebClientConfiguration; +import org.onap.cps.ncmp.impl.utils.http.WebClientConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java index 795d7c9c4c..95c3c77b71 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ package org.onap.cps.ncmp.impl.inventory; import java.util.Collection; -import java.util.List; import java.util.Map; import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.spi.FetchDescendantsOption; @@ -59,7 +58,7 @@ public interface CmHandleQueryService { * @param cmHandleState cm handle state * @return a list of data nodes representing the cm handles. */ - List<DataNode> queryCmHandlesByState(CmHandleState cmHandleState); + Collection<DataNode> queryCmHandlesByState(CmHandleState cmHandleState); /** * Method to return data nodes with ancestor representing the cm handles. @@ -67,8 +66,7 @@ public interface CmHandleQueryService { * @param cpsPath cps path for which the cmHandle is requested * @return a list of data nodes representing the cm handles. */ - List<DataNode> queryCmHandleAncestorsByCpsPath(String cpsPath, - FetchDescendantsOption fetchDescendantsOption); + Collection<DataNode> queryCmHandleAncestorsByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption); /** * Method to return data nodes representing the cm handles. @@ -76,7 +74,7 @@ public interface CmHandleQueryService { * @param cpsPath cps path for which the cmHandle is requested * @return a list of data nodes representing the cm handles. */ - List<DataNode> queryNcmpRegistryByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption); + Collection<DataNode> queryNcmpRegistryByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption); /** * Method to check the state of a cm handle with given id. @@ -93,7 +91,7 @@ public interface CmHandleQueryService { * @param dataStoreSyncState sync state * @return a list of data nodes representing the cm handles. */ - List<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState); + Collection<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState); /** * Get all cm handles ids by DMI plugin identifier. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java index e36477f8c8..f32008d482 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java @@ -30,16 +30,17 @@ import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsQueryService; +import org.onap.cps.cpspath.parser.CpsPathUtil; import org.onap.cps.ncmp.api.inventory.models.TrustLevel; import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.ncmp.impl.inventory.models.ModelledDmiServiceLeaves; import org.onap.cps.ncmp.impl.inventory.models.PropertyType; import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelCacheConfig; -import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.utils.CpsValidator; @@ -52,7 +53,8 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private static final String DESCENDANT_PATH = "//"; private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; - private final CpsDataPersistenceService cpsDataPersistenceService; + private final CpsDataService cpsDataService; + private final CpsQueryService cpsQueryService; @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN) private final Map<String, TrustLevel> trustLevelPerDmiPlugin; @@ -81,21 +83,24 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { } @Override - public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) { + public Collection<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) { return queryCmHandleAncestorsByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]", INCLUDE_ALL_DESCENDANTS); } @Override - public List<DataNode> queryNcmpRegistryByCpsPath(final String cpsPath, + public Collection<DataNode> queryNcmpRegistryByCpsPath(final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { - return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - cpsPath, fetchDescendantsOption); + return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath, + fetchDescendantsOption); } @Override - public List<DataNode> queryCmHandleAncestorsByCpsPath(final String cpsPath, + public Collection<DataNode> queryCmHandleAncestorsByCpsPath(final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { + if (CpsPathUtil.getCpsPathQuery(cpsPath).getXpathPrefix().endsWith("/cm-handles")) { + return queryNcmpRegistryByCpsPath(cpsPath, fetchDescendantsOption); + } return queryNcmpRegistryByCpsPath(cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption); } @@ -107,7 +112,7 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { } @Override - public List<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) { + public Collection<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) { return queryCmHandleAncestorsByCpsPath("//state/datastores" + "/operational[@sync-state=\"" + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS); } @@ -176,9 +181,9 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { return cmHandleIds; } - private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, - final String dmiProperty) { - return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + private Collection<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, + final String dmiProperty) { + return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@" + dmiProperty + "='" + dmiPluginIdentifier + "']", OMIT_DESCENDANTS); } @@ -186,7 +191,7 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private DataNode getCmHandleState(final String cmHandleId) { cpsValidator.validateNameCharacters(cmHandleId); final String xpath = NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']/state"; - return cpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, OMIT_DESCENDANTS).iterator().next(); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java index d6bda3beee..d9f7e38993 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java @@ -37,6 +37,7 @@ import com.hazelcast.map.IMap; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -58,13 +59,11 @@ import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.inventory.sync.ModuleOperationsUtils; import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler; -import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelCacheConfig; import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Slf4j @@ -79,11 +78,7 @@ public class CmHandleRegistrationService { private final CpsDataService cpsDataService; private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler; private final IMap<String, Object> moduleSyncStartedOnCmHandles; - - @Qualifier(TrustLevelCacheConfig.TRUST_LEVEL_PER_DMI_PLUGIN) - private final Map<String, TrustLevel> trustLevelPerDmiPlugin; private final TrustLevelManager trustLevelManager; - private final AlternateIdChecker alternateIdChecker; /** @@ -98,7 +93,7 @@ public class CmHandleRegistrationService { dmiPluginRegistration.validateDmiPluginRegistration(); final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); - setTrustLevelPerDmiPlugin(dmiPluginRegistration); + trustLevelManager.registerDmiPlugin(dmiPluginRegistration); processRemovedCmHandles(dmiPluginRegistration, dmiPluginRegistrationResponse); @@ -153,7 +148,7 @@ public class CmHandleRegistrationService { final Set<String> notDeletedCmHandles = new HashSet<>(); for (final List<String> tobeRemovedCmHandleBatch : Lists.partition(tobeRemovedCmHandleIds, DELETE_BATCH_SIZE)) { try { - batchDeleteCmHandlesFromDbAndModuleSyncMap(tobeRemovedCmHandleBatch); + batchDeleteCmHandlesFromDbAndCaches(tobeRemovedCmHandleBatch); tobeRemovedCmHandleBatch.forEach(cmHandleId -> cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId))); @@ -215,8 +210,7 @@ public class CmHandleRegistrationService { final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { final List<String> cmHandleIds = dmiPluginRegistration.getUpgradedCmHandles().getCmHandles(); - final String upgradedModuleSetTag = - StringUtils.trimToEmpty(dmiPluginRegistration.getUpgradedCmHandles().getModuleSetTag()); + final String upgradedModuleSetTag = dmiPluginRegistration.getUpgradedCmHandles().getModuleSetTag(); final Map<YangModelCmHandle, CmHandleState> acceptedCmHandleStatePerCmHandle = new HashMap<>(cmHandleIds.size()); final List<CmHandleRegistrationResponse> cmHandleUpgradeResponses = new ArrayList<>(cmHandleIds.size()); @@ -260,7 +254,7 @@ public class CmHandleRegistrationService { ncmpServiceCmHandle.getRegistrationTrustLevel()); } } - trustLevelManager.handleInitialRegistrationOfTrustLevels(initialTrustLevelPerCmHandleId); + trustLevelManager.registerCmHandles(initialTrustLevelPerCmHandleId); } private static boolean moduleUpgradeCanBeSkipped(final YangModelCmHandle yangModelCmHandle, @@ -281,7 +275,7 @@ public class CmHandleRegistrationService { private CmHandleRegistrationResponse deleteCmHandleAndGetCmHandleRegistrationResponse(final String cmHandleId) { try { - deleteCmHandleFromDbAndModuleSyncMap(cmHandleId); + deleteCmHandleFromDbAndCaches(cmHandleId); return CmHandleRegistrationResponse.createSuccessResponse(cmHandleId); } catch (final DataNodeNotFoundException dataNodeNotFoundException) { log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}", @@ -304,15 +298,17 @@ public class CmHandleRegistrationService { lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); } - private void deleteCmHandleFromDbAndModuleSyncMap(final String cmHandleId) { + private void deleteCmHandleFromDbAndCaches(final String cmHandleId) { inventoryPersistence.deleteSchemaSetWithCascade(cmHandleId); inventoryPersistence.deleteDataNode(NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']"); + trustLevelManager.removeCmHandles(Collections.singleton(cmHandleId)); removeDeletedCmHandleFromModuleSyncMap(cmHandleId); } - private void batchDeleteCmHandlesFromDbAndModuleSyncMap(final Collection<String> cmHandleIds) { + private void batchDeleteCmHandlesFromDbAndCaches(final Collection<String> cmHandleIds) { inventoryPersistence.deleteSchemaSetsWithCascade(cmHandleIds); inventoryPersistence.deleteDataNodes(mapCmHandleIdsToXpaths(cmHandleIds)); + trustLevelManager.removeCmHandles(cmHandleIds); cmHandleIds.forEach(this::removeDeletedCmHandleFromModuleSyncMap); } @@ -346,14 +342,6 @@ public class CmHandleRegistrationService { return cmHandleStatePerCmHandle.keySet().stream().map(YangModelCmHandle::getId).toList(); } - private void setTrustLevelPerDmiPlugin(final DmiPluginRegistration dmiPluginRegistration) { - if (DmiPluginRegistration.isNullEmptyOrBlank(dmiPluginRegistration.getDmiDataPlugin())) { - trustLevelPerDmiPlugin.put(dmiPluginRegistration.getDmiPlugin(), TrustLevel.COMPLETE); - } else { - trustLevelPerDmiPlugin.put(dmiPluginRegistration.getDmiDataPlugin(), TrustLevel.COMPLETE); - } - } - private Collection<String> checkAlternateIds( final List<NcmpServiceCmHandle> cmHandlesToBeCreated, final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java index e5848c0dfa..03ec30b73b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java @@ -22,7 +22,7 @@ package org.onap.cps.ncmp.impl.inventory; import java.util.Collection; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; public interface ParameterizedCmHandleQueryService { /** @@ -51,22 +51,14 @@ public interface ParameterizedCmHandleQueryService { Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); /** - * Query and return cm handle objects that match the given query parameters. + * Query and return yang model cm handle objects that match the given query parameters. * Supported query types: * public properties * modules * cps-path * * @param cmHandleQueryServiceParameters the cm handle query parameters - * @return collection of cm handles + * @return collection of yang model cm handles */ - Collection<NcmpServiceCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); - - /** - * Get all cm handle objects. - * Note: it is similar to all the queries above but simply no conditions and hence not 'parameterized' - * - * @return collection of cm handles - */ - Collection<NcmpServiceCmHandle> getAllCmHandles(); + Collection<YangModelCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java index 34eeaccf8f..84229e2895 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java @@ -27,7 +27,6 @@ import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.HA import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.HAS_ALL_PROPERTIES; import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.WITH_CPS_PATH; import static org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions.WITH_TRUST_LEVEL; -import static org.onap.cps.ncmp.impl.utils.YangDataConverter.toNcmpServiceCmHandle; import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY; import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; @@ -40,10 +39,8 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.PathParsingException; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions; import org.onap.cps.ncmp.impl.inventory.models.PropertyType; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @@ -54,7 +51,6 @@ import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; @Service -@Slf4j @RequiredArgsConstructor public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHandleQueryService { @@ -83,22 +79,18 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } @Override - public Collection<NcmpServiceCmHandle> queryCmHandles( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - + public Collection<YangModelCmHandle> queryCmHandles( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { return getAllCmHandles(); } - final Collection<String> cmHandleIds = queryCmHandleIds(cmHandleQueryServiceParameters); - - return getNcmpServiceCmHandles(cmHandleIds); + return inventoryPersistence.getYangModelCmHandles(cmHandleIds); } - @Override - public Collection<NcmpServiceCmHandle> getAllCmHandles() { + private Collection<YangModelCmHandle> getAllCmHandles() { final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT).iterator().next(); - return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet()); + return dataNode.getChildDataNodes().stream().map(YangDataConverter::toYangModelCmHandle).toList(); } private Collection<String> queryCmHandlesByDmiPlugin( @@ -226,22 +218,6 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return collectCmHandleIdsFromDataNodes(dataNode.getChildDataNodes()); } - private Collection<NcmpServiceCmHandle> getNcmpServiceCmHandles(final Collection<String> cmHandleIds) { - final Collection<YangModelCmHandle> yangModelcmHandles - = inventoryPersistence.getYangModelCmHandles(cmHandleIds); - - final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = new ArrayList<>(yangModelcmHandles.size()); - - yangModelcmHandles.forEach(yangModelcmHandle -> - ncmpServiceCmHandles.add(YangDataConverter.toNcmpServiceCmHandle(yangModelcmHandle)) - ); - return ncmpServiceCmHandles; - } - - private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) { - return toNcmpServiceCmHandle(YangDataConverter.toYangModelCmHandle(dataNode)); - } - private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Function<CmHandleQueryServiceParameters, Collection<String>>... queryFunctions) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java index 433c67f100..8ba70b3a31 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperations.java @@ -36,10 +36,10 @@ import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.inventory.models.YangResource; import org.onap.cps.ncmp.impl.dmi.DmiProperties; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; -import org.onap.cps.ncmp.impl.dmi.DmiServiceUrlTemplateBuilder; -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.models.DmiRequestBody; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; @@ -107,7 +107,7 @@ public class DmiModelOperations { final String jsonRequestBody, final String cmHandle, final String resourceName) { - final UrlTemplateParameters urlTemplateParameters = DmiServiceUrlTemplateBuilder.newInstance() + final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance() .fixedPathSegment("ch") .variablePathSegment("cmHandleId", cmHandle) .fixedPathSegment(resourceName) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java index 69fb7f59f5..97f1e8e70f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java @@ -26,6 +26,7 @@ import java.time.Duration; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -75,8 +76,8 @@ public class ModuleOperationsUtils { * * @return cm handles (data nodes) in ADVISED state (empty list if none found) */ - public List<DataNode> getAdvisedCmHandles() { - final List<DataNode> advisedCmHandlesAsDataNodes = + public Collection<DataNode> getAdvisedCmHandles() { + final Collection<DataNode> advisedCmHandlesAsDataNodes = cmHandleQueryService.queryCmHandlesByState(CmHandleState.ADVISED); log.debug("Total number of fetched advised cm handle(s) is (are) {}", advisedCmHandlesAsDataNodes.size()); return advisedCmHandlesAsDataNodes; @@ -90,7 +91,7 @@ public class ModuleOperationsUtils { * return empty list if not found */ public List<YangModelCmHandle> getUnsynchronizedReadyCmHandles() { - final List<DataNode> unsynchronizedCmHandles = cmHandleQueryService + final Collection<DataNode> unsynchronizedCmHandles = cmHandleQueryService .queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED); final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>(); @@ -110,8 +111,8 @@ public class ModuleOperationsUtils { * * @return a random LOCKED yang model cm handle, return null if not found */ - public List<YangModelCmHandle> getCmHandlesThatFailedModelSyncOrUpgrade() { - final List<DataNode> lockedCmHandlesAsDataNodeList + public Collection<YangModelCmHandle> getCmHandlesThatFailedModelSyncOrUpgrade() { + final Collection<DataNode> lockedCmHandlesAsDataNodeList = cmHandleQueryService.queryCmHandleAncestorsByCpsPath(CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); return convertCmHandlesDataNodesToYangModelCmHandles(lockedCmHandlesAsDataNodeList); @@ -236,8 +237,8 @@ public class ModuleOperationsUtils { return jsonObjectMapper.asJsonString(Map.of(firstElement.getKey(), firstElement.getValue())); } - private List<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles( - final List<DataNode> cmHandlesAsDataNodeList) { + private Collection<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles( + final Collection<DataNode> cmHandlesAsDataNodeList) { return cmHandlesAsDataNodeList.stream().map(YangDataConverter::toYangModelCmHandle).toList(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java index 39ea38aafc..8e5c9f3066 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java @@ -23,7 +23,6 @@ package org.onap.cps.ncmp.impl.inventory.sync; import com.hazelcast.map.IMap; import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; @@ -100,7 +99,7 @@ public class ModuleSyncTasks { * * @param failedCmHandles previously failed (locked) cm handles */ - public void resetFailedCmHandles(final List<YangModelCmHandle> failedCmHandles) { + public void resetFailedCmHandles(final Collection<YangModelCmHandle> failedCmHandles) { final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(failedCmHandles.size()); for (final YangModelCmHandle failedCmHandle : failedCmHandles) { final CompositeState compositeState = failedCmHandle.getCompositeState(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java index b4dde18da0..cc724e1f07 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,6 @@ package org.onap.cps.ncmp.impl.inventory.sync; import com.hazelcast.map.IMap; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -86,7 +85,7 @@ public class ModuleSyncWatchdog { @Scheduled(fixedDelayString = "${ncmp.timers.locked-modules-sync.sleep-time-ms:300000}") public void resetPreviouslyFailedCmHandles() { log.info("Processing module sync retry-watchdog waking up."); - final List<YangModelCmHandle> failedCmHandles + final Collection<YangModelCmHandle> failedCmHandles = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); log.info("Retrying {} cmHandles", failedCmHandles.size()); moduleSyncTasks.resetFailedCmHandles(failedCmHandles); @@ -103,7 +102,7 @@ public class ModuleSyncWatchdog { private void populateWorkQueueIfNeeded() { if (moduleSyncWorkQueue.isEmpty()) { - final List<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); + final Collection<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size()); for (final DataNode advisedCmHandle : advisedCmHandles) { if (!moduleSyncWorkQueue.offer(advisedCmHandle)) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumer.java index 617fe7f01d..efcbb78ace 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumer.java @@ -52,7 +52,7 @@ public class DeviceTrustLevelMessageConsumer { final DeviceTrustLevel deviceTrustLevel = CloudEventMapper.toTargetEvent(consumerRecord.value(), DeviceTrustLevel.class); final String trustLevelAsString = deviceTrustLevel.getData().getTrustLevel(); - trustLevelManager.handleUpdateOfDeviceTrustLevel(cmHandleId, TrustLevel.valueOf(trustLevelAsString)); + trustLevelManager.updateCmHandleTrustLevel(cmHandleId, TrustLevel.valueOf(trustLevelAsString)); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java index c81e9b7840..7581c4af7a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDog.java @@ -26,9 +26,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.TrustLevel; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; -import org.onap.cps.ncmp.impl.dmi.DmiServiceUrlTemplateBuilder; -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters; import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -67,13 +67,13 @@ public class DmiPluginTrustLevelWatchDog { } else { final Collection<String> cmHandleIds = cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiServiceName); - trustLevelManager.handleUpdateOfDmiTrustLevel(dmiServiceName, cmHandleIds, newDmiTrustLevel); + trustLevelManager.updateDmi(dmiServiceName, cmHandleIds, newDmiTrustLevel); } }); } private String getDmiHealthStatus(final String dmiServiceBaseUrl) { - final UrlTemplateParameters urlTemplateParameters = DmiServiceUrlTemplateBuilder.newInstance() + final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance() .createUrlTemplateParametersForHealthCheck(dmiServiceBaseUrl); return dmiRestClient.getDmiHealthStatus(urlTemplateParameters).block(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java index 44079c0bc9..aff0e19981 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.inventory.models.TrustLevel; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @@ -49,11 +50,26 @@ public class TrustLevelManager { private static final String AVC_NO_OLD_VALUE = null; /** + * Add dmi plugins to the cache. + * + * @param dmiPluginRegistration a dmi plugin being registered + */ + public void registerDmiPlugin(final DmiPluginRegistration dmiPluginRegistration) { + final String dmiServiceName; + if (DmiPluginRegistration.isNullEmptyOrBlank(dmiPluginRegistration.getDmiDataPlugin())) { + dmiServiceName = dmiPluginRegistration.getDmiPlugin(); + } else { + dmiServiceName = dmiPluginRegistration.getDmiDataPlugin(); + } + trustLevelPerDmiPlugin.put(dmiServiceName, TrustLevel.COMPLETE); + } + + /** * Add cmHandles to the cache and publish notification for initial trust level of cmHandles if it is NONE. * * @param cmHandlesToBeCreated a list of cmHandles being created */ - public void handleInitialRegistrationOfTrustLevels(final Map<String, TrustLevel> cmHandlesToBeCreated) { + public void registerCmHandles(final Map<String, TrustLevel> cmHandlesToBeCreated) { for (final Map.Entry<String, TrustLevel> entry : cmHandlesToBeCreated.entrySet()) { final String cmHandleId = entry.getKey(); if (trustLevelPerCmHandle.containsKey(cmHandleId)) { @@ -82,15 +98,15 @@ public class TrustLevelManager { * @param affectedCmHandleIds cm handle ids belonging to dmi service name * @param newDmiTrustLevel new trust level of the dmi plugin */ - public void handleUpdateOfDmiTrustLevel(final String dmiServiceName, - final Collection<String> affectedCmHandleIds, - final TrustLevel newDmiTrustLevel) { + public void updateDmi(final String dmiServiceName, + final Collection<String> affectedCmHandleIds, + final TrustLevel newDmiTrustLevel) { final TrustLevel oldDmiTrustLevel = trustLevelPerDmiPlugin.get(dmiServiceName); trustLevelPerDmiPlugin.put(dmiServiceName, newDmiTrustLevel); for (final String affectedCmHandleId : affectedCmHandleIds) { - final TrustLevel deviceTrustLevel = trustLevelPerCmHandle.get(affectedCmHandleId); - final TrustLevel oldEffectiveTrustLevel = deviceTrustLevel.getEffectiveTrustLevel(oldDmiTrustLevel); - final TrustLevel newEffectiveTrustLevel = deviceTrustLevel.getEffectiveTrustLevel(newDmiTrustLevel); + final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandle.get(affectedCmHandleId); + final TrustLevel oldEffectiveTrustLevel = cmHandleTrustLevel.getEffectiveTrustLevel(oldDmiTrustLevel); + final TrustLevel newEffectiveTrustLevel = cmHandleTrustLevel.getEffectiveTrustLevel(newDmiTrustLevel); sendAvcNotificationIfRequired(affectedCmHandleId, oldEffectiveTrustLevel, newEffectiveTrustLevel); } } @@ -100,23 +116,52 @@ public class TrustLevelManager { * changed. * * @param cmHandleId cm handle id - * @param newDeviceTrustLevel new trust level of the device + * @param newCmHandleTrustLevel new trust level of the device */ - public void handleUpdateOfDeviceTrustLevel(final String cmHandleId, - final TrustLevel newDeviceTrustLevel) { - final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); - final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA); + public void updateCmHandleTrustLevel(final String cmHandleId, + final TrustLevel newCmHandleTrustLevel) { + final String dmiServiceName = getDmiServiceName(cmHandleId); final TrustLevel dmiTrustLevel = trustLevelPerDmiPlugin.get(dmiServiceName); - final TrustLevel oldDeviceTrustLevel = trustLevelPerCmHandle.get(cmHandleId); + final TrustLevel oldCmHandleTrustLevel = trustLevelPerCmHandle.get(cmHandleId); - final TrustLevel oldEffectiveTrustLevel = oldDeviceTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); - final TrustLevel newEffectiveTrustLevel = newDeviceTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); + final TrustLevel oldEffectiveTrustLevel = oldCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); + final TrustLevel newEffectiveTrustLevel = newCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); - trustLevelPerCmHandle.put(cmHandleId, newDeviceTrustLevel); + trustLevelPerCmHandle.put(cmHandleId, newCmHandleTrustLevel); sendAvcNotificationIfRequired(cmHandleId, oldEffectiveTrustLevel, newEffectiveTrustLevel); } + /** + * Select effective trust level among device and dmi plugin. + * + * @param cmHandleId cm handle id + * @return TrustLevel effective trust level + */ + public TrustLevel getEffectiveTrustLevel(final String dmiServiceName, final String cmHandleId) { + final TrustLevel dmiTrustLevel = trustLevelPerDmiPlugin.get(dmiServiceName); + final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandle.get(cmHandleId); + return dmiTrustLevel.getEffectiveTrustLevel(cmHandleTrustLevel); + } + + /** + * Remove cm handle trust level from the cache. + * + * @param cmHandleIds cm handle ids to be removed from the cache + */ + public void removeCmHandles(final Collection<String> cmHandleIds) { + for (final String cmHandleId : cmHandleIds) { + if (trustLevelPerCmHandle.remove(cmHandleId) == null) { + log.debug("Removed Cm handle: {} is not in trust level cache", cmHandleId); + } + } + } + + private String getDmiServiceName(final String cmHandleId) { + final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); + return yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA); + } + private void sendAvcNotificationIfRequired(final String notificationCandidateCmHandleId, final TrustLevel oldEffectiveTrustLevel, final TrustLevel newEffectiveTrustLevel) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfiguration.java index a927764825..333030ccd2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/policyexecutor/PolicyExecutorWebClientConfiguration.java @@ -22,7 +22,7 @@ package org.onap.cps.ncmp.impl.policyexecutor; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig; -import org.onap.cps.ncmp.impl.utils.WebClientConfiguration; +import org.onap.cps.ncmp.impl.utils.http.WebClientConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java index c408ff9b13..c526dfb297 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java @@ -59,7 +59,7 @@ public class AlternateIdMatcher { /** * Get cm handle Id from given cmHandleReference. * - * @param cmHandleReference alternate ID + * @param cmHandleReference cm handle or alternate identifier * @return cm handle id string */ public String getCmHandleId(final String cmHandleReference) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiServiceUrlTemplateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java index e7dbea83f2..c850ca94a0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiServiceUrlTemplateBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.impl.dmi; +package org.onap.cps.ncmp.impl.utils.http; import java.util.Collections; import java.util.HashMap; @@ -30,7 +30,7 @@ import org.apache.logging.log4j.util.Strings; import org.springframework.web.util.UriComponentsBuilder; @NoArgsConstructor -public class DmiServiceUrlTemplateBuilder { +public class RestServiceUrlTemplateBuilder { private final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance(); private static final String FIXED_PATH_SEGMENT = null; @@ -43,8 +43,8 @@ public class DmiServiceUrlTemplateBuilder { * * @return a new instance of DmiServiceUrlTemplateBuilder */ - public static DmiServiceUrlTemplateBuilder newInstance() { - return new DmiServiceUrlTemplateBuilder(); + public static RestServiceUrlTemplateBuilder newInstance() { + return new RestServiceUrlTemplateBuilder(); } /** @@ -53,7 +53,7 @@ public class DmiServiceUrlTemplateBuilder { * @param pathSegment the path segment * @return this builder instance */ - public DmiServiceUrlTemplateBuilder fixedPathSegment(final String pathSegment) { + public RestServiceUrlTemplateBuilder fixedPathSegment(final String pathSegment) { pathSegments.put(pathSegment, FIXED_PATH_SEGMENT); return this; } @@ -66,7 +66,7 @@ public class DmiServiceUrlTemplateBuilder { * @param value the value to be insert in teh URL for the given variable path segment * @return this builder instance */ - public DmiServiceUrlTemplateBuilder variablePathSegment(final String pathSegment, final String value) { + public RestServiceUrlTemplateBuilder variablePathSegment(final String pathSegment, final String value) { pathSegments.put(pathSegment, value); return this; } @@ -80,8 +80,8 @@ public class DmiServiceUrlTemplateBuilder { * * @return this builder instance */ - public DmiServiceUrlTemplateBuilder queryParameter(final String queryParameterName, - final String queryParameterValue) { + public RestServiceUrlTemplateBuilder queryParameter(final String queryParameterName, + final String queryParameterValue) { if (Strings.isNotBlank(queryParameterValue)) { queryParameters.put(queryParameterName, queryParameterValue); } @@ -91,14 +91,12 @@ public class DmiServiceUrlTemplateBuilder { /** * Constructs a URL template with variables based on the accumulated path segments and query parameters. * - * @param dmiServiceBaseUrl the base URL of the DMI service, e.g., "http://dmi-service.com". - * @param dmiBasePath the base path of the DMI service + * @param serviceBaseUrl the base URL of the service, e.g., "http://dmi-service.com". + * @param basePath the base path of the service * @return a UrlTemplateParameters instance containing the complete URL template and URL variables */ - public UrlTemplateParameters createUrlTemplateParameters(final String dmiServiceBaseUrl, final String dmiBasePath) { - this.uriComponentsBuilder.pathSegment(dmiBasePath) - .pathSegment(VERSION_SEGMENT); - + public UrlTemplateParameters createUrlTemplateParameters(final String serviceBaseUrl, final String basePath) { + this.uriComponentsBuilder.pathSegment(basePath).pathSegment(VERSION_SEGMENT); final Map<String, String> urlTemplateVariables = new HashMap<>(); pathSegments.forEach((pathSegmentName, variablePathValue) -> { @@ -115,22 +113,21 @@ public class DmiServiceUrlTemplateBuilder { urlTemplateVariables.put(paramName, paramValue); }); - final String urlTemplate = dmiServiceBaseUrl + this.uriComponentsBuilder.build().toUriString(); + final String urlTemplate = serviceBaseUrl + this.uriComponentsBuilder.build().toUriString(); return new UrlTemplateParameters(urlTemplate, urlTemplateVariables); } /** - * Constructs a URL for DMI health check based on the given base URL. + * Constructs a URL for a spring actuator health check based on the given base URL. * - * @param dmiServiceBaseUrl the base URL of the DMI service, e.g., "http://dmi-service.com". + * @param serviceBaseUrl the base URL of the service, e.g., "http://dmi-service.com". * @return a {@link UrlTemplateParameters} instance containing the complete URL template and empty URL variables, * suitable for DMI health check. */ - public UrlTemplateParameters createUrlTemplateParametersForHealthCheck(final String dmiServiceBaseUrl) { - this.uriComponentsBuilder.pathSegment("actuator") - .pathSegment("health"); + public UrlTemplateParameters createUrlTemplateParametersForHealthCheck(final String serviceBaseUrl) { + this.uriComponentsBuilder.pathSegment("actuator").pathSegment("health"); - final String urlTemplate = dmiServiceBaseUrl + this.uriComponentsBuilder.build().toUriString(); + final String urlTemplate = serviceBaseUrl + this.uriComponentsBuilder.build().toUriString(); return new UrlTemplateParameters(urlTemplate, Collections.emptyMap()); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/UrlTemplateParameters.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/UrlTemplateParameters.java index f51511116a..839af71823 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/UrlTemplateParameters.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/UrlTemplateParameters.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.impl.dmi; +package org.onap.cps.ncmp.impl.utils.http; import java.util.Map; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/WebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java index 967f74e942..d8e8350345 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/WebClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.impl.utils; +package org.onap.cps.ncmp.impl.utils.http; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.channel.ChannelOption; diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy index e0ae204c8d..23f5edd890 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy @@ -29,7 +29,7 @@ import spock.lang.Specification @SpringBootTest @ContextConfiguration(classes = [DmiHttpClientConfig]) -@EnableConfigurationProperties(DmiHttpClientConfig) +@EnableConfigurationProperties class DmiHttpClientConfigSpec extends Specification { @Autowired diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy index 1946a452b1..ca71c345c1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy @@ -34,7 +34,7 @@ class PolicyExecutorHttpClientConfigSpec extends Specification { @Autowired PolicyExecutorHttpClientConfig policyExecutorHttpClientConfig - def 'Test http client configuration properties of data with custom and default values'() { + def 'Http client configuration properties for policy executor http client.'() { expect: 'properties are populated correctly for all services' with(policyExecutorHttpClientConfig.allServices) { assert maximumInMemorySizeInMegabytes == 31 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy index 2324d48a93..fd76abb581 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy @@ -31,9 +31,9 @@ import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor import org.onap.cps.ncmp.impl.dmi.DmiOperationsBaseSpec import org.onap.cps.ncmp.impl.dmi.DmiProperties -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -49,6 +49,8 @@ import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE +import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH import static org.onap.cps.ncmp.api.data.models.OperationType.READ import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA @@ -161,6 +163,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def 'Write data for pass-through:running datastore in DMI.'() { given: 'a cm handle for #cmHandleId' mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) + alternateIdMatcher.getCmHandleId(cmHandleId) >> cmHandleId and: 'a positive response from DMI service when it is called with the expected parameters' def expectedUrlTemplateParameters = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/data/ds/{datastore}?resourceIdentifier={resourceIdentifier}', ['resourceIdentifier': resourceIdentifier, 'datastore': 'ncmp-datastore:passthrough-running', 'cmHandleId': cmHandleId]) def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' @@ -176,6 +179,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { operation || expectedOperationInUrl CREATE || 'create' UPDATE || 'update' + DELETE || 'delete' + PATCH || 'patch' } def 'State Ready validation'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy deleted file mode 100644 index 4b09afa2ae..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy +++ /dev/null @@ -1,74 +0,0 @@ -package org.onap.cps.ncmp.impl.data - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender -import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import spock.lang.Specification - -import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH - -@SpringBootTest -@ContextConfiguration(classes = [PolicyExecutor]) -class PolicyExecutorSpec extends Specification { - - @Autowired - PolicyExecutor objectUnderTest - - def logAppender = Spy(ListAppender<ILoggingEvent>) - - def setup() { - setupLogger() - } - - def cleanup() { - ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders() - } - - def 'Configuration properties.'() { - expect: 'properties used from application.yml' - assert objectUnderTest.enabled - assert objectUnderTest.serverAddress == 'http://localhost' - assert objectUnderTest.serverPort == '8785' - } - - def 'Permission check logging.'() { - when: 'permission is checked for an operation' - def yangModelCmHandle = new YangModelCmHandle(id:'ch-1', alternateId:'fdn1') - objectUnderTest.checkPermission(yangModelCmHandle, PATCH, 'my credentials','my resource','my change') - then: 'correct details are logged ' - assert getLogEntry(0) == 'Policy Executor Enabled' - assert getLogEntry(3).contains('my credentials') - assert getLogEntry(4).contains('cm_patch') - assert getLogEntry(5).contains('fdn1') - assert getLogEntry(6).contains('ch-1') - assert getLogEntry(7).contains('my resource') - assert getLogEntry(8).contains('my change') - } - - def 'Permission check with feature disabled.'() { - given: 'feature is disabled' - objectUnderTest.enabled = false - when: 'permission is checked for an operation' - objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change') - then: 'nothing is logged' - assert logAppender.list.isEmpty() - } - - def setupLogger() { - def logger = LoggerFactory.getLogger(PolicyExecutor) - logger.setLevel(Level.DEBUG) - logger.addAppender(logAppender) - logAppender.start() - } - - def getLogEntry(index) { - logAppender.list[index].formattedMessage - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy new file mode 100644 index 0000000000..c859bb0a09 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy @@ -0,0 +1,45 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.impl.data.policyexecutor + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig +import org.onap.cps.ncmp.impl.policyexecutor.PolicyExecutorWebClientConfiguration +import org.onap.cps.ncmp.utils.WebClientBuilderTestConfig +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +@SpringBootTest +@ContextConfiguration(classes = [ObjectMapper, PolicyExecutor, PolicyExecutorWebClientConfiguration, PolicyExecutorHttpClientConfig, WebClientBuilderTestConfig ]) +class PolicyExecutorConfigurationSpec extends Specification { + + @Autowired + PolicyExecutor objectUnderTest + + def 'Policy executor configuration properties.'() { + expect: 'properties used from application.yml' + assert objectUnderTest.enabled + assert objectUnderTest.serverAddress == 'http://localhost' + assert objectUnderTest.serverPort == '8785' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy new file mode 100644 index 0000000000..63a915ab64 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy @@ -0,0 +1,154 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.impl.data.policyexecutor + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.exceptions.NcmpException +import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException +import org.onap.cps.ncmp.api.exceptions.ServerNcmpException +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.reactive.function.client.WebClient +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE +import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE + +class PolicyExecutorSpec extends Specification { + + def mockWebClient = Mock(WebClient) + def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec) + def mockResponseSpec = Mock(WebClient.ResponseSpec) + def spiedObjectMapper = Spy(ObjectMapper) + + PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient, spiedObjectMapper) + + def logAppender = Spy(ListAppender<ILoggingEvent>) + + def someValidJson = '{"Hello":"World"}' + + def setup() { + setupLogger() + objectUnderTest.enabled = true + mockWebClient.post() >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.header(*_) >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.body(*_) >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.retrieve() >> mockResponseSpec + } + + def cleanup() { + ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders() + } + + def 'Permission check with allow response.'() { + given: 'allow response' + mockResponse([decision:'allow'], HttpStatus.OK) + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson) + then: 'system logs the operation is allowed' + assert getLogEntry(2) == 'Policy Executor allows the operation' + and: 'no exception occurs' + noExceptionThrown() + where: 'all write operations are tested' + operationType << [ CREATE, DELETE, PATCH, UPDATE ] + } + + def 'Permission check with other response (not allowed).'() { + given: 'other response' + mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK) + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson) + then: 'Policy Executor exception is thrown' + def thrownException = thrown(PolicyExecutorException) + assert thrownException.message == 'Policy Executor did not allow request. Decision #123 : other' + assert thrownException.details == 'I dont like Mondays' + } + + def 'Permission check with non 2xx response.'() { + given: 'other response' + mockResponse([], HttpStatus.I_AM_A_TEAPOT) + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson) + then: 'Server Ncmp exception is thrown' + def thrownException = thrown(ServerNcmpException) + assert thrownException.message == 'Policy Executor invocation failed' + assert thrownException.details == 'HTTP status code: 418' + } + + def 'Permission check with invalid response from Policy Executor.'() { + given: 'invalid response from Policy executor' + mockResponseSpec.toEntity(*_) >> invalidResponse + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials','my resource',someValidJson) + then: 'system logs the expected message' + assert getLogEntry(1) == expectedMessage + where: 'following invalid responses are received' + invalidResponse || expectedMessage + Mono.empty() || 'No valid response from policy, ignored' + Mono.just(new ResponseEntity<>(null, HttpStatus.OK)) || 'No valid response body from policy, ignored' + } + + def 'Permission check with an invalid change request json.'() { + when: 'permission is checked for an invalid change request' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', 'invalid json string') + then: 'an ncmp exception thrown' + def ncmpException = thrown(NcmpException) + ncmpException.message == 'Cannot convert Change Request data to Object' + ncmpException.details.contains('invalid json string') + } + + def 'Permission check feature disabled.'() { + given: 'feature is disabled' + objectUnderTest.enabled = false + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson) + then: 'system logs that the feature not enabled' + assert getLogEntry(0) == 'Policy Executor Enabled: false' + } + + def mockResponse(mockResponseAsMap, httpStatus) { + JsonNode jsonNode = spiedObjectMapper.readTree(spiedObjectMapper.writeValueAsString(mockResponseAsMap)) + def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus)) + mockResponseSpec.toEntity(*_) >> mono + } + + def setupLogger() { + def logger = LoggerFactory.getLogger(PolicyExecutor) + logger.setLevel(Level.TRACE) + logger.addAppender(logAppender) + logAppender.start() + } + + def getLogEntry(index) { + logAppender.list[index].formattedMessage + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy index 3af474040e..74bd0484df 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy @@ -22,7 +22,7 @@ package org.onap.cps.ncmp.impl.datajobs import org.onap.cps.ncmp.impl.dmi.DmiProperties import org.onap.cps.ncmp.impl.dmi.DmiRestClient -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters import reactor.core.publisher.Mono import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy index d231dfa755..be46d885e0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy @@ -22,7 +22,7 @@ package org.onap.cps.ncmp.impl.datajobs import org.onap.cps.ncmp.impl.dmi.DmiProperties import org.onap.cps.ncmp.impl.dmi.DmiRestClient -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters import reactor.core.publisher.Mono import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy index affbf2aa4f..65fda8718c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiOperationsBaseSpec.groovy @@ -46,6 +46,7 @@ abstract class DmiOperationsBaseSpec extends Specification { def yangModelCmHandle = new YangModelCmHandle() def static dmiServiceName = 'myServiceName' def static cmHandleId = 'some-cm-handle' + def static alternateId = 'alt-id-' + cmHandleId def static resourceIdentifier = 'parent/child' def mockYangModelCmHandleRetrieval(dmiProperties) { @@ -68,6 +69,7 @@ abstract class DmiOperationsBaseSpec extends Specification { yangModelCmHandle.dmiServiceName = dmiServiceName yangModelCmHandle.dmiProperties = dmiProperties yangModelCmHandle.id = cmHandleId + yangModelCmHandle.alternateId = alternateId yangModelCmHandle.compositeState = new CompositeState() yangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY yangModelCmHandle.moduleSetTag = moduleSetTag diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy index d92e69a136..4d47ef14a0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpHeaders diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index 36fd755774..cb3c4ffec1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,10 @@ package org.onap.cps.ncmp.impl.inventory +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsQueryService import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.inventory.models.CmHandleState -import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.utils.CpsValidator import spock.lang.Shared @@ -37,7 +38,8 @@ import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS class CmHandleQueryServiceImplSpec extends Specification { - def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) + def mockCpsQueryService = Mock(CpsQueryService) + def mockCpsDataService = Mock(CpsDataService) def trustLevelPerDmiPlugin = [:] @@ -45,7 +47,7 @@ class CmHandleQueryServiceImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) - def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) + def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataService, mockCpsQueryService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) @Shared def static sampleDataNodes = [new DataNode()] @@ -104,7 +106,7 @@ class CmHandleQueryServiceImplSpec extends Specification { def 'Query CmHandles by a private field\'s value.'() { given: 'a data node exists with a certain additional-property' - mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] + mockCpsQueryService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] when: 'a query on CmHandle private properties is executed using a map' def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) then: 'one cm handle is returned' @@ -115,7 +117,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a cm handle state to query' def cmHandleState = CmHandleState.ADVISED and: 'the persistence service returns a list of data nodes' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes when: 'cm handles are fetched by state' def result = objectUnderTest.queryCmHandlesByState(cmHandleState) @@ -127,7 +129,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a cm handle state to compare' def cmHandleState = state and: 'the persistence service returns a list of data nodes' - mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] when: 'cm handles are compared by state' @@ -144,7 +146,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a cm handle state to query' def cmHandleState = CmHandleState.READY and: 'cps data service returns a list of data nodes' - mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] when: 'cm handles are fetched by state and id' @@ -157,7 +159,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a cm handle state to query' def cmHandleState = CmHandleState.READY and: 'cps data service returns a list of data nodes' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state' def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) @@ -167,10 +169,10 @@ class CmHandleQueryServiceImplSpec extends Specification { def 'Retrieve cm handle by cps path '() { given: 'a cm handle state to query based on the cps path' - def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED']) - def cpsPath = '//cps-path' - and: 'cps data service returns a valid data node' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + def cmHandleDataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1']) + def cpsPath = "//state[@cm-handle-state='LOCKED']" + and: 'cps data service returns a valid data node for cm handle ancestor' + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> Arrays.asList(cmHandleDataNode) when: 'get cm handles by cps path is invoked' @@ -179,6 +181,20 @@ class CmHandleQueryServiceImplSpec extends Specification { assert result.contains(cmHandleDataNode) } + def 'Retrieve cm handle by cps path querying cm handle directly'() { + given: 'a cm handle to query based on the cps path' + def cmHandleDataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-2']", leaves: ['id': 'ch-2']) + def cpsPath = "//cm-handles[@alternate-id='1']" + and: 'cps data service returns a valid data node' + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cpsPath, INCLUDE_ALL_DESCENDANTS) + >> Arrays.asList(cmHandleDataNode) + when: 'get cm handles by cps path is invoked' + def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result.contains(cmHandleDataNode) + } + def 'Get all cm handles by dmi plugin identifier'() { given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' mockResponses() @@ -191,15 +207,15 @@ class CmHandleQueryServiceImplSpec extends Specification { } void mockResponses() { - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> [] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4] + mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] + mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] + mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4] + mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> [] + mockCpsQueryService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3] + mockCpsQueryService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4] + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2] + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4] + mockCpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4] } def static createDataNode(dataNodeId) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index 0c702abea6..dcff2e9b89 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -59,13 +59,12 @@ class CmHandleRegistrationServiceSpec extends Specification { def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) def mockCpsDataService = Mock(CpsDataService) def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>) - def trustLevelPerDmiPlugin = [:] def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdChecker = Mock(AlternateIdChecker) def objectUnderTest = Spy(new CmHandleRegistrationService( mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, - mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin , mockTrustLevelManager, mockAlternateIdChecker)) + mockModuleSyncStartedOnCmHandles, mockTrustLevelManager, mockAlternateIdChecker)) def setup() { // always accept all cm handles @@ -143,9 +142,6 @@ class CmHandleRegistrationServiceSpec extends Specification { objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) then: 'create cm handles registration and sync modules is called with the correct plugin information' 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) - and: 'dmi is added to the dmi trustLevel map' - assert trustLevelPerDmiPlugin.size() == 1 - assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName) where: scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedDmiPluginRegisteredName 'combined DMI plugin' | 'service1' | '' | '' || 'service1' @@ -212,7 +208,7 @@ class CmHandleRegistrationServiceSpec extends Specification { when: 'registration is updated' objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) then: 'trustLevel is set for the created cm-handle' - 1 * mockTrustLevelManager.handleInitialRegistrationOfTrustLevels(expectedMapping) + 1 * mockTrustLevelManager.registerCmHandles(expectedMapping) where: scenario | registrationTrustLevel || expectedMapping 'with trusted cm handle' | TrustLevel.COMPLETE || [ 'ch-1' : TrustLevel.COMPLETE ] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy index 716efd8fdb..9e07de48bf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -35,6 +35,8 @@ import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher import org.onap.cps.spi.model.ConditionProperties import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification @@ -46,9 +48,9 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService) def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) def mockInventoryPersistence = Mock(InventoryPersistence) - def trustLevelPerCmHandle = [:] - - def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, trustLevelPerCmHandle) + def mockTrustLevelManager = Mock(TrustLevelManager) + def mockAlternateIdMatcher = Mock(AlternateIdMatcher) + def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, mockTrustLevelManager, mockAlternateIdMatcher) def 'Update DMI Registration'() { given: 'an (updated) dmi plugin registration' @@ -87,14 +89,20 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { assert result.containsAll('cm-handle-1','cm-handle-2') } - def 'Getting Yang Resources.'() { + def 'Getting Yang Resources for a given #scenario'() { when: 'yang resources is called' - objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') - then: 'CPS module services is invoked for the correct dataspace and cm handle' + objectUnderTest.getYangResourcesModuleReferences(cmHandleRef) + then: 'alternate id matcher is called' + mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle' + and: 'CPS module services is invoked for the correct cm handle' 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') + where: 'following cm handle reference is used' + scenario | cmHandleRef + 'Cm Handle Reference as cm handle-id' | 'some-cm-handle' + 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Get a cm handle.'() { + def 'Get a cm handle details using #scenario'() { given: 'the system returns a yang modelled cm handle' def dmiServiceName = 'some service name' def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, @@ -106,17 +114,18 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] def moduleSetTag = 'some-module-set-tag' def alternateId = 'some-alternate-id' - def yangModelCmHandle = new YangModelCmHandle(id: 'ch-1', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties, + def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, moduleSetTag: moduleSetTag, alternateId: alternateId) - 1 * mockInventoryPersistence.getYangModelCmHandle('ch-1') >> yangModelCmHandle + mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle and: 'a trust level for the cm handle in the cache' - trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) + mockTrustLevelManager.getEffectiveTrustLevel(*_) >> TrustLevel.COMPLETE when: 'getting cm handle details for a given cm handle id from ncmp service' - def result = objectUnderTest.getNcmpServiceCmHandle('ch-1') + def result = objectUnderTest.getNcmpServiceCmHandle(cmHandleRef) then: 'the result is a ncmpServiceCmHandle' assert result.class == NcmpServiceCmHandle.class and: 'the cm handle contains the cm handle id' - assert result.cmHandleId == 'ch-1' + assert result.cmHandleId == 'some-cm-handle' and: 'the cm handle contains the alternate id' assert result.alternateId == 'some-alternate-id' and: 'the cm handle contains the module-set-tag' @@ -129,22 +138,34 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { assert result.compositeState == compositeState and: 'the cm handle contains the trust level from the cache' assert result.currentTrustLevel == TrustLevel.COMPLETE + where: 'following cm handle reference is used' + scenario | cmHandleRef + 'Cm Handle Reference as cm handle-id' | 'some-cm-handle' + 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Get cm handle public properties'() { + def 'Get cm handle public properties using #scenario'() { given: 'a yang modelled cm handle' def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) + def cmHandleId = 'some-cm-handle' + def alternateId = 'some-alternate-id' + def yangModelCmHandle = new YangModelCmHandle(id:cmHandleId, alternateId: alternateId, dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) + and: 'we have corresponding cm handle for the cm handle reference' + 1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> cmHandleId and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle public properties for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') + 1 * mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle + when: 'getting cm handle public properties for a given cm handle reference from ncmp service' + def result = objectUnderTest.getCmHandlePublicProperties(cmHandleRef) then: 'the result returns the correct data' assert result == [ 'public prop' : 'some public prop' ] + where: 'following cm handle reference is used' + scenario | cmHandleRef + 'Cm Handle Reference as cm handle-id' | 'some-cm-handle' + 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Get cm handle composite state'() { + def 'Get cm handle composite state using #scenario'() { given: 'a yang modelled cm handle' def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), @@ -153,13 +174,21 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { dataStores: dataStores()) def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + def cmHandleId = 'some-cm-handle' + def alternateId = 'some-alternate-id' + def yangModelCmHandle = new YangModelCmHandle(id:cmHandleId, alternateId: alternateId, dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + and: 'we have corresponding cm handle for the cm handle reference' + 1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> cmHandleId and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + 1 * mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle when: 'getting cm handle composite state for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') + def result = objectUnderTest.getCmHandleCompositeState(cmHandleRef) then: 'the result returns the correct data' assert result == compositeState + where: 'following cm handle reference is used' + scenario | cmHandleRef + 'Cm Handle Reference as cm handle-id' | 'some-cm-handle' + 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } def 'Execute cm handle id search'() { @@ -179,18 +208,30 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { assert result == ['cm-handle-id-1'] } - def 'Getting module definitions by module'() { - when: 'get module definitions is performed with module name' - objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - then: 'ncmp inventory persistence service is invoked once with correct parameters' + def 'Getting module definitions by module for a given #scenario'() { + when: 'get module definitions is performed with module name and cm handle reference' + objectUnderTest.getModuleDefinitionsByCmHandleAndModule(cmHandleRef, 'some-module', '2021-08-04') + then: 'alternate id matcher returns some cm handle id for a given cm handle reference' + mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle' + and: 'ncmp inventory persistence service is invoked once with correct parameters' 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + where: 'following cm handle reference is used' + scenario | cmHandleRef + 'Cm Handle Reference as cm handle-id' | 'some-cm-handle' + 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Getting module definitions by cm handle id'() { - when: 'get module definitions is performed with cm handle id' - objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') + def 'Getting module definitions for a given #scenario'() { + when: 'get module definitions is performed with cm handle reference' + objectUnderTest.getModuleDefinitionsByCmHandleReference(cmHandleRef) + then: 'alternate id matcher returns some cm handle id for a given cm handle reference' + mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle' then: 'ncmp inventory persistence service is invoked once with correct parameter' 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') + where: 'following cm handle reference is used' + scenario | cmHandleRef + 'Cm Handle Reference as cm handle-id' | 'some-cm-handle' + 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } def 'Execute cm handle search'() { @@ -203,17 +244,18 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { and: 'query cm handle method returns two cm handles' mockParameterizedCmHandleQueryService.queryCmHandles( spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> [new NcmpServiceCmHandle(cmHandleId: 'ch-0'), new NcmpServiceCmHandle(cmHandleId: 'ch-1')] - and: ' a trust level for ch-1' - trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) + >> [new YangModelCmHandle(id: 'ch-0', dmiProperties: [], publicProperties: []), + new YangModelCmHandle(id: 'ch-1', dmiProperties: [], publicProperties: [])] + and: 'a trust level for cm handles' + mockTrustLevelManager.getEffectiveTrustLevel(*_) >> TrustLevel.COMPLETE when: 'execute cm handle search is called' def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) then: 'result consists of the two cm handles returned by the CPS Data Service' assert result.size() == 2 assert result[0].cmHandleId == 'ch-0' assert result[1].cmHandleId == 'ch-1' - and: 'only ch-1 has a trust level' - assert result[0].currentTrustLevel == null + and: 'cm handles have trust level' + assert result[0].currentTrustLevel == TrustLevel.COMPLETE assert result[1].currentTrustLevel == TrustLevel.COMPLETE } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 013bace04d..08644202c6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ package org.onap.cps.ncmp.impl.inventory import org.onap.cps.cpspath.parser.PathParsingException import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataInUseException @@ -139,10 +138,10 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { and: 'the inventory service is called with teh correct if and returns a yang model cm handle' 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] - and: 'the expected cm handle(s) are returned as NCMP Service cm handles' - assert result[0] instanceof NcmpServiceCmHandle + and: 'the expected cm handle(s) are returned as Yang Model cm handles' + assert result[0] instanceof YangModelCmHandle assert result.size() == 1 - assert result[0].dmiProperties == [name:'value'] + assert result[0].dmiProperties.size() == 1 } def 'Query cm handle ids when the query is empty.'() { @@ -165,7 +164,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the correct cm handles are returned' assert result.size() == 4 - assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + assert result.id.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') } def 'Query CMHandleId with #scenario.' () { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy index 196a1cd360..c80aa7b242 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy @@ -25,7 +25,7 @@ import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.impl.dmi.DmiOperationsBaseSpec import org.onap.cps.ncmp.impl.dmi.DmiProperties -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy index 6db304acd1..c7d0616bb2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DeviceTrustLevelMessageConsumerSpec.groovy @@ -49,7 +49,7 @@ class DeviceTrustLevelMessageConsumerSpec extends Specification { when: 'the event is consumed' objectUnderTest.deviceTrustLevelListener(consumerRecord) then: 'cm handles are stored with correct trust level' - 1 * mockTrustLevelManager.handleUpdateOfDeviceTrustLevel('"ch-1"', TrustLevel.COMPLETE) + 1 * mockTrustLevelManager.updateCmHandleTrustLevel('"ch-1"', TrustLevel.COMPLETE) } def createTrustLevelEvent(eventPayload) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy index 0a34d267c5..32f4503005 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy @@ -22,8 +22,8 @@ package org.onap.cps.ncmp.impl.inventory.trustlevel import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.dmi.DmiRestClient -import org.onap.cps.ncmp.impl.dmi.UrlTemplateParameters import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters import reactor.core.publisher.Mono import spock.lang.Specification @@ -46,7 +46,7 @@ class DmiPluginTrustLevelWatchDogSpec extends Specification { when: 'dmi watch dog method runs' objectUnderTest.checkDmiAvailability() then: 'the update delegated to manager' - numberOfCalls * mockTrustLevelManager.handleUpdateOfDmiTrustLevel('dmi-1', _, newDmiTrustLevel) + numberOfCalls * mockTrustLevelManager.updateDmi('dmi-1', _, newDmiTrustLevel) where: 'the following parameters are used' dmiHealhStatus | dmiOldTrustLevel | newDmiTrustLevel || numberOfCalls 'UP' | TrustLevel.COMPLETE | TrustLevel.COMPLETE || 0 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy index b5bfbc165c..7dc9602e46 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.impl.inventory.trustlevel +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle @@ -35,11 +36,20 @@ class TrustLevelManagerSpec extends Specification { def mockAttributeValueChangeEventPublisher = Mock(CmAvcEventPublisher) def objectUnderTest = new TrustLevelManager(trustLevelPerCmHandle, trustLevelPerDmiPlugin, mockInventoryPersistence, mockAttributeValueChangeEventPublisher) + def 'Initial dmi registration'() { + given: 'a dmi plugin' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'dmi-1') + when: 'method to register to the cache is called' + objectUnderTest.registerDmiPlugin(dmiPluginRegistration) + then: 'dmi plugin in the cache and trusted' + assert trustLevelPerDmiPlugin.get('dmi-1') == TrustLevel.COMPLETE + } + def 'Initial cm handle registration'() { given: 'two cm handles: one with no trust level and one trusted' def cmHandleModelsToBeCreated = ['ch-1': null, 'ch-2': TrustLevel.COMPLETE] - when: 'the initial registration handled' - objectUnderTest.handleInitialRegistrationOfTrustLevels(cmHandleModelsToBeCreated) + when: 'method to register to the cache is called' + objectUnderTest.registerCmHandles(cmHandleModelsToBeCreated) then: 'no notification sent' 0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) and: 'both cm handles are in the cache and are trusted' @@ -50,8 +60,8 @@ class TrustLevelManagerSpec extends Specification { def 'Initial cm handle registration with a cm handle that is not trusted'() { given: 'a not trusted cm handle' def cmHandleModelsToBeCreated = ['ch-2': TrustLevel.NONE] - when: 'the initial registration handled' - objectUnderTest.handleInitialRegistrationOfTrustLevels(cmHandleModelsToBeCreated) + when: 'method to register to the cache is called' + objectUnderTest.registerCmHandles(cmHandleModelsToBeCreated) then: 'notification is sent' 1 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) } @@ -62,7 +72,7 @@ class TrustLevelManagerSpec extends Specification { and: 'a trusted cm handle' trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) when: 'the update is handled' - objectUnderTest.handleUpdateOfDmiTrustLevel('my-dmi', ['ch-1'], TrustLevel.NONE) + objectUnderTest.updateDmi('my-dmi', ['ch-1'], TrustLevel.NONE) then: 'notification is sent' 1 * mockAttributeValueChangeEventPublisher.publishAvcEvent('ch-1', 'trustLevel', 'COMPLETE', 'NONE') and: 'the dmi in the cache is not trusted' @@ -75,54 +85,74 @@ class TrustLevelManagerSpec extends Specification { and: 'a trusted cm handle' trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) when: 'the update is handled' - objectUnderTest.handleUpdateOfDmiTrustLevel('my-dmi', ['ch-1'], TrustLevel.COMPLETE) + objectUnderTest.updateDmi('my-dmi', ['ch-1'], TrustLevel.COMPLETE) then: 'no notification is sent' 0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) and: 'the dmi in the cache is trusted' assert trustLevelPerDmiPlugin.get('my-dmi') == TrustLevel.COMPLETE } - def 'Device trust level updated'() { + def 'CmHandle trust level updated'() { given: 'a non trusted cm handle' trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE) and: 'a trusted dmi plugin' trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.COMPLETE) and: 'inventory persistence service returns yang model cm handle' mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(id: 'ch-1', dmiDataServiceName: 'my-dmi') - when: 'update of device to COMPLETE trust level handled' - objectUnderTest.handleUpdateOfDeviceTrustLevel('ch-1', TrustLevel.COMPLETE) + when: 'update of CmHandle to COMPLETE trust level handled' + objectUnderTest.updateCmHandleTrustLevel('ch-1', TrustLevel.COMPLETE) then: 'the cm handle in the cache is trusted' assert trustLevelPerCmHandle.get('ch-1', TrustLevel.COMPLETE) and: 'notification is sent' 1 * mockAttributeValueChangeEventPublisher.publishAvcEvent('ch-1', 'trustLevel', 'NONE', 'COMPLETE') } - def 'Device trust level updated with same value'() { + def 'CmHandle trust level updated with same value'() { given: 'a non trusted cm handle' trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE) and: 'a trusted dmi plugin' trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.COMPLETE) and: 'inventory persistence service returns yang model cm handle' mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(id: 'ch-1', dmiDataServiceName: 'my-dmi') - when: 'update of device trust to the same level (NONE)' - objectUnderTest.handleUpdateOfDeviceTrustLevel('ch-1', TrustLevel.NONE) + when: 'update of CmHandle trust to the same level (NONE)' + objectUnderTest.updateCmHandleTrustLevel('ch-1', TrustLevel.NONE) then: 'the cm handle in the cache is not trusted' assert trustLevelPerCmHandle.get('ch-1', TrustLevel.NONE) and: 'no notification is sent' 0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) } - def 'Dmi trust level restored to complete with non trusted device'() { + def 'Dmi trust level restored to complete with non trusted CmHandle'() { given: 'a non trusted dmi' trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.NONE) - and: 'a non trusted device' + and: 'a non trusted CmHandle' trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE) when: 'restore the dmi trust level to COMPLETE' - objectUnderTest.handleUpdateOfDmiTrustLevel('my-dmi', ['ch-1'], TrustLevel.COMPLETE) + objectUnderTest.updateDmi('my-dmi', ['ch-1'], TrustLevel.COMPLETE) then: 'the cm handle in the cache is still NONE' assert trustLevelPerCmHandle.get('ch-1') == TrustLevel.NONE and: 'no notification is sent' 0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) } + def 'Select effective trust level among CmHandle and dmi plugin'() { + given: 'a non trusted dmi' + trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.NONE) + and: 'a trusted CmHandle' + trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) + when: 'effective trust level selected' + def effectiveTrustLevel = objectUnderTest.getEffectiveTrustLevel('my-dmi', 'ch-1') + then: 'effective trust level is trusted' + assert effectiveTrustLevel == TrustLevel.NONE + } + + def 'CmHandle trust level removed'() { + given: 'a cm handle' + trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) + when: 'the remove is handled' + objectUnderTest.removeCmHandles(['ch-1']) + then: 'cm handle removed from the cache' + assert trustLevelPerCmHandle.get('ch-1') == null + } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiServiceUrlTemplateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilderSpec.groovy index 9e1b37023b..9e051156f7 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiServiceUrlTemplateBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilderSpec.groovy @@ -18,14 +18,13 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.impl.dmi - +package org.onap.cps.ncmp.impl.utils.http import spock.lang.Specification -class DmiServiceUrlTemplateBuilderSpec extends Specification { +class RestServiceUrlTemplateBuilderSpec extends Specification { - def objectUnderTest = new DmiServiceUrlTemplateBuilder() + def objectUnderTest = new RestServiceUrlTemplateBuilder() def 'Build URL template parameters with (variable) path segments and query parameters.'() { given: 'the URL details are given to the builder' @@ -35,9 +34,9 @@ class DmiServiceUrlTemplateBuilderSpec extends Specification { objectUnderTest.queryParameter('param1', 'abc') objectUnderTest.queryParameter('param2', 'value?with#special:characters') when: 'the URL template parameters are created' - def result = objectUnderTest.createUrlTemplateParameters('myDmiServer', 'myBasePath') + def result = objectUnderTest.createUrlTemplateParameters('myServer', 'myBasePath') then: 'the URL template contains variable names instead of value and un-encoded fixed segment' - assert result.urlTemplate == 'myDmiServer/myBasePath/v1/segment/{myVariableSegment}/segment?with:special&characters?param1={param1}¶m2={param2}' + assert result.urlTemplate == 'myServer/myBasePath/v1/segment/{myVariableSegment}/segment?with:special&characters?param1={param1}¶m2={param2}' and: 'URL variables contains name and un-encoded value pairs' assert result.urlVariables == ['myVariableSegment': 'someValue', 'param1': 'abc', 'param2': 'value?with#special:characters'] } @@ -46,7 +45,7 @@ class DmiServiceUrlTemplateBuilderSpec extends Specification { given: 'the query parameter is given to the builder' objectUnderTest.queryParameter('my¶m', 'special&characters=are?not\\encoded') when: 'the URL template parameters are created' - def result = objectUnderTest.createUrlTemplateParameters('myDmiServer', 'myBasePath') + def result = objectUnderTest.createUrlTemplateParameters('myServer', 'myBasePath') then: 'Special characters are not encoded' assert result.urlVariables == ['my¶m': 'special&characters=are?not\\encoded'] } @@ -55,7 +54,7 @@ class DmiServiceUrlTemplateBuilderSpec extends Specification { when: 'the query parameter is given to the builder' objectUnderTest.queryParameter('param', value) and: 'the URL template parameters are create' - def result = objectUnderTest.createUrlTemplateParameters('myDmiServer', 'myBasePath') + def result = objectUnderTest.createUrlTemplateParameters('myServer', 'myBasePath') then: 'no parameter gets added' assert result.urlVariables.isEmpty() where: 'the following parameter values are used' diff --git a/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java new file mode 100644 index 0000000000..2f6b270076 --- /dev/null +++ b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java @@ -0,0 +1,40 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.utils; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.client.WebClient; + +@TestConfiguration +public class WebClientBuilderTestConfig { + + /** + * Configures and creates a web client builder bean to make it accessible for the Spring Boot Test Context. + * + * @return a WebClient Builder instance. + */ + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + +} diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index 72d074ab5f..c76831da74 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -84,8 +84,8 @@ ncmp: policy-executor: enabled: true server: - address: "http://localhost" - port: "8785" + address: http://localhost + port: 8785 httpclient: all-services: maximumInMemorySizeInMegabytes: 31 diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index a4aeb5b5a5..430f4b5cd8 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -410,30 +410,6 @@ <artifactId>sonar-maven-plugin</artifactId> <version>3.9.1.2184</version> </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>1.6.0</version> - <executions> - <execution> - <id>generate-csv</id> - <phase>prepare-package</phase> - <goals> - <goal>exec</goal> - </goals> - </execution> - </executions> - <configuration> - <executable>${script.executor}</executable> - <workingDirectory>${parent.directory}/cps-ri/src/main/resources/</workingDirectory> - <arguments> - <argument>yangResourceCsvGenerator.py</argument> - <argument>dmi-registry@2021-12-13</argument> - <argument>dmi-registry@2022-02-10</argument> - <argument>dmi-registry@2022-05-10</argument> - </arguments> - </configuration> - </plugin> </plugins> </build> </project> diff --git a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 index 3aef120fed..be8d968fcb 100644 --- a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 +++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2023 TechMahindra Ltd * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ grammar CpsPath ; -cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? invalidPostFix?; +cpsPath : ( prefix | descendant ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? EOF ; ancestorAxis : SLASH KW_ANCESTOR COLONCOLON ancestorPath ; @@ -43,8 +43,6 @@ prefix : parent SLASH containerName ; descendant : SLASH prefix ; -incorrectPrefix : SLASH SLASH SLASH+ ; - yangElement : containerName listElementRef? ; containerName : QName ; @@ -61,8 +59,6 @@ booleanOperators : ( KW_AND | KW_OR ) ; comparativeOperators : ( EQ | GT | LT | GE | LE ) ; -invalidPostFix : (AT | CB | COLONCOLON | comparativeOperators ).+ ; - /* * Lexer Rules * Most of the lexer rules below are inspired by diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java index 0bb09235ff..ac535f5efc 100644 --- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java +++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2023 TechMahindra Ltd * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener; import org.onap.cps.cpspath.parser.antlr4.CpsPathParser; import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext; import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.DescendantContext; -import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.IncorrectPrefixContext; import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.LeafConditionContext; import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleLeafConditionsContext; import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PrefixContext; @@ -43,7 +42,7 @@ public class CpsPathBuilder extends CpsPathBaseListener { private final CpsPathQuery cpsPathQuery = new CpsPathQuery(); - private final List<CpsPathQuery.DataLeaf> leavesData = new ArrayList<>(); + private final List<CpsPathQuery.LeafCondition> leafConditions = new ArrayList<>(); private final StringBuilder normalizedXpathBuilder = new StringBuilder(); @@ -55,13 +54,6 @@ public class CpsPathBuilder extends CpsPathBaseListener { private final List<String> booleanOperators = new ArrayList<>(); - private final List<String> comparativeOperators = new ArrayList<>(); - - @Override - public void exitInvalidPostFix(final CpsPathParser.InvalidPostFixContext ctx) { - throw new PathParsingException(ctx.getText()); - } - @Override public void exitPrefix(final PrefixContext ctx) { cpsPathQuery.setXpathPrefix(normalizedXpathBuilder.toString()); @@ -73,12 +65,9 @@ public class CpsPathBuilder extends CpsPathBaseListener { } @Override - public void exitIncorrectPrefix(final IncorrectPrefixContext ctx) { - throw new PathParsingException("CPS path can only start with one or two slashes (/)"); - } - - @Override public void exitLeafCondition(final LeafConditionContext ctx) { + final String leafName = ctx.leafName().getText(); + final String operator = ctx.comparativeOperators().getText(); final Object comparisonValue; if (ctx.IntegerLiteral() != null) { comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText()); @@ -87,7 +76,7 @@ public class CpsPathBuilder extends CpsPathBaseListener { } else { throw new PathParsingException("Unsupported comparison value encountered in expression" + ctx.getText()); } - leafContext(ctx.leafName(), comparisonValue); + leafContext(leafName, operator, comparisonValue); } @Override @@ -96,11 +85,6 @@ public class CpsPathBuilder extends CpsPathBaseListener { } @Override - public void exitComparativeOperators(final CpsPathParser.ComparativeOperatorsContext ctx) { - comparativeOperators.add(ctx.getText()); - } - - @Override public void exitDescendant(final DescendantContext ctx) { cpsPathQuery.setCpsPathPrefixType(DESCENDANT); cpsPathQuery.setDescendantName(normalizedXpathBuilder.substring(1)); @@ -110,15 +94,14 @@ public class CpsPathBuilder extends CpsPathBaseListener { @Override public void enterMultipleLeafConditions(final MultipleLeafConditionsContext ctx) { normalizedXpathBuilder.append(OPEN_BRACKET); - leavesData.clear(); + leafConditions.clear(); booleanOperators.clear(); - comparativeOperators.clear(); } @Override public void exitMultipleLeafConditions(final MultipleLeafConditionsContext ctx) { normalizedXpathBuilder.append(CLOSE_BRACKET); - cpsPathQuery.setLeavesData(leavesData); + cpsPathQuery.setLeafConditions(leafConditions); } @Override @@ -164,11 +147,6 @@ public class CpsPathBuilder extends CpsPathBaseListener { cpsPathQuery.setNormalizedXpath(normalizedXpathBuilder.toString()); cpsPathQuery.setContainerNames(containerNames); cpsPathQuery.setBooleanOperators(booleanOperators); - cpsPathQuery.setComparativeOperators(comparativeOperators); - if (cpsPathQuery.hasAncestorAxis() && cpsPathQuery.getXpathPrefix() - .endsWith("/" + cpsPathQuery.getAncestorSchemaNodeIdentifier())) { - cpsPathQuery.setAncestorSchemaNodeIdentifier(""); - } return cpsPathQuery; } @@ -183,16 +161,16 @@ public class CpsPathBuilder extends CpsPathBaseListener { } } - private void leafContext(final CpsPathParser.LeafNameContext ctx, final Object comparisonValue) { - leavesData.add(new CpsPathQuery.DataLeaf(ctx.getText(), comparisonValue)); - appendCondition(normalizedXpathBuilder, ctx.getText(), comparisonValue); + private void leafContext(final String leafName, final String operator, final Object comparisonValue) { + leafConditions.add(new CpsPathQuery.LeafCondition(leafName, operator, comparisonValue)); + appendCondition(normalizedXpathBuilder, leafName, operator, comparisonValue); if (processingAncestorAxis) { - appendCondition(normalizedAncestorPathBuilder, ctx.getText(), comparisonValue); + appendCondition(normalizedAncestorPathBuilder, leafName, operator, comparisonValue); } } private void appendCondition(final StringBuilder currentNormalizedPathBuilder, final String name, - final Object value) { + final String operator, final Object value) { final char lastCharacter = currentNormalizedPathBuilder.charAt(currentNormalizedPathBuilder.length() - 1); final boolean isStartOfExpression = lastCharacter == '['; if (!isStartOfExpression) { @@ -200,7 +178,7 @@ public class CpsPathBuilder extends CpsPathBaseListener { } currentNormalizedPathBuilder.append("@") .append(name) - .append(getLastElement(comparativeOperators)) + .append(operator) .append("'") .append(value.toString().replace("'", "''")) .append("'"); diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java index f98df05a28..03602b64f6 100644 --- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java +++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2023 TechMahindra Ltd * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,8 +25,6 @@ import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE; import java.util.List; import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -40,12 +38,11 @@ public class CpsPathQuery { private List<String> containerNames; private CpsPathPrefixType cpsPathPrefixType = ABSOLUTE; private String descendantName; - private List<DataLeaf> leavesData; + private List<LeafCondition> leafConditions; private String ancestorSchemaNodeIdentifier = ""; private String textFunctionConditionLeafName; private String textFunctionConditionValue; private List<String> booleanOperators; - private List<String> comparativeOperators; private String containsFunctionConditionLeafName; private String containsFunctionConditionValue; @@ -74,7 +71,7 @@ public class CpsPathQuery { * @return boolean value. */ public boolean hasLeafConditions() { - return leavesData != null; + return leafConditions != null; } /** @@ -104,11 +101,6 @@ public class CpsPathQuery { return cpsPathPrefixType == ABSOLUTE && hasLeafConditions(); } - @Getter - @EqualsAndHashCode - @AllArgsConstructor - public static class DataLeaf { - private final String name; - private final Object value; - } + public record LeafCondition(String name, String operator, Object value) { } + } diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy index 15f6b11823..d0df3c745a 100644 --- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy +++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy @@ -48,8 +48,8 @@ class CpsPathQuerySpec extends Specification { and: 'the right query parameters are set' assert result.xpathPrefix == expectedXpathPrefix assert result.hasLeafConditions() - assert result.leavesData[0].name == expectedLeafName - assert result.leavesData[0].value == expectedLeafValue + assert result.leafConditions[0].name() == expectedLeafName + assert result.leafConditions[0].value() == expectedLeafValue where: 'the following data is used' scenario | cpsPath || expectedXpathPrefix | expectedLeafName | expectedLeafValue 'leaf of type String' | '/parent/child[@common-leaf-name="common-leaf-value"]' || '/parent/child' | 'common-leaf-name' | 'common-leaf-value' @@ -123,11 +123,11 @@ class CpsPathQuerySpec extends Specification { when: 'the given cps path is parsed' def result = CpsPathQuery.createFrom(cpsPath) then: 'the expected number of leaves are returned' - result.leavesData.size() == expectedNumberOfLeaves + result.leafConditions.size() == expectedNumberOfLeaves and: 'the given operator(s) returns in the correct order' result.booleanOperators == expectedOperators and: 'the given comparativeOperator(s) returns in the correct order' - result.comparativeOperators == expectedComparativeOperator + result.leafConditions.operator == expectedComparativeOperator where: 'the following data is used' cpsPath || expectedNumberOfLeaves || expectedOperators || expectedComparativeOperator '/parent[@code=1]/child[@common-leaf-name-int=5]' || 1 || [] || ['='] @@ -234,40 +234,23 @@ class CpsPathQuerySpec extends Specification { when: 'the given cps path is parsed using multiple conditions on same leaf' def result = CpsPathQuery.createFrom('/test[@same-name="value1" or @same-name="value2"]') then: 'two leaves are present with correct values' - assert result.leavesData.size() == 2 - assert result.leavesData[0].name == "same-name" - assert result.leavesData[0].value == "value1" - assert result.leavesData[1].name == "same-name" - assert result.leavesData[1].value == "value2" + assert result.leafConditions.size() == 2 + assert result.leafConditions[0].name == "same-name" + assert result.leafConditions[0].value == "value1" + assert result.leafConditions[1].name == "same-name" + assert result.leafConditions[1].value == "value2" } def 'Ordering of data leaves is preserved.'() { when: 'the given cps path is parsed' def result = CpsPathQuery.createFrom(cpsPath) then: 'the order of the data leaves is preserved' - assert result.leavesData[0].name == expectedFirstLeafName - assert result.leavesData[1].name == expectedSecondLeafName + assert result.leafConditions[0].name == expectedFirstLeafName + assert result.leafConditions[1].name == expectedSecondLeafName where: 'the following data is used' cpsPath || expectedFirstLeafName | expectedSecondLeafName '/test[@name1="value1" and @name2="value2"]' || 'name1' | 'name2' '/test[@name2="value2" and @name1="value1"]' || 'name2' | 'name1' } - def 'Ancestor axis matching prefix'() { - when: 'building a cps path query' - def result = parseXPathAndBuild(xpath) - then: 'ancestor axis is removed when same as prefix' - assert result.hasAncestorAxis() == expectAncestorAxis - where: 'the following xpaths are used' - xpath || expectAncestorAxis - '//abc/def/ancestor::abc' || true - '//abc/def/ancestor::def' || false - '//abc/def/ancestor::ef' || true - } - - def parseXPathAndBuild(xpath) { - def cpsPathBuilder = CpsPathUtil.getCpsPathBuilder(xpath) - cpsPathBuilder.build() - } - } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java index b2014757d7..eb61d56632 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java @@ -24,14 +24,12 @@ package org.onap.cps.spi.repository; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.CpsPathPrefixType; import org.onap.cps.cpspath.parser.CpsPathQuery; import org.onap.cps.spi.PaginationOption; @@ -43,10 +41,8 @@ import org.onap.cps.spi.utils.EscapeUtils; import org.springframework.stereotype.Component; @RequiredArgsConstructor -@Slf4j @Component public class FragmentQueryBuilder { - private static final AnchorEntity ACROSS_ALL_ANCHORS = null; @PersistenceContext private EntityManager entityManager; @@ -59,8 +55,14 @@ public class FragmentQueryBuilder { * @return a executable query object */ public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity, final CpsPathQuery cpsPathQuery) { - return getQueryForDataspaceOrAnchorAndCpsPath(anchorEntity.getDataspace(), - anchorEntity, cpsPathQuery, Collections.emptyList()); + final StringBuilder sqlStringBuilder = new StringBuilder(); + final Map<String, Object> queryParameters = new HashMap<>(); + + sqlStringBuilder.append("SELECT fragment.* FROM fragment"); + addWhereClauseForAnchor(anchorEntity, sqlStringBuilder, queryParameters); + addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, false); + + return getQuery(sqlStringBuilder.toString(), queryParameters, FragmentEntity.class); } /** @@ -73,8 +75,18 @@ public class FragmentQueryBuilder { public Query getQueryForDataspaceAndCpsPath(final DataspaceEntity dataspaceEntity, final CpsPathQuery cpsPathQuery, final List<Long> anchorIdsForPagination) { - return getQueryForDataspaceOrAnchorAndCpsPath(dataspaceEntity, ACROSS_ALL_ANCHORS, - cpsPathQuery, anchorIdsForPagination); + final StringBuilder sqlStringBuilder = new StringBuilder(); + final Map<String, Object> queryParameters = new HashMap<>(); + + sqlStringBuilder.append("SELECT fragment.* FROM fragment"); + if (anchorIdsForPagination.isEmpty()) { + addWhereClauseForDataspace(dataspaceEntity, sqlStringBuilder, queryParameters); + } else { + addWhereClauseForAnchorIds(anchorIdsForPagination, sqlStringBuilder, queryParameters); + } + addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, true); + + return getQuery(sqlStringBuilder.toString(), queryParameters, FragmentEntity.class); } /** @@ -89,52 +101,52 @@ public class FragmentQueryBuilder { final PaginationOption paginationOption) { final StringBuilder sqlStringBuilder = new StringBuilder(); final Map<String, Object> queryParameters = new HashMap<>(); - sqlStringBuilder.append("SELECT distinct(fragment.anchor_id) FROM fragment " - + "JOIN anchor ON anchor.id = fragment.anchor_id WHERE dataspace_id = :dataspaceId"); - queryParameters.put("dataspaceId", dataspaceEntity.getId()); - addAbsoluteParentXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters, ACROSS_ALL_ANCHORS); - addXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters); - addLeafConditions(cpsPathQuery, sqlStringBuilder); - addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); - addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); - if (PaginationOption.NO_PAGINATION != paginationOption) { - sqlStringBuilder.append(" ORDER BY fragment.anchor_id"); - addPaginationCondition(sqlStringBuilder, queryParameters, paginationOption); - } - final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString()); + sqlStringBuilder.append("SELECT distinct(fragment.anchor_id) FROM fragment"); + addWhereClauseForDataspace(dataspaceEntity, sqlStringBuilder, queryParameters); + addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, true); + sqlStringBuilder.append(" ORDER BY fragment.anchor_id"); + addPaginationCondition(sqlStringBuilder, queryParameters, paginationOption); + + return getQuery(sqlStringBuilder.toString(), queryParameters, Long.class); + } + + private Query getQuery(final String sql, final Map<String, Object> queryParameters, final Class<?> returnType) { + final Query query = entityManager.createNativeQuery(sql, returnType); setQueryParameters(query, queryParameters); return query; } - private Query getQueryForDataspaceOrAnchorAndCpsPath(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, - final CpsPathQuery cpsPathQuery, - final List<Long> anchorIdsForPagination) { - final StringBuilder sqlStringBuilder = new StringBuilder(); - final Map<String, Object> queryParameters = new HashMap<>(); + private static void addWhereClauseForAnchor(final AnchorEntity anchorEntity, + final StringBuilder sqlStringBuilder, + final Map<String, Object> queryParameters) { + sqlStringBuilder.append(" WHERE anchor_id = :anchorId"); + queryParameters.put("anchorId", anchorEntity.getId()); + } - if (anchorEntity == ACROSS_ALL_ANCHORS) { - sqlStringBuilder.append("SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id" - + " WHERE dataspace_id = :dataspaceId"); - queryParameters.put("dataspaceId", dataspaceEntity.getId()); - if (!anchorIdsForPagination.isEmpty()) { - sqlStringBuilder.append(" AND anchor_id IN (:anchorIdsForPagination)"); - queryParameters.put("anchorIdsForPagination", anchorIdsForPagination); - } - } else { - sqlStringBuilder.append("SELECT * FROM fragment WHERE anchor_id = :anchorId"); - queryParameters.put("anchorId", anchorEntity.getId()); - } - addAbsoluteParentXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters, anchorEntity); + private static void addWhereClauseForAnchorIds(final List<Long> anchorIdsForPagination, + final StringBuilder sqlStringBuilder, + final Map<String, Object> queryParameters) { + sqlStringBuilder.append(" WHERE anchor_id IN (:anchorIdsForPagination)"); + queryParameters.put("anchorIdsForPagination", anchorIdsForPagination); + } + + private static void addWhereClauseForDataspace(final DataspaceEntity dataspaceEntity, + final StringBuilder sqlStringBuilder, + final Map<String, Object> queryParameters) { + sqlStringBuilder.append(" JOIN anchor ON anchor.id = fragment.anchor_id WHERE dataspace_id = :dataspaceId"); + queryParameters.put("dataspaceId", dataspaceEntity.getId()); + } + + private static void addNodeSearchConditions(final CpsPathQuery cpsPathQuery, + final StringBuilder sqlStringBuilder, + final Map<String, Object> queryParameters, + final boolean acrossAnchors) { + addAbsoluteParentXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters, acrossAnchors); addXpathSearchCondition(cpsPathQuery, sqlStringBuilder, queryParameters); addLeafConditions(cpsPathQuery, sqlStringBuilder); addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); - - final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class); - setQueryParameters(query, queryParameters); - return query; } private static void addXpathSearchCondition(final CpsPathQuery cpsPathQuery, @@ -152,12 +164,12 @@ public class FragmentQueryBuilder { private static void addAbsoluteParentXpathSearchCondition(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder, final Map<String, Object> queryParameters, - final AnchorEntity anchorEntity) { + final boolean acrossAnchors) { if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) { if (cpsPathQuery.getNormalizedParentPath().isEmpty()) { sqlStringBuilder.append(" AND parent_id IS NULL"); } else { - if (anchorEntity == ACROSS_ALL_ANCHORS) { + if (acrossAnchors) { sqlStringBuilder.append(" AND parent_id IN (SELECT id FROM fragment WHERE xpath = :parentXpath)"); } else { sqlStringBuilder.append(" AND parent_id = (SELECT id FROM fragment WHERE xpath = :parentXpath" @@ -171,10 +183,12 @@ public class FragmentQueryBuilder { private static void addPaginationCondition(final StringBuilder sqlStringBuilder, final Map<String, Object> queryParameters, final PaginationOption paginationOption) { - final Integer offset = (paginationOption.getPageIndex() - 1) * paginationOption.getPageSize(); - sqlStringBuilder.append(" LIMIT :pageSize OFFSET :offset"); - queryParameters.put("pageSize", paginationOption.getPageSize()); - queryParameters.put("offset", offset); + if (PaginationOption.NO_PAGINATION != paginationOption) { + final Integer offset = (paginationOption.getPageIndex() - 1) * paginationOption.getPageSize(); + sqlStringBuilder.append(" LIMIT :pageSize OFFSET :offset"); + queryParameters.put("pageSize", paginationOption.getPageSize()); + queryParameters.put("offset", offset); + } } private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) { @@ -185,40 +199,34 @@ public class FragmentQueryBuilder { } } - private void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) { + private static void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) { if (cpsPathQuery.hasLeafConditions()) { - queryLeafConditions(cpsPathQuery, sqlStringBuilder); - } - } - - private void queryLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) { - sqlStringBuilder.append(" AND ("); - final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators()); - final Queue<String> comparativeOperatorQueue = new LinkedList<>(cpsPathQuery.getComparativeOperators()); - cpsPathQuery.getLeavesData().forEach(leaf -> { - final String nextComparativeOperator = comparativeOperatorQueue.poll(); - if (leaf.getValue() instanceof Integer) { - sqlStringBuilder.append("(attributes ->> '").append(leaf.getName()).append("')\\:\\:int"); - sqlStringBuilder.append(nextComparativeOperator); - sqlStringBuilder.append(leaf.getValue()); - } else { - if ("=".equals(nextComparativeOperator)) { - final String leafValueAsText = leaf.getValue().toString(); - sqlStringBuilder.append("attributes ->> '").append(leaf.getName()).append("'"); - sqlStringBuilder.append(" = '"); - sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText)); - sqlStringBuilder.append("'"); + sqlStringBuilder.append(" AND ("); + final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators()); + cpsPathQuery.getLeafConditions().forEach(leafCondition -> { + if (leafCondition.value() instanceof Integer) { + sqlStringBuilder.append("(attributes ->> '").append(leafCondition.name()).append("')\\:\\:int"); + sqlStringBuilder.append(leafCondition.operator()); + sqlStringBuilder.append(leafCondition.value()); } else { - throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer "); + if ("=".equals(leafCondition.operator())) { + final String leafValueAsText = leafCondition.value().toString(); + sqlStringBuilder.append("attributes ->> '").append(leafCondition.name()).append("'"); + sqlStringBuilder.append(" = '"); + sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText)); + sqlStringBuilder.append("'"); + } else { + throw new CpsPathException(" can use only " + leafCondition.operator() + " with integer "); + } } - } - if (!booleanOperatorsQueue.isEmpty()) { - sqlStringBuilder.append(" "); - sqlStringBuilder.append(booleanOperatorsQueue.poll()); - sqlStringBuilder.append(" "); - } - }); - sqlStringBuilder.append(")"); + if (!booleanOperatorsQueue.isEmpty()) { + sqlStringBuilder.append(" "); + sqlStringBuilder.append(booleanOperatorsQueue.poll()); + sqlStringBuilder.append(" "); + } + }); + sqlStringBuilder.append(")"); + } } private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery, diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/README.md b/cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/README.md deleted file mode 100644 index 212acb9006..0000000000 --- a/cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/README.md +++ /dev/null @@ -1,23 +0,0 @@ -<!-- - ============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========================================================= ---> - -##Placeholder folder for generated CSV files as part of yang models. - -Do not remove this folder
\ No newline at end of file diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2021-12-13.yang b/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2021-12-13.yang deleted file mode 100644 index ed3559bf61..0000000000 --- a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2021-12-13.yang +++ /dev/null @@ -1,63 +0,0 @@ -module dmi-registry { - - yang-version 1.1; - - namespace "org:onap:cps:ncmp"; - - prefix dmi-reg; - - contact "dylan.byrne@est.tech"; - - revision "2021-05-20" { - description - "Initial Version"; - } - - revision "2021-10-20" { - description - "Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility"; - } - - revision "2021-12-13" { - description - "Added new list of public additonal properties for a Cm-Handle which are exposed to clients of the NCMP interface"; - } - - container dmi-registry { - list cm-handles { - key "id"; - leaf id { - type string; - } - leaf dmi-service-name { - type string; - } - leaf dmi-data-service-name { - type string; - } - leaf dmi-model-service-name { - type string; - } - - list additional-properties { - key "name"; - leaf name { - type string; - } - leaf value { - type string; - } - } - - list public-properties { - key "name"; - leaf name { - type string; - } - leaf value { - type string; - } - } - } - } -}
\ No newline at end of file diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-02-10.yang b/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-02-10.yang deleted file mode 100644 index 3c6d990c5d..0000000000 --- a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-02-10.yang +++ /dev/null @@ -1,81 +0,0 @@ -module dmi-registry { - - yang-version 1.1; - - namespace "org:onap:cps:ncmp"; - - prefix dmi-reg; - - contact "toine.siebelink@est.tech"; - - revision "2022-02-10" { - description - "Added State, LockReason, LockReasonDetails to aid with cmHandle sync and timestamp to aid with retry/timeout scenarios"; - } - - revision "2021-12-13" { - description - "Added new list of public additional properties for a Cm-Handle which are exposed to clients of the NCMP interface"; - } - - revision "2021-10-20" { - description - "Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility"; - } - - revision "2021-05-20" { - description - "Initial Version"; - } - - container dmi-registry { - list cm-handles { - key "id"; - leaf id { - type string; - } - leaf dmi-service-name { - type string; - } - leaf dmi-data-service-name { - type string; - } - leaf dmi-model-service-name { - type string; - } - - list additional-properties { - key "name"; - leaf name { - type string; - } - leaf value { - type string; - } - } - - list public-properties { - key "name"; - leaf name { - type string; - } - leaf value { - type string; - } - } - - leaf state { - type string; - } - leaf lock-reason { - type string; - } - leaf lock-reason-details { - type string; - } - leaf last-update-time { - type string; - } - } - } -} diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-05-10.yang b/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-05-10.yang deleted file mode 100644 index 77517968c6..0000000000 --- a/cps-ri/src/main/resources/changelog/db/changes/data/yang-models/dmi-registry@2022-05-10.yang +++ /dev/null @@ -1,123 +0,0 @@ -module dmi-registry { - - yang-version 1.1; - - namespace "org:onap:cps:ncmp"; - - prefix dmi-reg; - - contact "toine.siebelink@est.tech"; - - revision "2022-05-10" { - description - "Added DataSyncEnabled, SyncState with State, LastSyncTime, DataStoreSyncState with Operational and Running syncstate"; - } - - revision "2022-02-10" { - description - "Added State, LockReason, LockReasonDetails to aid with cmHandle sync and timestamp to aid with retry/timeout scenarios"; - } - - revision "2021-12-13" { - description - "Added new list of public additional properties for a Cm-Handle which are exposed to clients of the NCMP interface"; - } - - revision "2021-10-20" { - description - "Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility"; - } - - revision "2021-05-20" { - description - "Initial Version"; - } - - grouping LockReason { - leaf reason { - type string; - } - leaf details { - type string; - } - } - - grouping SyncState { - leaf sync-state { - type string; - } - leaf last-sync-time { - type string; - } - } - - grouping Datastores { - container operational { - uses SyncState; - } - container running { - uses SyncState; - } - } - - container dmi-registry { - list cm-handles { - key "id"; - leaf id { - type string; - } - leaf dmi-service-name { - type string; - } - leaf dmi-data-service-name { - type string; - } - leaf dmi-model-service-name { - type string; - } - - list additional-properties { - key "name"; - leaf name { - type string; - } - leaf value { - type string; - } - } - - list public-properties { - key "name"; - leaf name { - type string; - } - leaf value { - type string; - } - } - - container state { - leaf cm-handle-state { - type string; - } - - container lock-reason { - uses LockReason; - } - - leaf last-update-time { - type string; - } - - leaf data-sync-enabled { - type boolean; - default "false"; - } - - container datastores { - uses Datastores; - } - } - } - } -}
\ No newline at end of file diff --git a/cps-ri/src/main/resources/yangResourceCsvGenerator.py b/cps-ri/src/main/resources/yangResourceCsvGenerator.py deleted file mode 100644 index 3a076d4378..0000000000 --- a/cps-ri/src/main/resources/yangResourceCsvGenerator.py +++ /dev/null @@ -1,66 +0,0 @@ -# ============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========================================================= - - -import csv -import datetime -import hashlib -import sys -import re - -yang_source = '' -checksum = '' -regexForModuleName = '(?<=module)(.*)(?={)' -regexForRevision = '(?<=revision)(.*)(?={)' - -def main(): - for yang_source in sys.argv[1:]: - checksum = hashlib.sha256(str(yang_source).encode()).hexdigest() - - with open('changelog/db/changes/data/yang-models/' + yang_source + '.yang', 'r') as content: - dmiRegistry = content.read() - - try: - latestRevision = re.search(regexForRevision, dmiRegistry).group(0).replace('\"','').strip() - except: - print("ERROR IN in yangResourceCsvGenerator.py: Unable to find revision for " + yang_source + '.yang') - - try: - module_name = re.search(regexForModuleName, dmiRegistry).group(0).strip() - except: - print("ERROR IN in yangResourceCsvGenerator.py: Unable to find module name for " + yang_source + '.yang') - - #If true, file was created after module_name and revision columns were added to yang-resources table - writeWithModuleNameAndRevision = yang_source != 'dmi-registry@2021-12-13' - - try: - # open the file in the write mode - with open('changelog/db/changes/data/dmi/generated-csv/generated_yang_resource_' + yang_source + '.csv', 'w', newline='') \ - as file: - writer = csv.writer(file, delimiter='|') - if(writeWithModuleNameAndRevision): - writer.writerow(["name", "content", "checksum", "module_name", "revision"]) - writer.writerow([yang_source + '.yang', dmiRegistry, checksum, module_name, latestRevision]) - else: - writer.writerow(["name", "content", "checksum"]) - writer.writerow([yang_source + '.yang', dmiRegistry, checksum]) - except: - print("ERROR IN in yangResourceCsvGenerator.py: Unable to write to changelog/db/changes/data/dmi/generated-csv/generated_yang_resource_" + yang_source + ".csv") - - -main()
\ No newline at end of file diff --git a/cps-service/pom.xml b/cps-service/pom.xml index f04b4e81c8..37a45957f3 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -144,18 +144,6 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> - <dependency> - <groupId>io.cloudevents</groupId> - <artifactId>cloudevents-json-jackson</artifactId> - </dependency> - <dependency> - <groupId>io.cloudevents</groupId> - <artifactId>cloudevents-kafka</artifactId> - </dependency> - <dependency> - <groupId>io.cloudevents</groupId> - <artifactId>cloudevents-spring</artifactId> - </dependency> <!-- T E S T D E P E N D E N C I E S --> <dependency> <groupId>org.codehaus.groovy</groupId> diff --git a/csit/install-deps.sh b/csit/install-deps.sh new file mode 100755 index 0000000000..ef0b96a799 --- /dev/null +++ b/csit/install-deps.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Copyright 2024 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. +# + +echo "---> install-deps.sh" +echo "Installing dependencies" + +# Create directory for downloaded binaries. +mkdir -p bin +touch bin/.gitignore + +# Add it to the PATH, so downloaded versions will be used. +export PATH="$(pwd)/bin:$PATH" + +# Download docker-compose. +if [ ! -x bin/docker-compose ]; then + echo " Downloading docker-compose" + curl -s -L https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64 > bin/docker-compose + chmod +x bin/docker-compose +else + echo " docker-compose already installed" +fi +docker-compose version diff --git a/csit/plans/cps/setup.sh b/csit/plans/cps/setup.sh index 80829eba14..036f14b82f 100755 --- a/csit/plans/cps/setup.sh +++ b/csit/plans/cps/setup.sh @@ -1,7 +1,6 @@ #!/bin/bash # # Copyright 2016-2017 Huawei Technologies Co., Ltd. -# 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. @@ -19,7 +18,7 @@ # Modifications copyright (c) 2020-2021 Samsung Electronics Co., Ltd. # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021 Bell Canada. -# Modifications Copyright (C) 2021 Nordix Foundation. +# Modifications Copyright (C) 2021-2024 Nordix Foundation. # # Branched from ccsdk/distribution to this repository Feb 23, 2021 # @@ -59,10 +58,6 @@ export $(cut -d= -f1 $WORKSPACE/plans/cps/test.properties) ###################### setup cps-ncmp ############################ cd $CPS_HOME/docker-compose -curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > docker-compose -chmod +x docker-compose -docker-compose version - # start CPS/NCMP, DMI Plugin, and PostgreSQL containers with docker compose docker-compose --profile dmi-service up -d @@ -82,4 +77,4 @@ check_health $DMI_HOST:$DMI_PORT 'dmi-plugin' ###################### ROBOT Configurations ########################## # Pass variables required for Robot test suites in ROBOT_VARIABLES -ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v DMI_CSIT_STUB_HOST:$LOCAL_IP -v DMI_CSIT_STUB_PORT:$DMI_DEMO_STUB_PORT -v DMI_AUTH_ENABLED:$DMI_AUTH_ENABLED -v DATADIR_CPS_CORE:$WORKSPACE/data/cps-core -v DATADIR_NCMP:$WORKSPACE/data/ncmp -v DATADIR_SUBS_NOTIFICATION:$WORKSPACE/data/subscription-notification --exitonfailure"
\ No newline at end of file +ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v DMI_CSIT_STUB_HOST:$LOCAL_IP -v DMI_CSIT_STUB_PORT:$DMI_DEMO_STUB_PORT -v DMI_AUTH_ENABLED:$DMI_AUTH_ENABLED -v DATADIR_CPS_CORE:$WORKSPACE/data/cps-core -v DATADIR_NCMP:$WORKSPACE/data/ncmp -v DATADIR_SUBS_NOTIFICATION:$WORKSPACE/data/subscription-notification --exitonfailure" diff --git a/csit/run-project-csit.sh b/csit/run-project-csit.sh index fcb3c925c1..f362cc7130 100755 --- a/csit/run-project-csit.sh +++ b/csit/run-project-csit.sh @@ -2,6 +2,7 @@ # # Copyright 2020-2021 © Samsung Electronics Co., Ltd. # Modifications Copyright (C) 2021 Pantheon.tech +# Modifications Copyright (C) 2024 Nordix Foundation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,6 +29,8 @@ rm -rf ${WORKSPACE}/archives mkdir -p ${WORKSPACE}/archives cd ${WORKSPACE} +source install-deps.sh + # Execute all test-suites defined under plans subdirectory for dir in plans/*/ do diff --git a/csit/tests/cps-trust-level/cps-trust-level.robot b/csit/tests/cps-trust-level/cps-trust-level.robot index e4deeff32b..810bcf4d12 100644 --- a/csit/tests/cps-trust-level/cps-trust-level.robot +++ b/csit/tests/cps-trust-level/cps-trust-level.robot @@ -36,7 +36,7 @@ ${ncmpBasePath} /ncmp/v1 ${dmiUrl} http://${DMI_HOST}:${DMI_PORT} ${jsonCreateCmHandles} {"dmiPlugin":"${dmiUrl}","dmiDataPlugin":"","dmiModelPlugin":"","createdCmHandles":[{"trustLevel":"COMPLETE","cmHandle":"CH-1"},{"trustLevel":"COMPLETE","cmHandle":"CH-2"},{"cmHandle":"CH-3"},{"trustLevel":"NONE","cmHandle":"CH-4"}]} ${jsonTrustLevelPropertyQueryParameters} {"cmHandleQueryParameters": [{"conditionName": "cmHandleWithTrustLevel", "conditionParameters": [ {"trustLevel": "COMPLETE"} ] }]} -${jsonTrustLevelQueryResponse} {"data":{"attributeValueChange":[{"attributeName":"trustLevel","newAttributeValue":"NONE"}]}} +${jsonTrustLevelEventPayload} {"data":{"attributeValueChange":[{"attributeName":"trustLevel","newAttributeValue":"NONE"}]}} *** Test Cases *** Register data node @@ -57,7 +57,7 @@ Verify notification Compare Header Values ${header_key_value_pair[0]} ${header_key_value_pair[1]} "ce_type" "org.onap.cps.ncmp.events.avc.ncmp_to_client.AvcEvent" Compare Header Values ${header_key_value_pair[0]} ${header_key_value_pair[1]} "ce_correlationid" "CH-4" END - Should Be Equal As Strings ${payload} ${jsonTrustLevelQueryResponse} + Should Be Equal As Strings ${payload} ${jsonTrustLevelEventPayload} [Teardown] Basic Teardown ${group_id} Retrieve CM Handle ids where query parameters Match (trust level query) diff --git a/docker-compose/config/grafana/jvm-micrometer-dashboard.json b/docker-compose/config/grafana/jvm-micrometer-dashboard.json new file mode 100644 index 0000000000..8f7747c596 --- /dev/null +++ b/docker-compose/config/grafana/jvm-micrometer-dashboard.json @@ -0,0 +1,3613 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "limit": 100, + "name": "Annotations & Alerts", + "showIn": 0, + "type": "dashboard" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "enable": true, + "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0", + "iconColor": "rgba(255, 96, 96, 1)", + "name": "Restart Detection", + "showIn": 0, + "step": "1m", + "tagKeys": "restart-tag", + "textFormat": "uptime reset", + "titleFormat": "Restart" + } + ] + }, + "description": "Dashboard for Micrometer instrumented applications (Java, Spring Boot, Micronaut)", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 4701, + "graphTooltip": 1, + "id": 1, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 139, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "Quick Facts", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 63, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "refId": "A", + "step": 14400 + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "dateTimeAsIso" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 92, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "refId": "A", + "step": 14400 + } + ], + "title": "Start time", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 70 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 65, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 14400 + } + ], + "title": "Heap used", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + }, + { + "options": { + "from": -1e+32, + "result": { + "text": "N/A" + }, + "to": 0 + }, + "type": "range" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 70 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 75, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 14400 + } + ], + "title": "Non-Heap used", + "type": "stat" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 140, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "I/O Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 111, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP", + "refId": "A" + } + ], + "title": "Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "HTTP" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890f02", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "HTTP - 5xx" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#bf1b00", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 112, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status=~\"5..\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP - 5xx", + "refId": "A" + } + ], + "title": "Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 113, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(http_server_requests_seconds_sum{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "HTTP - AVG", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "max(http_server_requests_seconds_max{application=\"$application\", instance=\"$instance\", status!~\"5..\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "HTTP - MAX", + "refId": "B" + } + ], + "title": "Duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 119, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "tomcat_threads_busy_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "TOMCAT - BSY", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "tomcat_threads_current_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "TOMCAT - CUR", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "tomcat_threads_config_max_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "TOMCAT - MAX", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jetty_threads_busy{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "JETTY - BSY", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jetty_threads_current{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "JETTY - CUR", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jetty_threads_config_max{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "JETTY - MAX", + "refId": "F" + } + ], + "title": "Utilisation", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 141, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "JVM Memory", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 13 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "title": "JVM Heap", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 13 + }, + "id": 25, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "title": "JVM Non-Heap", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 13 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "title": "JVM Total", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 13 + }, + "id": 86, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "vss", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "rss", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "swap", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "total", + "refId": "D" + } + ], + "title": "JVM Process Memory", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 142, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "JVM Misc", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 21 + }, + "id": 106, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "system", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "process", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[15m])", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "process-15m", + "refId": "C" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 21 + }, + "id": 93, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "system-1m", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "cpus", + "refId": "B" + } + ], + "title": "Load", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 21 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "live", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "daemon", + "metric": "", + "refId": "B", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "peak", + "refId": "C", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "process", + "refId": "D", + "step": 2400 + } + ], + "title": "Threads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "blocked" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#bf1b00", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "new" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#fce2de", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "runnable" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#7eb26d", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "terminated" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#511749", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "timed-waiting" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#c15c17", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "waiting" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#eab839", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 21 + }, + "id": 124, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{state}}", + "refId": "A" + } + ], + "title": "Thread States", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "The percent of time spent on Garbage Collection over all CPUs assigned to the JVM process.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 28 + }, + "id": 138, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])) by (application, instance) / on(application, instance) system_cpu_count", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "CPU time spent on GC", + "refId": "A" + } + ], + "title": "GC Pressure", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "debug" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#1F78C1", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#BF1B00", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "info" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#508642", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "trace" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#6ED0E0", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "warn" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#EAB839", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 6, + "y": 28 + }, + "id": 91, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{level}}", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "title": "Log Events", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 28 + }, + "id": 61, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_files_open_files{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "open", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_files_max_files{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "B", + "step": 2400 + } + ], + "title": "File Descriptors", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 143, + "panels": [], + "repeat": "persistence_counts", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "JVM Memory Pools (Heap)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 36 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "jvm_memory_pool_heap", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "title": "$jvm_memory_pool_heap", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 43 + }, + "id": 144, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "JVM Memory Pools (Non-Heap)", + "type": "row" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "rightLogBase": 1 + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 44 + }, + "id": 78, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "maxPerRow": 3, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "jvm_memory_pool_nonheap", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "timeseries", + "x-axis": true, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": [ + "mbytes", + "short" + ], + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "min": 0, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ] + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 58 + }, + "id": 145, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "Garbage Collection", + "type": "row" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 59 + }, + "id": 98, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{action}} ({{cause}})", + "refId": "A" + } + ], + "thresholds": [], + "title": "Collections", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "timeseries", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "show": true + } + ] + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 59 + }, + "id": 101, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "avg {{action}} ({{cause}})", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "max {{action}} ({{cause}})", + "refId": "B" + } + ], + "thresholds": [], + "title": "Pause Durations", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "timeseries", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "show": true + } + ] + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 59 + }, + "id": 99, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "allocated", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "promoted", + "refId": "B" + } + ], + "thresholds": [], + "title": "Allocated/Promoted", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "timeseries", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ] + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 66 + }, + "id": 146, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "Classloading", + "type": "row" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "rightLogBase": 1 + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "loaded", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "title": "Classes loaded", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "timeseries", + "x-axis": true, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": [ + "short", + "short" + ], + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": 0, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ] + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "rightLogBase": 1 + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 67 + }, + "id": 38, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "delta-1m", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "title": "Class delta", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "timeseries", + "x-axis": true, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": [ + "ops", + "short" + ], + "yaxes": [ + { + "format": "short", + "label": "", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ] + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 74 + }, + "id": 147, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "Buffer Pools", + "type": "row" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 75 + }, + "id": 131, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "maxPerRow": 3, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "jvm_buffer_pool", + "seriesOverrides": [ + { + "alias": "count", + "yaxis": 2 + }, + { + "alias": "buffers", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "capacity", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=~\"$jvm_buffer_pool\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "buffers", + "refId": "C" + } + ], + "thresholds": [], + "title": "$jvm_buffer_pool", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "timeseries", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "min": "0", + "show": true + }, + { + "decimals": 0, + "format": "short", + "label": "", + "logBase": 1, + "min": "0", + "show": true + } + ] + } + ], + "refresh": "auto", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [ + { + "current": { + "isNone": true, + "selected": false, + "text": "None", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Application", + "multi": false, + "name": "application", + "options": [], + "query": "label_values(application)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "current": { + "selected": false, + "text": "docker-compose-cps-and-ncmp-1:8080", + "value": "docker-compose-cps-and-ncmp-1:8080" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Instance", + "multi": false, + "multiFormat": "glob", + "name": "instance", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 2, + "includeAll": true, + "label": "JVM Memory Pools Heap", + "multi": false, + "multiFormat": "glob", + "name": "jvm_memory_pool_heap", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 2, + "includeAll": true, + "label": "JVM Memory Pools Non-Heap", + "multi": false, + "multiFormat": "glob", + "name": "jvm_memory_pool_nonheap", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "", + "hide": 2, + "includeAll": true, + "label": "JVM Buffer Pools", + "multi": false, + "multiFormat": "glob", + "name": "jvm_buffer_pool", + "options": [], + "query": "label_values(jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "now": true, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "JVM (Micrometer)", + "uid": "bdvp1kgecrda8f", + "version": 1, + "weekStart": "" +} diff --git a/docker-compose/config/grafana/provisioning/dashboards/dashboard.yml b/docker-compose/config/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000000..b6dba86680 --- /dev/null +++ b/docker-compose/config/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +providers: + - name: default + orgId: 1 + type: file + options: + path: /var/lib/grafana/dashboards + foldersFromFilesStructure: true diff --git a/docker-compose/config/grafana/provisioning/datasources/datasource.yml b/docker-compose/config/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000000..86fd3465e1 --- /dev/null +++ b/docker-compose/config/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,8 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true diff --git a/docker-compose/nginx.conf b/docker-compose/config/nginx/nginx.conf index 17fad1b876..17fad1b876 100644 --- a/docker-compose/nginx.conf +++ b/docker-compose/config/nginx/nginx.conf diff --git a/docker-compose/proxy_params b/docker-compose/config/nginx/proxy_params index ab092a98f3..ab092a98f3 100644 --- a/docker-compose/proxy_params +++ b/docker-compose/config/nginx/proxy_params diff --git a/docker-compose/postgres-init.sql b/docker-compose/config/postgres-init.sql index 0c96de5b55..0c96de5b55 100644 --- a/docker-compose/postgres-init.sql +++ b/docker-compose/config/postgres-init.sql diff --git a/docker-compose/prometheus.yml b/docker-compose/config/prometheus.yml index 9640709089..89af4a6800 100644 --- a/docker-compose/prometheus.yml +++ b/docker-compose/config/prometheus.yml @@ -3,8 +3,10 @@ global: evaluation_interval: 5s scrape_configs: - - job_name: 'cps-and-ncm' + - job_name: 'cps-and-ncmp' metrics_path: '/actuator/prometheus' scrape_interval: 5s static_configs: - - targets: ['cps-and-ncmp:8080'] + - targets: + - 'docker-compose-cps-and-ncmp-1:8080' + - 'docker-compose-cps-and-ncmp-2:8080' diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 5af325a50b..f79c03dbab 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -33,7 +33,7 @@ services: POSTGRES_USER: ${DB_USERNAME:-cps} POSTGRES_PASSWORD: ${DB_PASSWORD:-cps} volumes: - - ./postgres-init.sql:/docker-entrypoint-initdb.d/postgres-init.sql + - ./config/postgres-init.sql:/docker-entrypoint-initdb.d/postgres-init.sql deploy: resources: reservations: @@ -79,8 +79,8 @@ services: depends_on: - cps-and-ncmp volumes: - - ./nginx.conf:/etc/nginx/nginx.conf - - ./proxy_params:/etc/nginx/proxy_params + - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf + - ./config/nginx/proxy_params:/etc/nginx/proxy_params ### if kafka is not required comment out zookeeper and kafka ### zookeeper: @@ -139,6 +139,7 @@ services: KAFKA_BOOTSTRAP_SERVER: kafka:29092 NCMP_CONSUMER_GROUP_ID: ncmp-group NCMP_ASYNC_M2M_TOPIC: ncmp-async-m2m + MODULE_INITIAL_PROCESSING_DELAY_MS: 0 MODULE_REFERENCES_DELAY_MS: 100 MODULE_RESOURCES_DELAY_MS: 1000 READ_DATA_FOR_CM_HANDLE_DELAY_MS: 300 @@ -158,26 +159,28 @@ services: - policy-executor-stub prometheus: - container_name: prometheus-container + container_name: prometheus image: prom/prometheus:latest ports: - 9090:9090 restart: always volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml + - ./config/prometheus.yml:/etc/prometheus/prometheus.yml profiles: - monitoring grafana: image: grafana/grafana-oss:latest user: "" - container_name: grafana-container + container_name: grafana depends_on: prometheus: condition: service_started ports: - 3000:3000 volumes: + - ./config/grafana/provisioning/:/etc/grafana/provisioning/ + - ./config/grafana/jvm-micrometer-dashboard.json:/var/lib/grafana/dashboards/jvm-micrometer-dashboard.json - grafana:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=admin diff --git a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json index 2ec9daf949..4d98bc8632 100644 --- a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json +++ b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-create-schema:1.0.0", "$ref": "#/definitions/NcmpCreate", "definitions": { "NcmpCreate": { diff --git a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json index 5df0325e39..1246d9d815 100644 --- a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json +++ b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-delete-schema:1.0.0", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-delete-schema:1.0.0", "$ref": "#/definitions/NcmpDelete", "definitions": { "NcmpDelete": { diff --git a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json index e26c244c94..4917aea146 100644 --- a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json +++ b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-patch-schema:1.0.0", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-patch-schema:1.0.0", "$ref": "#/definitions/NcmpPatch", "definitions": { "NcmpPatch": { diff --git a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json index 0a497e38c5..831526cd52 100644 --- a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json +++ b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-update-schema:1.0.0", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-update-schema:1.0.0", "$ref": "#/definitions/NcmpUpdate", "definitions": { "NcmpUpdate": { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 5e46e95a0c..bd53c4ea13 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -46,6 +46,7 @@ import org.onap.cps.spi.utils.SessionManager import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc @@ -125,12 +126,19 @@ abstract class CpsIntegrationSpecBase extends Specification { @Autowired AlternateIdMatcher alternateIdMatcher + + @Value('${ncmp.policy-executor.server.port:8080}') + private String policyServerPort; + MockWebServer mockDmiServer1 = new MockWebServer() MockWebServer mockDmiServer2 = new MockWebServer() + MockWebServer mockPolicyServer = new MockWebServer() DmiDispatcher dmiDispatcher1 = new DmiDispatcher() DmiDispatcher dmiDispatcher2 = new DmiDispatcher() + PolicyDispatcher policyDispatcher = new PolicyDispatcher(); + def DMI1_URL = null def DMI2_URL = null @@ -155,13 +163,18 @@ abstract class CpsIntegrationSpecBase extends Specification { mockDmiServer2.setDispatcher(dmiDispatcher2) mockDmiServer2.start() + mockPolicyServer.setDispatcher(policyDispatcher) + mockPolicyServer.start(Integer.valueOf(policyServerPort)) + DMI1_URL = String.format("http://%s:%s", mockDmiServer1.getHostName(), mockDmiServer1.getPort()) DMI2_URL = String.format("http://%s:%s", mockDmiServer2.getHostName(), mockDmiServer2.getPort()) + } def cleanup() { mockDmiServer1.shutdown() mockDmiServer2.shutdown() + mockPolicyServer.shutdown() } def static readResourceDataFile(filename) { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy index fcc23db782..35a7b6a7c2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy @@ -20,18 +20,18 @@ package org.onap.cps.integration.base -import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile - import groovy.json.JsonSlurper -import java.util.regex.Matcher import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest -import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteRequest import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType +import java.util.regex.Matcher + +import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile + /** * This class simulates responses from the DMI server in NCMP integration tests. * @@ -117,32 +117,32 @@ class DmiDispatcher extends Dispatcher { return mockResponseWithBody(HttpStatus.OK, response) } - private getModuleReferencesResponse(cmHandleId) { + def getModuleReferencesResponse(cmHandleId) { def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect { MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) }.join(',') + ']}' return mockResponseWithBody(HttpStatus.OK, moduleReferences) } - private getModuleResourcesResponse(cmHandleId) { + def getModuleResourcesResponse(cmHandleId) { def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect { MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) }.join(',') + ']' return mockResponseWithBody(HttpStatus.OK, moduleResources) } - private getModuleNamesForCmHandle(cmHandleId) { + def getModuleNamesForCmHandle(cmHandleId) { if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) { throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId) } return moduleNamesPerCmHandleId.get(cmHandleId) } - private static mockResponse(status) { + def static mockResponse(status) { return new MockResponse().setResponseCode(status.value()) } - private static mockResponseWithBody(status, responseBody) { + def static mockResponseWithBody(status, responseBody) { return new MockResponse() .setResponseCode(status.value()) .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy new file mode 100644 index 0000000000..27e7563e61 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy @@ -0,0 +1,74 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.integration.base + + +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper + +/** + * This class simulates responses from the Policy Execution server in NCMP integration tests. + */ +class PolicyDispatcher extends Dispatcher { + + def objectMapper = new ObjectMapper() + def expectedAuthorizationToken = 'ABC' + def allowAll = true; // Prevents legacy test being affected + + @Override + MockResponse dispatch(RecordedRequest recordedRequest) { + + if (!allowAll && !recordedRequest.getHeader('Authorization').contains(expectedAuthorizationToken)) { + return new MockResponse().setResponseCode(401) + } + + if (recordedRequest.path != '/v1/execute') { + return new MockResponse().setResponseCode(400) + } + + def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class) + def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier') + def responseAsMap = [:] + responseAsMap.put('decisionId',1) + if (allowAll || targetIdentifier == 'fdn1') { + responseAsMap.put('decision','allow') + responseAsMap.put('message','') + } else { + responseAsMap.put('decision','deny') + responseAsMap.put('message','I only like fdn1') + } + def responseAsString = objectMapper.writeValueAsString(responseAsMap) + + return mockResponseWithBody(HttpStatus.OK, responseAsString) + } + + static mockResponseWithBody(status, responseBody) { + return new MockResponse() + .setResponseCode(status.value()) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .setBody(responseBody) + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy index 69598a0604..5c2a4fc665 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy @@ -271,7 +271,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase { 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"] 'ancestor with parent list element' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"] 'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"] - 'ancestor same as target type' | '//books/title[text()="Matilda"]/ancestor::books' || ["/bookstore/categories[@code='1']/books[@title='Matilda']"] } def 'Cps Path query across anchors with #scenario descendants.'() { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy new file mode 100644 index 0000000000..99f245ae8c --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType + +import static org.springframework.http.HttpMethod.POST +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request + +class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase { + + def setup() { + // Enable mocked policy executor logic + policyDispatcher.allowAll = false; + //minimum setup for 2 cm handles with alternate ids + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': [], 'ch-2': []] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'fdn1') + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'fdn2') + } + + def cleanup() { + deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-2') + } + + def 'Policy Executor create request with #scenario.'() { + when: 'a pass-through write request is sent to NCMP' + def response = mvc.perform(request(POST, "/ncmp/v1/ch/$cmHandle/data/ds/ncmp-datastore:passthrough-running") + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON) + .content('{ "some-json": "data" }') + .header(HttpHeaders.AUTHORIZATION, authorization)) + .andReturn().response + then: 'the expected status code is returned' + response.getStatus() == execpectedStatusCode + where: 'following parameters are used' + scenario | cmHandle | authorization || execpectedStatusCode + 'accepted cm handle' | 'ch-1' | 'mock expects "ABC"' || 201 + 'un-accepted cm handle' | 'ch-2' | 'mock expects "ABC"' || 409 + 'invalid authorization' | 'ch-1' | 'something else' || 500 + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy index 33973e547b..1e1af556f1 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy @@ -41,7 +41,7 @@ class RestApiSpec extends CpsIntegrationSpecBase { 'ch-3': ['M1', 'M3'] ] when: 'a POST request is made to register the CM Handles' - def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}' + def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) .andExpect(status().is2xxSuccessful()) then: 'CM-handles go to READY state' @@ -76,6 +76,27 @@ class RestApiSpec extends CpsIntegrationSpecBase { 'M3' || ['ch-3'] } + def 'Search for CM Handles using Cps Path Query.'() { + given: 'a JSON request body containing search parameter' + def requestBodyWithSearchCondition = """{ + "cmHandleQueryParameters": [ + { + "conditionName": "cmHandleWithCpsPath", + "conditionParameters": [ {"cpsPath" : "%s"} ] + } + ] + }""".formatted(cpsPath) + expect: "a search for cps path ${cpsPath} returns expected CM handles" + mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithSearchCondition)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) + .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); + where: + scenario | cpsPath || expectedCmHandles + 'All Ready CM handles' | "//state[@cm-handle-state='READY']" || ['ch-1', 'ch-2', 'ch-3'] + 'Having Alternate ID alt-3' | "//cm-handles[@alternate-id='alt-3']" || ['ch-3'] + } + def 'De-register CM handles using REST API.'() { when: 'a POST request is made to deregister the CM Handle' def requestBody = '{"dmiPlugin":"'+DMI1_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}' diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml index fefae345e6..760dad01af 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -213,6 +213,20 @@ ncmp: init: mode: ALWAYS + policy-executor: + enabled: true + server: + address: http://localhost + port: 8790 + httpclient: + all-services: + maximumInMemorySizeInMegabytes: 1 + maximumConnectionsTotal: 10 + pendingAcquireMaxCount: 10 + connectionTimeoutInSeconds: 30 + readTimeoutInSeconds: 30 + writeTimeoutInSeconds: 30 + hazelcast: cluster-name: cps-and-ncmp-test-caches mode: diff --git a/k6-tests/install-deps.sh b/k6-tests/install-deps.sh new file mode 100755 index 0000000000..bb5deb93dd --- /dev/null +++ b/k6-tests/install-deps.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Copyright 2024 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. +# + +echo "---> install-deps.sh" +echo "Installing dependencies" + +# Create directory for downloaded binaries. +mkdir -p bin +touch bin/.gitignore + +# Add it to the PATH, so downloaded versions will be used. +export PATH="$(pwd)/bin:$PATH" + +# Download docker-compose. +if [ ! -x bin/docker-compose ]; then + echo " Downloading docker-compose" + curl -s -L https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64 > bin/docker-compose + chmod +x bin/docker-compose +else + echo " docker-compose already installed" +fi +docker-compose version + +# Download k6 with kafka extension. +if [ ! -x bin/k6 ]; then + echo " Downloading k6 with kafka extension" + curl -s -L https://github.com/mostafa/xk6-kafka/releases/download/v0.26.0/xk6-kafka_v0.26.0_linux_amd64.tar.gz | tar -xz + mv dist/xk6-kafka_v0.26.0_linux_amd64 bin/k6 && rmdir dist + chmod +x bin/k6 +else + echo " k6 already installed" +fi +k6 --version diff --git a/k6-tests/ncmp/common/cmhandle-crud.js b/k6-tests/ncmp/common/cmhandle-crud.js index 88ecdb45b8..7fab62abd8 100644 --- a/k6-tests/ncmp/common/cmhandle-crud.js +++ b/k6-tests/ncmp/common/cmhandle-crud.js @@ -18,35 +18,42 @@ * ============LICENSE_END========================================================= */ -import http from 'k6/http'; -import { check, sleep } from 'k6'; -import { NCMP_BASE_URL, DMI_PLUGIN_URL, TOTAL_CM_HANDLES, MODULE_SET_TAGS, REGISTRATION_BATCH_SIZE, CONTENT_TYPE_JSON_PARAM, makeBatchOfCmHandleIds } from './utils.js'; +import { sleep } from 'k6'; +import { performPostRequest, NCMP_BASE_URL, DMI_PLUGIN_URL, TOTAL_CM_HANDLES, MODULE_SET_TAGS +} from './utils.js'; import { executeCmHandleIdSearch } from './search-base.js'; -export function registerAllCmHandles() { - forEachBatchOfCmHandles(createCmHandles); - waitForAllCmHandlesToBeReady(); +export function createCmHandles(cmHandleIds) { + const url = `${NCMP_BASE_URL}/ncmpInventory/v1/ch`; + const payload = JSON.stringify(createCmHandlePayload(cmHandleIds)); + return performPostRequest(url, payload, 'createCmHandles'); } -export function deregisterAllCmHandles() { - forEachBatchOfCmHandles(deleteCmHandles); +export function deleteCmHandles(cmHandleIds) { + const url = `${NCMP_BASE_URL}/ncmpInventory/v1/ch`; + const payload = JSON.stringify({ + "dmiPlugin": DMI_PLUGIN_URL, + "removedCmHandles": cmHandleIds, + }); + return performPostRequest(url, payload, 'deleteCmHandles'); } -function forEachBatchOfCmHandles(functionToExecute) { - const TOTAL_BATCHES = Math.ceil(TOTAL_CM_HANDLES / REGISTRATION_BATCH_SIZE); - for (let batchNumber = 0; batchNumber < TOTAL_BATCHES; batchNumber++) { - const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(REGISTRATION_BATCH_SIZE, batchNumber); - functionToExecute(nextBatchOfCmHandleIds); - } +export function waitForAllCmHandlesToBeReady() { + const POLLING_INTERVAL_SECONDS = 5; + let cmHandlesReady = 0; + do { + sleep(POLLING_INTERVAL_SECONDS); + cmHandlesReady = getNumberOfReadyCmHandles(); + console.log(`${cmHandlesReady}/${TOTAL_CM_HANDLES} CM handles are READY`); + } while (cmHandlesReady < TOTAL_CM_HANDLES); } -function createCmHandles(cmHandleIds) { - const url = `${NCMP_BASE_URL}/ncmpInventory/v1/ch`; - const payload = { +function createCmHandlePayload(cmHandleIds) { + return { "dmiPlugin": DMI_PLUGIN_URL, "createdCmHandles": cmHandleIds.map((cmHandleId, index) => ({ "cmHandle": cmHandleId, - "alternateId": `alt-${cmHandleId}`, + "alternateId": cmHandleId.replace('ch-', 'alt-'), "moduleSetTag": MODULE_SET_TAGS[index % MODULE_SET_TAGS.length], "cmHandleProperties": {"neType": "RadioNode"}, "publicCmHandleProperties": { @@ -56,30 +63,6 @@ function createCmHandles(cmHandleIds) { } })), }; - const response = http.post(url, JSON.stringify(payload), CONTENT_TYPE_JSON_PARAM); - check(response, { 'create CM-handles status equals 200': (r) => r.status === 200 }); - return response; -} - -function deleteCmHandles(cmHandleIds) { - const url = `${NCMP_BASE_URL}/ncmpInventory/v1/ch`; - const payload = { - "dmiPlugin": DMI_PLUGIN_URL, - "removedCmHandles": cmHandleIds, - }; - const response = http.post(url, JSON.stringify(payload), CONTENT_TYPE_JSON_PARAM); - check(response, { 'delete CM-handles status equals 200': (r) => r.status === 200 }); - return response; -} - -function waitForAllCmHandlesToBeReady() { - const POLLING_INTERVAL_SECONDS = 5; - let cmHandlesReady = 0; - do { - sleep(POLLING_INTERVAL_SECONDS); - cmHandlesReady = getNumberOfReadyCmHandles(); - console.log(`${cmHandlesReady}/${TOTAL_CM_HANDLES} CM handles are READY`); - } while (cmHandlesReady < TOTAL_CM_HANDLES); } function getNumberOfReadyCmHandles() { diff --git a/k6-tests/ncmp/common/passthrough-crud.js b/k6-tests/ncmp/common/passthrough-crud.js index 5617f9d093..0cd96ad64d 100644 --- a/k6-tests/ncmp/common/passthrough-crud.js +++ b/k6-tests/ncmp/common/passthrough-crud.js @@ -18,47 +18,37 @@ * ============LICENSE_END========================================================= */ -import http from 'k6/http'; +import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; import { - CONTENT_TYPE_JSON_PARAM, - getRandomCmHandleId, + performPostRequest, + performGetRequest, NCMP_BASE_URL, - TOPIC_DATA_OPERATIONS_BATCH_READ + TOPIC_DATA_OPERATIONS_BATCH_READ, + TOTAL_CM_HANDLES } from './utils.js'; -export function passthroughRead() { - const cmHandleId = getRandomCmHandleId(); +export function passthroughRead(useAlternateId) { + const cmHandleReference = getRandomCmHandleReference(useAlternateId); const resourceIdentifier = 'my-resource-identifier'; - const includeDescendants = true; const datastoreName = 'ncmp-datastore:passthrough-operational'; - const url = `${NCMP_BASE_URL}/ncmp/v1/ch/${cmHandleId}/data/ds/${datastoreName}?resourceIdentifier=${resourceIdentifier}&include-descendants=${includeDescendants}` - const response = http.get(url); - return response; -} - -export function passthroughReadWithAltId() { - const cmHandleId = getRandomCmHandleId(); - const resourceIdentifier = 'my-resource-identifier'; const includeDescendants = true; - const datastoreName = 'ncmp-datastore:passthrough-operational'; - const url = `${NCMP_BASE_URL}/ncmp/v1/ch/alt-${cmHandleId}/data/ds/${datastoreName}?resourceIdentifier=${resourceIdentifier}&include-descendants=${includeDescendants}` - const response = http.get(url); - return response; + const url = generatePassthroughUrl(cmHandleReference, datastoreName, resourceIdentifier, includeDescendants); + return performGetRequest(url, 'passthroughRead'); } -export function passthroughWrite() { - const cmHandleId = getRandomCmHandleId(); +export function passthroughWrite(useAlternateId) { + const cmHandleReference = getRandomCmHandleReference(useAlternateId); const resourceIdentifier = 'my-resource-identifier'; const datastoreName = 'ncmp-datastore:passthrough-running'; - const url = `${NCMP_BASE_URL}/ncmp/v1/ch/${cmHandleId}/data/ds/${datastoreName}?resourceIdentifier=${resourceIdentifier}` - const body = `{"neType": "BaseStation"}` - const response = http.post(url, JSON.stringify(body), CONTENT_TYPE_JSON_PARAM); - return response; + const includeDescendants = false; + const url = generatePassthroughUrl(cmHandleReference, datastoreName, resourceIdentifier, includeDescendants); + const payload = JSON.stringify({"neType": "BaseStation"}); + return performPostRequest(url, payload, 'passthroughWrite'); } export function batchRead(cmHandleIds) { - const url = `${NCMP_BASE_URL}/ncmp/v1/data?topic=${TOPIC_DATA_OPERATIONS_BATCH_READ}` - const payload = { + const url = `${NCMP_BASE_URL}/ncmp/v1/data?topic=${TOPIC_DATA_OPERATIONS_BATCH_READ}`; + const payload = JSON.stringify({ "operations": [ { "resourceIdentifier": "parent/child", @@ -69,7 +59,16 @@ export function batchRead(cmHandleIds) { "operation": "read" } ] - }; - const response = http.post(url, JSON.stringify(payload), CONTENT_TYPE_JSON_PARAM); - return response; + }); + return performPostRequest(url, payload, 'batchRead'); +} + +function getRandomCmHandleReference(useAlternateId) { + const prefix = useAlternateId ? 'alt' : 'ch'; + return `${prefix}-${randomIntBetween(1, TOTAL_CM_HANDLES)}`; +} + +function generatePassthroughUrl(cmHandleReference, datastoreName, resourceIdentifier, includeDescendants) { + const descendantsParam = includeDescendants ? `&include-descendants=${includeDescendants}` : ''; + return `${NCMP_BASE_URL}/ncmp/v1/ch/${cmHandleReference}/data/ds/${datastoreName}?resourceIdentifier=${resourceIdentifier}${descendantsParam}`; }
\ No newline at end of file diff --git a/k6-tests/ncmp/common/search-base.js b/k6-tests/ncmp/common/search-base.js index bc964856af..a6424fe5d0 100644 --- a/k6-tests/ncmp/common/search-base.js +++ b/k6-tests/ncmp/common/search-base.js @@ -18,27 +18,7 @@ * ============LICENSE_END========================================================= */ -import http from 'k6/http'; -import { NCMP_BASE_URL, CONTENT_TYPE_JSON_PARAM } from './utils.js'; - -const SEARCH_PARAMETERS_PER_SCENARIO = { - 'module': { - 'cmHandleQueryParameters': [ - { - 'conditionName': 'hasAllModules', - 'conditionParameters': [{'moduleName': 'ietf-yang-types-1'}] - } - ] - }, - 'readyCmHandles': { - 'cmHandleQueryParameters': [ - { - 'conditionName': 'cmHandleWithCpsPath', - 'conditionParameters': [{'cpsPath': '//state[@cm-handle-state="READY"]'}] - } - ] - } -}; +import {performPostRequest, NCMP_BASE_URL} from './utils.js'; export function executeCmHandleSearch(scenario) { return executeSearchRequest('searches', scenario); @@ -52,6 +32,28 @@ function executeSearchRequest(searchType, scenario) { const searchParameters = SEARCH_PARAMETERS_PER_SCENARIO[scenario]; const payload = JSON.stringify(searchParameters); const url = `${NCMP_BASE_URL}/ncmp/v1/ch/${searchType}`; - const response = http.post(url, payload, CONTENT_TYPE_JSON_PARAM); - return response; + return performPostRequest(url, payload, searchType); } + +const SEARCH_PARAMETERS_PER_SCENARIO = { + "module-and-properties": { + "cmHandleQueryParameters": [ + { + "conditionName": "hasAllModules", + "conditionParameters": [{"moduleName": "ietf-yang-types"}] + }, + { + "conditionName": "hasAllProperties", + "conditionParameters": [{"Color": "yellow"}] + } + ] + }, + "readyCmHandles": { + "cmHandleQueryParameters": [ + { + "conditionName": "cmHandleWithCpsPath", + "conditionParameters": [{"cpsPath": "//state[@cm-handle-state='READY']"}] + } + ] + } +}; diff --git a/k6-tests/ncmp/common/utils.js b/k6-tests/ncmp/common/utils.js index 294789f940..e6d9c92ee6 100644 --- a/k6-tests/ncmp/common/utils.js +++ b/k6-tests/ncmp/common/utils.js @@ -18,25 +18,19 @@ * ============LICENSE_END========================================================= */ +import http from 'k6/http'; export const NCMP_BASE_URL = 'http://localhost:8883'; export const DMI_PLUGIN_URL = 'http://ncmp-dmi-plugin-demo-and-csit-stub:8092'; export const TOTAL_CM_HANDLES = 20000; export const REGISTRATION_BATCH_SIZE = 100; export const READ_DATA_FOR_CM_HANDLE_DELAY_MS = 300; // must have same value as in docker-compose.yml export const WRITE_DATA_FOR_CM_HANDLE_DELAY_MS = 670; // must have same value as in docker-compose.yml -export const CONTENT_TYPE_JSON_PARAM = { headers: {'Content-Type': 'application/json'} }; +export const CONTENT_TYPE_JSON_PARAM = {'Content-Type': 'application/json'}; export const DATA_OPERATION_READ_BATCH_SIZE = 200; export const TOPIC_DATA_OPERATIONS_BATCH_READ = 'topic-data-operations-batch-read'; export const KAFKA_BOOTSTRAP_SERVERS = ['localhost:9092']; export const MODULE_SET_TAGS = ['tagA','tagB','tagC',' tagD'] -export function recordTimeInSeconds(functionToExecute) { - const startTimeInMillis = Date.now(); - functionToExecute(); - const endTimeInMillis = Date.now(); - const totalTimeInSeconds = (endTimeInMillis - startTimeInMillis) / 1000.0; - return totalTimeInSeconds; -} /** * Generates a batch of CM-handle IDs based on batch size and number. @@ -45,29 +39,59 @@ export function recordTimeInSeconds(functionToExecute) { * @returns {string[]} Array of CM-handle IDs, for example ['ch-201', 'ch-202' ... 'ch-300'] */ export function makeBatchOfCmHandleIds(batchSize, batchNumber) { - const batchOfIds = []; const startIndex = 1 + batchNumber * batchSize; - for (let i = 0; i < batchSize; i++) { - let cmHandleId = `ch-${startIndex + i}`; - batchOfIds.push(cmHandleId); - } - return batchOfIds; + return Array.from({ length: batchSize }, (_, i) => `ch-${startIndex + i}`); +} + +/** + * Helper function to perform POST requests with JSON payload and content type. + * @param {string} url - The URL to send the POST request to. + * @param {Object} payload - The JSON payload to send in the POST request. + * @param {string} metricTag - A tag for the metric endpoint. + * @returns {Object} The response from the HTTP POST request. + */ +export function performPostRequest(url, payload, metricTag) { + const metricTags = { + endpoint: metricTag + }; + + return http.post(url, payload, { + headers: CONTENT_TYPE_JSON_PARAM, + tags: metricTags + }); } -export function getRandomCmHandleId() { - return `ch-${Math.floor(Math.random() * TOTAL_CM_HANDLES) + 1}`; +/** + * Helper function to perform GET requests with metric tags. + * + * This function sends an HTTP GET request to the specified URL and attaches + * a metric tag to the request, which is useful for monitoring and analytics. + * + * @param {string} url - The URL to which the GET request will be sent. + * @param {string} metricTag - A string representing the metric tag to associate with the request. + * This tag is used for monitoring and tracking the request. + * @returns {Object} The response from the HTTP GET request. The response includes the status code, + * headers, body, and other related information. + */ +export function performGetRequest(url, metricTag) { + const metricTags = { + endpoint: metricTag + }; + return http.get(url, {tags: metricTags}); } export function makeCustomSummaryReport(data, options) { const summaryCsvLines = [ '#,Test Name,Unit,Limit,Actual', + makeSummaryCsvLine('0', 'HTTP request failures for all tests', 'rate of failed requests', 'http_req_failed', data, options), makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', data, options), makeSummaryCsvLine('2', 'De-registration of CM-handles', 'CM-handles/second', 'cmhandles_deleted_per_second', data, options), - makeSummaryCsvLine('3', 'CM-handle ID search with Module filter', 'milliseconds', 'http_req_duration{scenario:id_search_module}', data, options), - makeSummaryCsvLine('4', 'CM-handle search with Module filter', 'milliseconds', 'http_req_duration{scenario:cm_search_module}', data, options), + makeSummaryCsvLine('3', 'CM-handle ID search with Module and Property filter', 'milliseconds', 'id_search_duration', data, options), + makeSummaryCsvLine('4', 'CM-handle search with Module and Property filter', 'milliseconds', 'cm_search_duration', data, options), makeSummaryCsvLine('5a', 'NCMP overhead for Synchronous single CM-handle pass-through read', 'milliseconds', 'ncmp_overhead_passthrough_read', data, options), makeSummaryCsvLine('5b', 'NCMP overhead for Synchronous single CM-handle pass-through read with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_read_alt_id', data, options), - makeSummaryCsvLine('6', 'NCMP overhead for Synchronous single CM-handle pass-through write', 'milliseconds', 'ncmp_overhead_passthrough_write', data, options), + makeSummaryCsvLine('6a', 'NCMP overhead for Synchronous single CM-handle pass-through write', 'milliseconds', 'ncmp_overhead_passthrough_write', data, options), + makeSummaryCsvLine('6b', 'NCMP overhead for Synchronous single CM-handle pass-through write with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_write_alt_id', data, options), makeSummaryCsvLine('7', 'Data operations batch read', 'events/second', 'data_operations_batch_read_cmhandles_per_second', data, options), ]; return summaryCsvLines.join('\n') + '\n'; diff --git a/k6-tests/ncmp/ncmp-kpi.js b/k6-tests/ncmp/ncmp-kpi.js index f4a44dba68..1d084f21e3 100644 --- a/k6-tests/ncmp/ncmp-kpi.js +++ b/k6-tests/ncmp/ncmp-kpi.js @@ -19,23 +19,26 @@ */ import { check } from 'k6'; -import { Gauge, Trend } from 'k6/metrics'; +import { Trend } from 'k6/metrics'; import { Reader } from 'k6/x/kafka'; import { TOTAL_CM_HANDLES, READ_DATA_FOR_CM_HANDLE_DELAY_MS, WRITE_DATA_FOR_CM_HANDLE_DELAY_MS, - makeCustomSummaryReport, recordTimeInSeconds, makeBatchOfCmHandleIds, DATA_OPERATION_READ_BATCH_SIZE, - TOPIC_DATA_OPERATIONS_BATCH_READ, KAFKA_BOOTSTRAP_SERVERS + makeCustomSummaryReport, makeBatchOfCmHandleIds, DATA_OPERATION_READ_BATCH_SIZE, + TOPIC_DATA_OPERATIONS_BATCH_READ, KAFKA_BOOTSTRAP_SERVERS, REGISTRATION_BATCH_SIZE } from './common/utils.js'; -import { registerAllCmHandles, deregisterAllCmHandles } from './common/cmhandle-crud.js'; +import { createCmHandles, deleteCmHandles, waitForAllCmHandlesToBeReady } from './common/cmhandle-crud.js'; import { executeCmHandleSearch, executeCmHandleIdSearch } from './common/search-base.js'; import { passthroughRead, passthroughReadWithAltId, passthroughWrite, batchRead } from './common/passthrough-crud.js'; -let cmHandlesCreatedPerSecondGauge = new Gauge('cmhandles_created_per_second'); -let cmHandlesDeletedPerSecondGauge = new Gauge('cmhandles_deleted_per_second'); +let cmHandlesCreatedPerSecondTrend = new Trend('cmhandles_created_per_second', false); +let cmHandlesDeletedPerSecondTrend = new Trend('cmhandles_deleted_per_second', false); let passthroughReadNcmpOverheadTrend = new Trend('ncmp_overhead_passthrough_read', true); let passthroughReadNcmpOverheadTrendWithAlternateId = new Trend('ncmp_overhead_passthrough_read_alt_id', true); let passthroughWriteNcmpOverheadTrend = new Trend('ncmp_overhead_passthrough_write', true); -let dataOperationsBatchReadCmHandlePerSecondTrend = new Trend('data_operations_batch_read_cmhandles_per_second'); +let passthroughWriteNcmpOverheadTrendWithAlternateId = new Trend('ncmp_overhead_passthrough_write_alt_id', true); +let idSearchDurationTrend = new Trend('id_search_duration', true); +let cmSearchDurationTrend = new Trend('cm_search_duration', true); +let dataOperationsBatchReadCmHandlePerSecondTrend = new Trend('data_operations_batch_read_cmhandles_per_second', false); const reader = new Reader({ brokers: KAFKA_BOOTSTRAP_SERVERS, @@ -45,37 +48,43 @@ const reader = new Reader({ const DURATION = '15m'; export const options = { - setupTimeout: '6m', + setupTimeout: '8m', teardownTimeout: '6m', scenarios: { passthrough_read: { executor: 'constant-vus', - exec: 'passthrough_read', - vus: 9, + exec: 'executePassthroughReadScenario', + vus: 4, duration: DURATION, }, passthrough_read_alt_id: { executor: 'constant-vus', - exec: 'passthrough_read_alt_id', - vus: 1, + exec: 'executePassthroughReadAltIdScenario', + vus: 4, duration: DURATION, }, passthrough_write: { executor: 'constant-vus', - exec: 'passthrough_write', - vus: 10, + exec: 'executePassthroughWriteScenario', + vus: 4, duration: DURATION, }, - id_search_module: { + passthrough_write_alt_id: { executor: 'constant-vus', - exec: 'id_search_module', - vus: 3, + exec: 'executePassthroughWriteAltIdScenario', + vus: 4, duration: DURATION, }, - cm_search_module: { + cm_handle_id_search: { executor: 'constant-vus', - exec: 'cm_search_module', - vus: 3, + exec: 'executeCmHandleIdSearchScenario', + vus: 5, + duration: DURATION, + }, + cm_handle_search: { + executor: 'constant-vus', + exec: 'executeCmHandleSearchScenario', + vus: 5, duration: DURATION, }, data_operation_send_async_http_request: { @@ -96,69 +105,108 @@ export const options = { } }, thresholds: { - 'cmhandles_created_per_second': ['value >= 22'], - 'cmhandles_deleted_per_second': ['value >= 22'], - 'ncmp_overhead_passthrough_read': ['avg <= 100'], - 'ncmp_overhead_passthrough_read_alt_id': ['avg <= 100'], - 'ncmp_overhead_passthrough_write': ['avg <= 100'], - 'http_req_duration{scenario:id_search_module}': ['avg <= 625'], - 'http_req_duration{scenario:cm_search_module}': ['avg <= 13000'], - 'http_req_failed{scenario:id_search_module}': ['rate == 0'], - 'http_req_failed{scenario:cm_search_module}': ['rate == 0'], - 'http_req_failed{scenario:passthrough_read}': ['rate == 0'], - 'http_req_failed{scenario:passthrough_write}': ['rate == 0'], - 'http_req_failed{scenario:data_operation_send_async_http_request}': ['rate == 0'], - 'kafka_reader_error_count{scenario:data_operation_consume_kafka_responses}': ['count == 0'], + 'http_req_failed': ['rate == 0'], + 'cmhandles_created_per_second': ['avg >= 22'], + 'cmhandles_deleted_per_second': ['avg >= 22'], + 'ncmp_overhead_passthrough_read': ['avg <= 40'], + 'ncmp_overhead_passthrough_write': ['avg <= 40'], + 'ncmp_overhead_passthrough_read_alt_id': ['avg <= 40'], + 'ncmp_overhead_passthrough_write_alt_id': ['avg <= 40'], + 'id_search_duration': ['avg <= 2000'], + 'cm_search_duration': ['avg <= 15000'], 'data_operations_batch_read_cmhandles_per_second': ['avg >= 150'], }, }; export function setup() { - const totalRegistrationTimeInSeconds = recordTimeInSeconds(registerAllCmHandles); - cmHandlesCreatedPerSecondGauge.add(TOTAL_CM_HANDLES / totalRegistrationTimeInSeconds); + const startTimeInMillis = Date.now(); + + const TOTAL_BATCHES = Math.ceil(TOTAL_CM_HANDLES / REGISTRATION_BATCH_SIZE); + for (let batchNumber = 0; batchNumber < TOTAL_BATCHES; batchNumber++) { + const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(REGISTRATION_BATCH_SIZE, batchNumber); + const response = createCmHandles(nextBatchOfCmHandleIds); + check(response, { 'create CM-handles status equals 200': (r) => r.status === 200 }); + } + + waitForAllCmHandlesToBeReady(); + + const endTimeInMillis = Date.now(); + const totalRegistrationTimeInSeconds = (endTimeInMillis - startTimeInMillis) / 1000.0; + + cmHandlesCreatedPerSecondTrend.add(TOTAL_CM_HANDLES / totalRegistrationTimeInSeconds); } export function teardown() { - const totalDeregistrationTimeInSeconds = recordTimeInSeconds(deregisterAllCmHandles); - cmHandlesDeletedPerSecondGauge.add(TOTAL_CM_HANDLES / totalDeregistrationTimeInSeconds); + const startTimeInMillis = Date.now(); + + let DEREGISTERED_CM_HANDLES = 0 + const TOTAL_BATCHES = Math.ceil(TOTAL_CM_HANDLES / REGISTRATION_BATCH_SIZE); + for (let batchNumber = 0; batchNumber < TOTAL_BATCHES; batchNumber++) { + const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(REGISTRATION_BATCH_SIZE, batchNumber); + const response = deleteCmHandles(nextBatchOfCmHandleIds); + if (response.error_code === 0) { + DEREGISTERED_CM_HANDLES += REGISTRATION_BATCH_SIZE + } + check(response, { 'delete CM-handles status equals 200': (r) => r.status === 200 }); + } + + const endTimeInMillis = Date.now(); + const totalDeregistrationTimeInSeconds = (endTimeInMillis - startTimeInMillis) / 1000.0; + + cmHandlesDeletedPerSecondTrend.add(DEREGISTERED_CM_HANDLES / totalDeregistrationTimeInSeconds); +} + +export function executePassthroughReadScenario() { + const response = passthroughRead(false); + if (check(response, { 'passthrough read status equals 200': (r) => r.status === 200 })) { + const overhead = response.timings.duration - READ_DATA_FOR_CM_HANDLE_DELAY_MS; + passthroughReadNcmpOverheadTrend.add(overhead); + } } -export function passthrough_read() { - const response = passthroughRead(); - check(response, { 'passthrough read status equals 200': (r) => r.status === 200 }); - const overhead = response.timings.duration - READ_DATA_FOR_CM_HANDLE_DELAY_MS; - passthroughReadNcmpOverheadTrend.add(overhead); +export function executePassthroughReadAltIdScenario() { + const response = passthroughRead(true); + if (check(response, { 'passthrough read with alternate Id status equals 200': (r) => r.status === 200 })) { + const overhead = response.timings.duration - READ_DATA_FOR_CM_HANDLE_DELAY_MS; + passthroughReadNcmpOverheadTrendWithAlternateId.add(overhead); + } } -export function passthrough_read_alt_id() { - const response = passthroughReadWithAltId(); - check(response, { 'passthrough read with alternate Id status equals 200': (r) => r.status === 200 }); - const overhead = response.timings.duration - READ_DATA_FOR_CM_HANDLE_DELAY_MS; - passthroughReadNcmpOverheadTrendWithAlternateId.add(overhead); +export function executePassthroughWriteScenario() { + const response = passthroughWrite(false); + if (check(response, { 'passthrough write status equals 201': (r) => r.status === 201 })) { + const overhead = response.timings.duration - WRITE_DATA_FOR_CM_HANDLE_DELAY_MS; + passthroughWriteNcmpOverheadTrend.add(overhead); + } } -export function passthrough_write() { - const response = passthroughWrite(); - check(response, { 'passthrough write status equals 201': (r) => r.status === 201 }); - const overhead = response.timings.duration - WRITE_DATA_FOR_CM_HANDLE_DELAY_MS; - passthroughWriteNcmpOverheadTrend.add(overhead); +export function executePassthroughWriteAltIdScenario() { + const response = passthroughWrite(true); + if (check(response, { 'passthrough write with alternate Id status equals 201': (r) => r.status === 201 })) { + const overhead = response.timings.duration - WRITE_DATA_FOR_CM_HANDLE_DELAY_MS; + passthroughWriteNcmpOverheadTrendWithAlternateId.add(overhead); + } } -export function id_search_module() { - const response = executeCmHandleIdSearch('module'); - check(response, { 'module ID search status equals 200': (r) => r.status === 200 }); - check(JSON.parse(response.body), { 'module ID search returned expected CM-handles': (arr) => arr.length === TOTAL_CM_HANDLES }); +export function executeCmHandleIdSearchScenario() { + const response = executeCmHandleIdSearch('module-and-properties'); + if (check(response, { 'CM handle ID search status equals 200': (r) => r.status === 200 }) + && check(response, { 'CM handle ID search returned expected CM-handles': (r) => r.json('#') === TOTAL_CM_HANDLES })) { + idSearchDurationTrend.add(response.timings.duration); + } } -export function cm_search_module() { - const response = executeCmHandleSearch('module'); - check(response, { 'module search status equals 200': (r) => r.status === 200 }); - check(JSON.parse(response.body), { 'module search returned expected CM-handles': (arr) => arr.length === TOTAL_CM_HANDLES }); +export function executeCmHandleSearchScenario() { + const response = executeCmHandleSearch('module-and-properties'); + if (check(response, { 'CM handle search status equals 200': (r) => r.status === 200 }) + && check(response, { 'CM handle search returned expected CM-handles': (r) => r.json('#') === TOTAL_CM_HANDLES })) { + cmSearchDurationTrend.add(response.timings.duration); + } } export function data_operation_send_async_http_request() { - const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(DATA_OPERATION_READ_BATCH_SIZE,1); - const response = batchRead(nextBatchOfCmHandleIds) + const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(DATA_OPERATION_READ_BATCH_SIZE, 0); + const response = batchRead(nextBatchOfCmHandleIds); check(response, { 'data operation batch read status equals 200': (r) => r.status === 200 }); } diff --git a/k6-tests/ncmp/run-all-tests.sh b/k6-tests/ncmp/run-all-tests.sh index 2db32ecd76..1fa661a472 100755 --- a/k6-tests/ncmp/run-all-tests.sh +++ b/k6-tests/ncmp/run-all-tests.sh @@ -19,7 +19,9 @@ pushd "$(dirname "$0")" >/dev/null || exit 1 number_of_failures=0 echo "Running K6 performance tests..." -k6 --quiet run ncmp-kpi.js > summary.csv || ((number_of_failures++)) + +# Redirecting stderr to /dev/null to prevent large log files +k6 --quiet run ncmp-kpi.js > summary.csv 2>/dev/null || ((number_of_failures++)) if [ -f summary.csv ]; then diff --git a/k6-tests/run-k6-tests.sh b/k6-tests/run-k6-tests.sh index 9b8747b1ff..b1ad38911a 100755 --- a/k6-tests/run-k6-tests.sh +++ b/k6-tests/run-k6-tests.sh @@ -31,6 +31,10 @@ trap on_exit EXIT pushd "$(dirname "$0")" || exit 1 +# Install needed dependencies. +source install-deps.sh + +# Run k6 test suite. ./setup.sh ./ncmp/run-all-tests.sh NCMP_RESULT=$? diff --git a/k6-tests/teardown.sh b/k6-tests/teardown.sh index 1b4d721a23..7693dc03a4 100755 --- a/k6-tests/teardown.sh +++ b/k6-tests/teardown.sh @@ -19,4 +19,10 @@ echo '================================== docker info ==========================' docker ps -a echo 'Stopping, Removing containers and volumes...' -docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub down --volumes +docker_compose_cmd="docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub down --volumes" +# Set an environment variable CLEAN_DOCKER_IMAGES=1 to also remove docker images when done (used on jenkins job) +if [ "${CLEAN_DOCKER_IMAGES:-0}" -eq 1 ]; then + $docker_compose_cmd --rmi all +else + $docker_compose_cmd +fi diff --git a/spotbugs/src/main/resources/spotbugs-exclude.xml b/spotbugs/src/main/resources/spotbugs-exclude.xml index 78f61d290e..23e62cdbc7 100644 --- a/spotbugs/src/main/resources/spotbugs-exclude.xml +++ b/spotbugs/src/main/resources/spotbugs-exclude.xml @@ -36,6 +36,7 @@ <Bug pattern="NP_NULL_PARAM_DEREF" /> <Bug pattern="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" /> <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" /> + <Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" /> <!-- https://stackoverflow.com/a/34674776. Doesn't detect Lombok All Args Constructor variables being used with map get key and value, which can lead to spotbugs being detected on used fields --> |