diff options
104 files changed, 1633 insertions, 722 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml index b1c4379ee..9ecfa2d32 100644 --- a/checkstyle/pom.xml +++ b/checkstyle/pom.xml @@ -26,7 +26,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>checkstyle</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <profiles> <profile> diff --git a/cps-application/pom.xml b/cps-application/pom.xml index e6e2f9a2a..6804c7de6 100644 --- a/cps-application/pom.xml +++ b/cps-application/pom.xml @@ -28,7 +28,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index aeec8a255..810068081 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -2,6 +2,7 @@ # Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021-2022 Bell Canada # Modifications Copyright (C) 2021-2024 Nordix Foundation +# Modifications Copyright (C) 2024 TechMahindra Ltd # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -113,6 +114,11 @@ app: topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events} device-heartbeat: topic: ${DMI_DEVICE_HEARTBEAT_TOPIC:dmi-device-heartbeat} + cps: + data-updated: + change-event-notifications-enabled: ${CPS_CHANGE_EVENT_NOTIFICATIONS_ENABLED:true} + topic: ${CPS_CHANGE_EVENT_TOPIC:cps-data-updated-events} + notification: @@ -138,14 +144,12 @@ springdoc: - name: cps-ncmp-inventory url: /api-docs/cps-ncmp/openapi-inventory.yaml - - security: # comma-separated uri patterns which do not require authorization permit-uri: /actuator/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/**,/v3/api-docs/** auth: - username: ${CPS_USERNAME} - password: ${CPS_PASSWORD} + username: ${CPS_USERNAME:cpsuser} + password: ${CPS_PASSWORD:cpsr0cks!} # Actuator management: @@ -174,10 +178,9 @@ ncmp: maximumConnectionsPerRoute: 50 maximumConnectionsTotal: 100 idleConnectionEvictionThresholdInSeconds: 5 - maximumInMemorySizeInMegabytes: 16 auth: - username: ${DMI_USERNAME} - password: ${DMI_PASSWORD} + username: ${DMI_USERNAME:cpsuser} + password: ${DMI_PASSWORD:cpsr0cks!} enabled: ${DMI_AUTH_ENABLED:true} api: base-path: dmi diff --git a/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java b/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java index ec16ceefe..c18a3ed10 100644 --- a/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java +++ b/cps-application/src/test/java/org/onap/cps/architecture/LayeredArchitectureTest.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * Copyright (C) 2021-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. @@ -45,6 +45,7 @@ public class LayeredArchitectureTest { private static final String CPS_UTILS_PACKAGE = "org.onap.cps.utils.."; private static final String NCMP_INIT_PACKAGE = "org.onap.cps.ncmp.init.."; private static final String CPS_CACHE_PACKAGE = "org.onap.cps.cache.."; + private static final String CPS_EVENTS_PACKAGE = "org.onap.cps.events.."; @ArchTest static final ArchRule restControllerShouldOnlyDependOnRestController = @@ -57,7 +58,7 @@ public class LayeredArchitectureTest { .or().resideInAPackage(SPI_SERVICE_PACKAGE).should().onlyHaveDependentClassesThat() .resideInAnyPackage(REST_CONTROLLER_PACKAGE, API_SERVICE_PACKAGE, SPI_SERVICE_PACKAGE, NCMP_REST_PACKAGE, NCMP_SERVICE_PACKAGE, YANG_SCHEMA_PACKAGE, NOTIFICATION_PACKAGE, CPS_UTILS_PACKAGE, NCMP_INIT_PACKAGE, - CPS_CACHE_PACKAGE)); + CPS_CACHE_PACKAGE, CPS_EVENTS_PACKAGE)); @ArchTest diff --git a/cps-bom/pom.xml b/cps-bom/pom.xml index 366d4c72f..4548eb21c 100644 --- a/cps-bom/pom.xml +++ b/cps-bom/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-bom</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <packaging>pom</packaging> <description>This artifact contains dependencyManagement declarations of all published CPS components.</description> diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index b7ec70b33..effaa06f6 100644 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -27,7 +27,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-dependencies</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <packaging>pom</packaging> <name>${project.groupId}:${project.artifactId}</name> @@ -143,6 +143,12 @@ <version>5.3.1</version> </dependency> <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <version>4.12.0</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit-junit5</artifactId> <version>1.2.0</version> diff --git a/cps-events/pom.xml b/cps-events/pom.xml index 680d0bbad..ec0c96bc2 100644 --- a/cps-events/pom.xml +++ b/cps-events/pom.xml @@ -24,7 +24,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json b/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json index 18f83ccf8..a3eaf63fa 100644 --- a/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json +++ b/cps-events/src/main/resources/schemas/updatenode/cps-data-updated-event-schema-1.0.0.json @@ -12,7 +12,7 @@ "type": "object", "properties": { "observedTimestamp": { - "description": "The timestamp when the data has been observed. The expected format is 'yyyy-MM-dd'T'HH:mm:ss.SSSZ'. Ex: '2020-12-01T00:00:00.000+0000' ", + "description": "The timestamp when the data has been observed. The expected format is 'yyyy-MM-dd'T'HH:mm:ss.SSSZ'. Ex: '2024-02-12T09:35:46.143+0530' ", "type": "string" }, "dataspaceName": { diff --git a/cps-ncmp-events/pom.xml b/cps-ncmp-events/pom.xml index e12ab3bba..3804e5bd2 100644 --- a/cps-ncmp-events/pom.xml +++ b/cps-ncmp-events/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml index ae755a314..7b277cdb3 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-ncmp-rest-stub</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> </parent> <artifactId>cps-ncmp-rest-stub-app</artifactId> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml index b6c461c6d..cb655320f 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml @@ -21,7 +21,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-ncmp-rest-stub</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> </parent> <artifactId>cps-ncmp-rest-stub-service</artifactId> diff --git a/cps-ncmp-rest-stub/pom.xml b/cps-ncmp-rest-stub/pom.xml index c2c789e5b..441ce43f4 100644 --- a/cps-ncmp-rest-stub/pom.xml +++ b/cps-ncmp-rest-stub/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index 453da8fea..e33334479 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java index 453abcabb..5467eeffc 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java @@ -96,8 +96,9 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor private boolean allRegistrationsSuccessful( final DmiPluginRegistrationErrorResponse dmiPluginRegistrationErrorResponse) { return dmiPluginRegistrationErrorResponse.getFailedCreatedCmHandles().isEmpty() - && dmiPluginRegistrationErrorResponse.getFailedUpdatedCmHandles().isEmpty() - && dmiPluginRegistrationErrorResponse.getFailedRemovedCmHandles().isEmpty(); + && dmiPluginRegistrationErrorResponse.getFailedUpdatedCmHandles().isEmpty() + && dmiPluginRegistrationErrorResponse.getFailedRemovedCmHandles().isEmpty() + && dmiPluginRegistrationErrorResponse.getFailedUpgradeCmHandles().isEmpty(); } private DmiPluginRegistrationErrorResponse getFailureRegistrationResponse( diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy index 1d03be107..7b850a7ff 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy @@ -201,7 +201,8 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { def dmiRegistrationResponse = new DmiPluginRegistrationResponse( createdCmHandles: [createCmHandleResponse], updatedCmHandles: [updateCmHandleResponse], - removedCmHandles: [removeCmHandleResponse] + removedCmHandles: [removeCmHandleResponse], + upgradedCmHandles: [upgradeCmHandleResponse] ) mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse when: 'registration endpoint is invoked' @@ -218,15 +219,18 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { responseBody.getFailedCreatedCmHandles() == expectedFailedCreatedCmHandle responseBody.getFailedUpdatedCmHandles() == expectedFailedUpdateCmHandle responseBody.getFailedRemovedCmHandles() == expectedFailedRemovedCmHandle + responseBody.getFailedUpgradeCmHandles() == expectedFailedUpgradedCmHandle where: - scenario | createCmHandleResponse | updateCmHandleResponse | removeCmHandleResponse || expectedFailedCreatedCmHandle | expectedFailedUpdateCmHandle | expectedFailedRemovedCmHandle - 'only create failed' | expectedFailedResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') || [expectedUnknownErrorResponse('cm-handle-1')] | [] | [] - 'only update failed' | expectedSuccessResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') || [] | [expectedUnknownErrorResponse('cm-handle-2')] | [] - 'only delete failed' | expectedSuccessResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') || [] | [] | [expectedUnknownErrorResponse('cm-handle-3')] - 'all three failed' | expectedFailedResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') || [expectedUnknownErrorResponse('cm-handle-1')] | [expectedUnknownErrorResponse('cm-handle-2')] | [expectedUnknownErrorResponse('cm-handle-3')] - 'create update failed' | expectedFailedResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') || [expectedUnknownErrorResponse('cm-handle-1')] | [expectedUnknownErrorResponse('cm-handle-2')] | [] - 'create delete failed' | expectedFailedResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') || [expectedUnknownErrorResponse('cm-handle-1')] | [] | [expectedUnknownErrorResponse('cm-handle-3')] - 'update delete failed' | expectedSuccessResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') || [] | [expectedUnknownErrorResponse('cm-handle-2')] | [expectedUnknownErrorResponse('cm-handle-3')] + scenario | createCmHandleResponse | updateCmHandleResponse | removeCmHandleResponse | upgradeCmHandleResponse || expectedFailedCreatedCmHandle | expectedFailedUpdateCmHandle | expectedFailedRemovedCmHandle | expectedFailedUpgradedCmHandle + 'only create failed' | expectedFailedResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') | expectedSuccessResponse('cm-handle-4') || [expectedUnknownErrorResponse('cm-handle-1')] | [] | [] | [] + 'only update failed' | expectedSuccessResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') | expectedSuccessResponse('cm-handle-4') || [] | [expectedUnknownErrorResponse('cm-handle-2')] | [] | [] + 'only delete failed' | expectedSuccessResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') | expectedSuccessResponse('cm-handle-4') || [] | [] | [expectedUnknownErrorResponse('cm-handle-3')] | [] + 'only upgrade failed' | expectedSuccessResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') | expectedFailedResponse('cm-handle-4') || [] | [] | [] | [expectedUnknownErrorResponse('cm-handle-4')] + 'all four failed' | expectedFailedResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') | expectedFailedResponse('cm-handle-4') || [expectedUnknownErrorResponse('cm-handle-1')] | [expectedUnknownErrorResponse('cm-handle-2')] | [expectedUnknownErrorResponse('cm-handle-3')] | [expectedUnknownErrorResponse('cm-handle-4')] + 'create update failed' | expectedFailedResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedSuccessResponse('cm-handle-3') | expectedSuccessResponse('cm-handle-4') || [expectedUnknownErrorResponse('cm-handle-1')] | [expectedUnknownErrorResponse('cm-handle-2')] | [] | [] + 'create delete failed' | expectedFailedResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') | expectedSuccessResponse('cm-handle-4') || [expectedUnknownErrorResponse('cm-handle-1')] | [] | [expectedUnknownErrorResponse('cm-handle-3')] | [] + 'update delete failed' | expectedSuccessResponse('cm-handle-1') | expectedFailedResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') | expectedSuccessResponse('cm-handle-4') || [] | [expectedUnknownErrorResponse('cm-handle-2')] | [expectedUnknownErrorResponse('cm-handle-3')] | [] + 'delete upgrade failed' | expectedSuccessResponse('cm-handle-1') | expectedSuccessResponse('cm-handle-2') | expectedFailedResponse('cm-handle-3') | expectedFailedResponse('cm-handle-4') || [] | [] | [expectedUnknownErrorResponse('cm-handle-3')] | [expectedUnknownErrorResponse('cm-handle-4')] } def 'Get all cm handle IDs by DMI plugin identifier.'() { diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index 04864c458..fc41da3ae 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -70,8 +70,8 @@ <artifactId>mapstruct-processor</artifactId> </dependency> <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-webflux</artifactId> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> </dependency> <!-- T E S T - D E P E N D E N C I E S --> <dependency> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java index 6df0e4994..798a280c8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java @@ -22,29 +22,28 @@ package org.onap.cps.ncmp.api.impl.client; import com.fasterxml.jackson.databind.JsonNode; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Locale; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration.DmiProperties; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; import org.onap.cps.ncmp.api.impl.operations.OperationType; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.client.RestTemplate; @Component @RequiredArgsConstructor @Slf4j public class DmiRestClient { + private static final String HEALTH_CHECK_URL_EXTENSION = "/actuator/health"; private static final String NOT_SPECIFIED = ""; - private static final String NO_AUTHORIZATION = null; - private final WebClient webClient; + private final RestTemplate restTemplate; private final DmiProperties dmiProperties; /** @@ -60,19 +59,14 @@ public class DmiRestClient { final String requestBodyAsJsonString, final OperationType operationType, final String authorization) { + final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders(), + authorization)); try { - return webClient.post().uri(new URI(dmiResourceUrl)) - .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization)) - .body(BodyInserters.fromValue(requestBodyAsJsonString)) - .retrieve() - .toEntity(Object.class) - .block(); + return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class); } catch (final HttpStatusCodeException httpStatusCodeException) { final String exceptionMessage = "Unable to " + operationType.toString() + " resource data."; throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(), - httpStatusCodeException.getStatusCode().value()); - } catch (final URISyntaxException ex) { - throw new RuntimeException(ex); + httpStatusCodeException.getStatusCode().value()); } } @@ -83,14 +77,13 @@ public class DmiRestClient { * @return plugin health status ("UP" is all OK, "" (not-specified) in case of any exception) */ public String getDmiHealthStatus(final String dmiPluginBaseUrl) { + final HttpEntity<Object> httpHeaders = new HttpEntity<>(configureHttpHeaders(new HttpHeaders(), null)); try { - final JsonNode responseHealthStatus = webClient.get() - .uri(new URI(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION)) - .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION)) - .retrieve() - .bodyToMono(JsonNode.class).block(); + final JsonNode responseHealthStatus = + restTemplate.getForObject(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION, + JsonNode.class, httpHeaders); return responseHealthStatus == null ? NOT_SPECIFIED : - responseHealthStatus.get("status").asText(); + responseHealthStatus.get("status").asText(); } catch (final Exception e) { log.warn("Failed to retrieve health status from {}. Error Message: {}", dmiPluginBaseUrl, e.getMessage()); return NOT_SPECIFIED; @@ -103,6 +96,7 @@ public class DmiRestClient { } else if (authorization != null && authorization.toLowerCase(Locale.getDefault()).startsWith("bearer ")) { httpHeaders.add(HttpHeaders.AUTHORIZATION, authorization); } + httpHeaders.setContentType(MediaType.APPLICATION_JSON); return httpHeaders; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java deleted file mode 100644 index 8ac2e3fc1..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java +++ /dev/null @@ -1,88 +0,0 @@ - -/* - * ============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.impl.config; - -import io.netty.channel.ChannelOption; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutHandler; -import java.util.concurrent.TimeUnit; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.netty.http.client.HttpClient; - -@Slf4j -@Configuration -@RequiredArgsConstructor -public class DmiWebClientConfiguration { - - @Value("${ncmp.dmi.httpclient.connectionTimeoutInSeconds:20000}") - private Integer connectionTimeoutInSeconds; - - @Value("${ncmp.dmi.httpclient.maximumInMemorySizeInMegabytes:1}") - private Integer maximumInMemorySizeInMegabytes; - - @Getter - @Component - public static class DmiProperties { - @Value("${ncmp.dmi.auth.username}") - private String authUsername; - @Value("${ncmp.dmi.auth.password}") - private String authPassword; - @Value("${ncmp.dmi.api.base-path}") - private String dmiBasePath; - @Value("${ncmp.dmi.auth.enabled}") - private boolean dmiBasicAuthEnabled; - } - - /** - * Configures and create a WebClient bean. - * - * @return a WebClient instance. - */ - @Bean - public WebClient webClient() { - final var httpClient = HttpClient.create() - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutInSeconds * 1000) - .doOnConnected(connection -> - connection - .addHandlerLast(new ReadTimeoutHandler(connectionTimeoutInSeconds, TimeUnit.SECONDS)) - .addHandlerLast(new WriteTimeoutHandler(connectionTimeoutInSeconds, TimeUnit.SECONDS))); - - return WebClient.builder() - .defaultHeaders(header -> header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)) - .defaultHeaders(header -> header.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) - .clientConnector(new ReactorClientHttpConnector(httpClient)) - .codecs(configurer -> configurer - .defaultCodecs() - .maxInMemorySize(maximumInMemorySizeInMegabytes * 1024 * 1024)) - .build(); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java index 729930e65..d547e31c6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java @@ -1,4 +1,4 @@ -/*- +/* * ============LICENSE_START======================================================= * Copyright (C) 2023 Nordix Foundation. * ================================================================================ diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java index 32852b326..c6ff116a7 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl.config; import java.util.Arrays; import lombok.AccessLevel; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -30,6 +31,7 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -40,6 +42,7 @@ import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Configuration @@ -47,6 +50,19 @@ import org.springframework.web.client.RestTemplate; @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class NcmpConfiguration { + @Getter + @Component + public static class DmiProperties { + @Value("${ncmp.dmi.auth.username}") + private String authUsername; + @Value("${ncmp.dmi.auth.password}") + private String authPassword; + @Value("${ncmp.dmi.api.base-path}") + private String dmiBasePath; + @Value("${ncmp.dmi.auth.enabled}") + private boolean dmiBasicAuthEnabled; + } + /** * Rest template bean. * diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandler.java new file mode 100644 index 000000000..50a5df537 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandler.java @@ -0,0 +1,70 @@ +/* + * ============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.impl.events.cmsubscription; + +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent; +import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CmNotificationSubscriptionEventsHandler { + private final CmNotificationSubscriptionNcmpOutEventProducer cmNotificationSubscriptionNcmpOutEventProducer; + private final CmNotificationSubscriptionDmiInEventProducer cmNotificationSubscriptionDmiInEventProducer; + + /** + * Publish the event to the client who requested the subscription with key as subscription id and event is Cloud + * Event compliant. + * + * @param subscriptionId Cm Subscription id + * @param eventType Type of event + * @param cmNotificationSubscriptionNcmpOutEvent Cm Notification Subscription Event for the + * client + * @param isScheduledEvent Determines if the event is to be scheduled + * or published now + */ + public void publishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, final String eventType, + final CmNotificationSubscriptionNcmpOutEvent + cmNotificationSubscriptionNcmpOutEvent, + final boolean isScheduledEvent) { + cmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + eventType, cmNotificationSubscriptionNcmpOutEvent, isScheduledEvent); + } + + /** + * Publish the event to the provided dmi plugin with key as subscription id and the event is in Cloud Event format. + * + * @param subscriptionId Cm Subscription id + * @param dmiPluginName Dmi Plugin Name + * @param eventType Type of event + * @param cmNotificationSubscriptionDmiInEvent Cm Notification Subscription event for Dmi + */ + public void publishCmNotificationSubscriptionDmiInEvent(final String subscriptionId, final String dmiPluginName, + final String eventType, + final CmNotificationSubscriptionDmiInEvent + cmNotificationSubscriptionDmiInEvent) { + cmNotificationSubscriptionDmiInEventProducer.publishCmNotificationSubscriptionDmiInEvent(subscriptionId, + dmiPluginName, eventType, cmNotificationSubscriptionDmiInEvent); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandler.java new file mode 100644 index 000000000..73f9563ec --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandler.java @@ -0,0 +1,78 @@ +/* + * ============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.impl.events.cmsubscription; + +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent; +import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CmNotificationSubscriptionMappersHandler { + + private final CmNotificationSubscriptionDmiInEventMapper cmNotificationSubscriptionDmiInEventMapper; + private final CmNotificationSubscriptionNcmpOutEventMapper cmNotificationSubscriptionNcmpOutEventMapper; + + /** + * Mapper to form a request for the DMI Plugin for the Cm Notification Subscription. + * + * @param dmiCmNotificationSubscriptionPredicates Collection of Cm Notification Subscription predicates + * @return cm notification subscription dmi in event + */ + public CmNotificationSubscriptionDmiInEvent toCmNotificationSubscriptionDmiInEvent( + final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates) { + return cmNotificationSubscriptionDmiInEventMapper.toCmNotificationSubscriptionDmiInEvent( + dmiCmNotificationSubscriptionPredicates); + } + + /** + * Mapper to form a response for the client for the Cm Notification Subscription. + * + * @param subscriptionId Cm Notification Subscription id + * @param dmiCmNotificationSubscriptionDetailsMap contains CmNotificationSubscriptionDetails per dmi plugin + * @return CmNotificationSubscriptionNcmpOutEvent to sent back to the client + */ + public CmNotificationSubscriptionNcmpOutEvent toCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, + final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap) { + return cmNotificationSubscriptionNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + dmiCmNotificationSubscriptionDetailsMap); + } + + /** + * Mapper to form a rejected response for the client for the Cm Notification Subscription Request. + * + * @param subscriptionId subscription id + * @param rejectedTargetFilters list of rejected target filters for the subscription request + * @return to sent back to the client + */ + public CmNotificationSubscriptionNcmpOutEvent toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + final String subscriptionId, final List<String> rejectedTargetFilters) { + return cmNotificationSubscriptionNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + subscriptionId, rejectedTargetFilters); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java index f7ea4a465..f7dd51e63 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventPublishingTask.java @@ -20,14 +20,13 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription; -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionNcmpOutEventProducer.buildAndGetCmNotificationNcmpOutEventAsCloudEvent; +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer.buildAndGetCmNotificationNcmpOutEventAsCloudEvent; import io.cloudevents.CloudEvent; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.events.EventsPublisher; -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; import org.onap.cps.utils.JsonObjectMapper; @@ -36,14 +35,13 @@ import org.onap.cps.utils.JsonObjectMapper; @RequiredArgsConstructor public class CmNotificationSubscriptionNcmpOutEventPublishingTask implements Runnable { - private final String topicName; private final String subscriptionId; private final String eventType; private final EventsPublisher<CloudEvent> eventsPublisher; private final JsonObjectMapper jsonObjectMapper; - private final Map<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache; - private final CmNotificationSubscriptionNcmpOutEventMapper cmNotificationSubscriptionNcmpOutEventMapper; + private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler; + private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler; /** * Delegating the responsibility of publishing CmNotificationSubscriptionNcmpOutEvent as a separate task which will @@ -52,13 +50,14 @@ public class CmNotificationSubscriptionNcmpOutEventPublishingTask implements Run @Override public void run() { final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap = - cmNotificationSubscriptionCache.get(subscriptionId); + dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent = - cmNotificationSubscriptionNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, dmiCmNotificationSubscriptionDetailsMap); eventsPublisher.publishCloudEvent(topicName, subscriptionId, buildAndGetCmNotificationNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent)); + dmiCmNotificationSubscriptionCacheHandler + .removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(subscriptionId); } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java index 4b3a08514..b5370bf1e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandler.java @@ -29,6 +29,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; @@ -51,7 +52,7 @@ public class DmiCmNotificationSubscriptionCacheHandler { /** * Adds new subscription to the subscription cache. * - * @param subscriptionId subscription Id + * @param subscriptionId subscription id * @param predicates subscription request predicates */ public void add(final String subscriptionId, final List<Predicate> predicates) { @@ -59,6 +60,33 @@ public class DmiCmNotificationSubscriptionCacheHandler { } /** + * Get cm notification subscription cache entry via subscription id. + * + * @param subscriptionId subscription id + * @return map of dmi cm notification subscriptions per dmi + */ + public Map<String, DmiCmNotificationSubscriptionDetails> get(final String subscriptionId) { + return cmNotificationSubscriptionCache.get(subscriptionId); + } + + + /** + * Remove cache entries with CmNotificationSubscriptionStatus ACCEPTED/REJECTED via subscription id. + * + * @param subscriptionId subscription id as key in CM notification Subscription cache. + */ + public void removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(final String subscriptionId) { + final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionsPerDmi = + cmNotificationSubscriptionCache.get(subscriptionId); + final Map<String, DmiCmNotificationSubscriptionDetails> updatedDmiCmNotificationSubscriptionsPerDmi = + dmiCmNotificationSubscriptionsPerDmi.entrySet().stream().filter( + dmiCmNotificationSubscription -> + !isAcceptedOrRejected(dmiCmNotificationSubscription.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + cmNotificationSubscriptionCache.put(subscriptionId, updatedDmiCmNotificationSubscriptionsPerDmi); + } + + /** * Creates map of subscription details per DMI. * * @param predicates CM Subscription Create Request Predicates @@ -94,10 +122,13 @@ public class DmiCmNotificationSubscriptionCacheHandler { * @param status String of status * */ - public void updateDmiCmNotificationSubscriptionStatusPerDmi( - final String subscriptionId, final String dmiServiceName, final CmNotificationSubscriptionStatus status) { - cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName) - .setCmNotificationSubscriptionStatus(status); + public void updateDmiCmNotificationSubscriptionStatusPerDmi(final String subscriptionId, + final String dmiServiceName, final CmNotificationSubscriptionStatus status) { + final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi = + cmNotificationSubscriptionCache.get(subscriptionId); + dmiCmNotificationSubscriptionDetailsPerDmi.get(dmiServiceName).setCmNotificationSubscriptionStatus(status); + cmNotificationSubscriptionCache.put(subscriptionId, dmiCmNotificationSubscriptionDetailsPerDmi); + } /** @@ -109,10 +140,10 @@ public class DmiCmNotificationSubscriptionCacheHandler { */ public void persistIntoDatabasePerDmi(final String subscriptionId, final String dmiServiceName) { final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicateList = - cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName) - .getDmiCmNotificationSubscriptionPredicates(); + cmNotificationSubscriptionCache.get(subscriptionId).get(dmiServiceName) + .getDmiCmNotificationSubscriptionPredicates(); for (final DmiCmNotificationSubscriptionPredicate dmiCmNotificationSubscriptionPredicate: - dmiCmNotificationSubscriptionPredicateList) { + dmiCmNotificationSubscriptionPredicateList) { final DatastoreType datastoreType = dmiCmNotificationSubscriptionPredicate.getDatastoreType(); final Set<String> cmHandles = dmiCmNotificationSubscriptionPredicate.getTargetCmHandleIds(); final Set<String> xpaths = dmiCmNotificationSubscriptionPredicate.getXpaths(); @@ -153,4 +184,10 @@ public class DmiCmNotificationSubscriptionCacheHandler { } return targetCmHandlesByDmiServiceNames; } + + private boolean isAcceptedOrRejected( + final DmiCmNotificationSubscriptionDetails dmiCmNotificationSubscription) { + return dmiCmNotificationSubscription.getCmNotificationSubscriptionStatus().toString().equals("ACCEPTED") + || dmiCmNotificationSubscription.getCmNotificationSubscriptionStatus().toString().equals("REJECTED"); + } }
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java index 82ae5467e..fb89aae3f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java @@ -18,16 +18,22 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.cmsubscription; +package org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer; import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; import io.cloudevents.CloudEvent; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent; +import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @@ -37,6 +43,8 @@ import org.springframework.stereotype.Component; public class CmNotificationSubscriptionDmiOutEventConsumer { private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler; + private final CmNotificationSubscriptionEventsHandler cmNotificationSubscriptionEventsHandler; + private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler; /** * Consume the Cm Notification Subscription event from the dmi-plugin. @@ -65,10 +73,12 @@ public class CmNotificationSubscriptionDmiOutEventConsumer { if ("ACCEPTED".equals(cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage())) { handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmNotificationSubscriptionStatus.ACCEPTED); dmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi(subscriptionId, dmiPluginName); + handleEventsStatusPerDmi(subscriptionId); } if ("REJECTED".equals(cmNotificationSubscriptionDmiOutEvent.getData().getStatusMessage())) { handleCacheStatusPerDmi(subscriptionId, dmiPluginName, CmNotificationSubscriptionStatus.REJECTED); + handleEventsStatusPerDmi(subscriptionId); } log.info("Cm Subscription with id : {} handled by the dmi-plugin : {} has the status : {}", subscriptionId, @@ -76,8 +86,18 @@ public class CmNotificationSubscriptionDmiOutEventConsumer { } private void handleCacheStatusPerDmi(final String subscriptionId, final String dmiPluginName, - final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus) { + final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus) { dmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi(subscriptionId, - dmiPluginName, cmNotificationSubscriptionStatus); + dmiPluginName, cmNotificationSubscriptionStatus); + } + + private void handleEventsStatusPerDmi(final String subscriptionId) { + final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsPerDmi = + dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); + final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent = + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + dmiCmNotificationSubscriptionDetailsPerDmi); + cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java index 377e15531..70135b307 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.cmsubscription; +package org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer; import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java index 5c192a953..9fbe26848 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.cmsubscription; +package org.onap.cps.ncmp.api.impl.events.cmsubscription.producer; import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java index 76ee08e64..ac5de07f0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.api.impl.events.cmsubscription; +package org.onap.cps.ncmp.api.impl.events.cmsubscription.producer; import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; @@ -33,8 +33,9 @@ import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.events.EventsPublisher; -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper; -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionNcmpOutEventPublishingTask; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler; import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.beans.factory.annotation.Value; @@ -55,8 +56,8 @@ public class CmNotificationSubscriptionNcmpOutEventProducer { private final EventsPublisher<CloudEvent> eventsPublisher; private final JsonObjectMapper jsonObjectMapper; - private final Map<String, Map<String, DmiCmNotificationSubscriptionDetails>> cmNotificationSubscriptionCache; - private final CmNotificationSubscriptionNcmpOutEventMapper cmNotificationSubscriptionNcmpOutEventMapper; + private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler; + private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler; private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private static final Map<String, ScheduledFuture<?>> scheduledTasksPerSubscriptionId = new ConcurrentHashMap<>(); @@ -72,8 +73,9 @@ public class CmNotificationSubscriptionNcmpOutEventProducer { * or published now */ public void publishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, final String eventType, - final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent, - final boolean isScheduledEvent) { + final CmNotificationSubscriptionNcmpOutEvent + cmNotificationSubscriptionNcmpOutEvent, + final boolean isScheduledEvent) { if (isScheduledEvent && !scheduledTasksPerSubscriptionId.containsKey(subscriptionId)) { final ScheduledFuture<?> scheduledFuture = @@ -86,16 +88,15 @@ public class CmNotificationSubscriptionNcmpOutEventProducer { cmNotificationSubscriptionNcmpOutEvent); log.info("Published CmNotificationSubscriptionEvent on demand for subscriptionId : {}", subscriptionId); } - } private ScheduledFuture<?> scheduleAndPublishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, - final String eventType) { + final String eventType) { final CmNotificationSubscriptionNcmpOutEventPublishingTask cmNotificationSubscriptionNcmpOutEventPublishingTask = new CmNotificationSubscriptionNcmpOutEventPublishingTask(cmNotificationSubscriptionNcmpOutEventTopic, - subscriptionId, eventType, eventsPublisher, jsonObjectMapper, cmNotificationSubscriptionCache, - cmNotificationSubscriptionNcmpOutEventMapper); + subscriptionId, eventType, eventsPublisher, jsonObjectMapper, + cmNotificationSubscriptionMappersHandler, dmiCmNotificationSubscriptionCacheHandler); return scheduledExecutorService.schedule(cmNotificationSubscriptionNcmpOutEventPublishingTask, cmNotificationSubscriptionDmiOutEventTimeoutInMs, TimeUnit.MILLISECONDS); } @@ -112,22 +113,34 @@ public class CmNotificationSubscriptionNcmpOutEventProducer { private void publishCmNotificationSubscriptionNcmpOutEventNow(final String subscriptionId, final String eventType, - final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent) { + final CmNotificationSubscriptionNcmpOutEvent + cmNotificationSubscriptionNcmpOutEvent) { final CloudEvent cmNotificationSubscriptionNcmpOutEventAsCloudEvent = buildAndGetCmNotificationNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent); eventsPublisher.publishCloudEvent(cmNotificationSubscriptionNcmpOutEventTopic, subscriptionId, cmNotificationSubscriptionNcmpOutEventAsCloudEvent); + dmiCmNotificationSubscriptionCacheHandler + .removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(subscriptionId); } - protected static CloudEvent buildAndGetCmNotificationNcmpOutEventAsCloudEvent( + /** + * Get an NCMP out event as cloud event. + * + * @param jsonObjectMapper JSON object mapper + * @param subscriptionId subscription id + * @param eventType event type + * @param cmNotificationSubscriptionNcmpOutEvent cm notification subscription NCMP out event + * @return cm notification subscription NCMP out event as cloud event + */ + public static CloudEvent buildAndGetCmNotificationNcmpOutEventAsCloudEvent( final JsonObjectMapper jsonObjectMapper, final String subscriptionId, final String eventType, final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent) { return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType) - .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.cm.subscription:1.0.0")) - .withExtension("correlationid", subscriptionId) - .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionNcmpOutEvent)).build(); + .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.cm.subscription:1.0.0")) + .withExtension("correlationid", subscriptionId) + .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionNcmpOutEvent)).build(); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java index 8204f05c9..7872ba0a3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImpl.java @@ -22,14 +22,19 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionNcmpOutEventProducer; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionDelta; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler; import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler; -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails; +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent; import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.Predicate; +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent; import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent; import org.springframework.stereotype.Service; @@ -38,33 +43,56 @@ import org.springframework.stereotype.Service; public class CmNotificationSubscriptionHandlerServiceImpl implements CmNotificationSubscriptionHandlerService { private final CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService; - private final CmNotificationSubscriptionNcmpOutEventMapper cmNotificationSubscriptionNcmpOutEventMapper; - private final CmNotificationSubscriptionNcmpOutEventProducer cmNotificationSubscriptionNcmpOutEventProducer; + private final CmNotificationSubscriptionDelta cmNotificationSubscriptionDelta; + private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler; + private final CmNotificationSubscriptionEventsHandler cmNotificationSubscriptionEventsHandler; private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler; @Override public void processSubscriptionCreateRequest( - final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent) { + final CmNotificationSubscriptionNcmpInEvent cmNotificationSubscriptionNcmpInEvent) { final String subscriptionId = cmNotificationSubscriptionNcmpInEvent.getData().getSubscriptionId(); final List<Predicate> predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates(); if (cmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId(subscriptionId)) { dmiCmNotificationSubscriptionCacheHandler.add(subscriptionId, predicates); + sendSubscriptionCreateRequestToDmi(subscriptionId); + scheduleCmNotificationSubscriptionNcmpOutEventResponse(subscriptionId); } else { - final Set<String> subscriptionTargetFilters = predicates.stream().flatMap( - predicate -> predicate.getTargetFilter().stream()).collect(Collectors.toSet()); rejectAndPublishCmNotificationSubscriptionCreateRequest(subscriptionId, - new ArrayList<>(subscriptionTargetFilters)); + predicates); } } + private void scheduleCmNotificationSubscriptionNcmpOutEventResponse(final String subscriptionId) { + cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + "subscriptionCreateResponse", null, true); + } + private void rejectAndPublishCmNotificationSubscriptionCreateRequest(final String subscriptionId, - final List<String> subscriptionTargetFilters) { + final List<Predicate> predicates) { + final Set<String> subscriptionTargetFilters = + predicates.stream().flatMap(predicate -> predicate.getTargetFilter().stream()) + .collect(Collectors.toSet()); final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent = - cmNotificationSubscriptionNcmpOutEventMapper - .toCmNotificationSubscriptionNcmpOutEventForRejectedRequest(subscriptionId, - subscriptionTargetFilters); - cmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, - "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false); + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + subscriptionId, new ArrayList<>(subscriptionTargetFilters)); + cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false); + } + + private void sendSubscriptionCreateRequestToDmi(final String subscriptionId) { + final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap = + dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); + dmiCmNotificationSubscriptionDetailsMap.forEach((dmiPluginName, dmiCmNotificationSubscriptionDetails) -> { + final List<DmiCmNotificationSubscriptionPredicate> dmiCmNotificationSubscriptionPredicates = + cmNotificationSubscriptionDelta.getDelta( + dmiCmNotificationSubscriptionDetails.getDmiCmNotificationSubscriptionPredicates()); + final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent = + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionDmiInEvent( + dmiCmNotificationSubscriptionPredicates); + cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(subscriptionId, + dmiPluginName, "subscriptionCreateRequest", cmNotificationSubscriptionDmiInEvent); + }); } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NoAlternateIdParentFoundException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NoAlternateIdParentFoundException.java new file mode 100644 index 000000000..2e6cd3308 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NoAlternateIdParentFoundException.java @@ -0,0 +1,39 @@ +/* + * ============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.impl.exception; + +import java.io.Serial; + +public class NoAlternateIdParentFoundException extends NcmpException { + + @Serial + private static final long serialVersionUID = -2412915490233422945L; + private static final String ALTERNATE_ID_NOT_FOUND = "No matching (parent) cm handle found using alternate ids"; + + /** + * Constructor. + * + * @param cpsPath datanode cpsPath + */ + public NoAlternateIdParentFoundException(final String cpsPath) { + super(ALTERNATE_ID_NOT_FOUND, String.format("cannot find a datanode with alternate id %s", cpsPath)); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java index e230b3fcb..184b12570 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java @@ -130,6 +130,16 @@ public interface InventoryPersistence extends NcmpPersistence { DataNode getCmHandleDataNodeByAlternateId(String alternateId); /** + * Get data node that matches longest alternate id by removing elements (as defined by the separator string) + * from right to left. + * + * @param alternateId alternate ID + * @param separator a string that separates each element from the next. + * @return data node + */ + DataNode getCmHandleDataNodeByLongestMatchAlternateId(final String alternateId, final String separator); + + /** * Get collection of data nodes of given cm handles. * * @param cmHandleIds collection of cmHandle IDs diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java index 3ae3dd911..bf54fe5d9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java @@ -33,9 +33,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; +import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.spi.FetchDescendantsOption; @@ -169,17 +171,30 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv @Override public DataNode getCmHandleDataNodeByAlternateId(final String alternateId) { - final String xPathForCmHandleByAlternateId = getXPathForCmHandleByAlternateId(alternateId); + final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId); final Collection<DataNode> dataNodes = cmHandleQueries - .queryNcmpRegistryByCpsPath(xPathForCmHandleByAlternateId, OMIT_DESCENDANTS); + .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS); if (dataNodes.isEmpty()) { throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - xPathForCmHandleByAlternateId); + cpsPathForCmHandleByAlternateId); } return dataNodes.iterator().next(); } @Override + public DataNode getCmHandleDataNodeByLongestMatchAlternateId(final String alternateId, final String separator) { + String bestMatch = alternateId; + while (StringUtils.isNotEmpty(bestMatch)) { + try { + return getCmHandleDataNodeByAlternateId(bestMatch); + } catch (final DataNodeNotFoundException ignored) { + bestMatch = getParentPath(bestMatch, separator); + } + } + throw new NoAlternateIdParentFoundException(alternateId); + } + + @Override public Collection<DataNode> getCmHandleDataNodes(final Collection<String> cmHandleIds) { final Collection<String> xpaths = new ArrayList<>(cmHandleIds.size()); cmHandleIds.forEach(cmHandleId -> xpaths.add(getXPathForCmHandleById(cmHandleId))); @@ -195,7 +210,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']"; } - private static String getXPathForCmHandleByAlternateId(final String alternateId) { + private static String getCpsPathForCmHandleByAlternateId(final String alternateId) { return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='" + alternateId + "']"; } @@ -206,4 +221,9 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private String createCmHandlesJsonData(final List<YangModelCmHandle> yangModelCmHandles) { return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}"; } + + private static String getParentPath(final String path, final String separator) { + final int lastSeparatorIndex = path.lastIndexOf(separator); + return lastSeparatorIndex < 0 ? "" : path.substring(0, lastSeparatorIndex); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index cac25c845..a9ec1241b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -35,7 +35,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NcmpResponseStatus; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration.DmiProperties; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; import org.onap.cps.ncmp.api.impl.inventory.CmHandleState; import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; @@ -61,7 +61,7 @@ public class DmiDataOperations extends DmiOperations { public DmiDataOperations(final InventoryPersistence inventoryPersistence, final JsonObjectMapper jsonObjectMapper, - final DmiProperties dmiProperties, + final NcmpConfiguration.DmiProperties dmiProperties, final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); @@ -226,7 +226,7 @@ public class DmiDataOperations extends DmiOperations { } private static Set<String> getDistinctCmHandleIdsFromDataOperationRequest(final DataOperationRequest - dataOperationRequest) { + dataOperationRequest) { return dataOperationRequest.getDataOperationDefinitions().stream() .flatMap(dataOperationDefinition -> dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet()); @@ -235,7 +235,7 @@ public class DmiDataOperations extends DmiOperations { private void buildDataOperationRequestUrlAndSendToDmiService(final String topicParamInQuery, final String requestId, final Map<String, List<DmiDataOperation>> - groupsOutPerDmiServiceName, + groupsOutPerDmiServiceName, final String authorization) { groupsOutPerDmiServiceName.forEach((dmiServiceName, dmiDataOperationRequestBodies) -> { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java index c71da1cd1..3a281d740 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java @@ -32,7 +32,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration.DmiProperties; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; @@ -55,7 +55,7 @@ public class DmiModelOperations extends DmiOperations { */ public DmiModelOperations(final InventoryPersistence inventoryPersistence, final JsonObjectMapper jsonObjectMapper, - final DmiProperties dmiProperties, + final NcmpConfiguration.DmiProperties dmiProperties, final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); } @@ -71,7 +71,7 @@ public class DmiModelOperations extends DmiOperations { .moduleSetTag(yangModelCmHandle.getModuleSetTag()).build(); dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); final ResponseEntity<Object> dmiFetchModulesResponseEntity = getResourceFromDmiWithJsonData( - yangModelCmHandle.resolveDmiServiceName(MODEL), + yangModelCmHandle.resolveDmiServiceName(MODEL), jsonObjectMapper.asJsonString(dmiRequestBody), yangModelCmHandle.getId(), "modules"); return toModuleReferences((Map) dmiFetchModulesResponseEntity.getBody()); } @@ -89,12 +89,12 @@ public class DmiModelOperations extends DmiOperations { return Collections.emptyMap(); } final String jsonWithDataAndDmiProperties = getRequestBodyToFetchYangResources( - newModuleReferences, yangModelCmHandle.getDmiProperties()); + newModuleReferences, yangModelCmHandle.getDmiProperties()); final ResponseEntity<Object> responseEntity = getResourceFromDmiWithJsonData( - yangModelCmHandle.resolveDmiServiceName(MODEL), - jsonWithDataAndDmiProperties, - yangModelCmHandle.getId(), - "moduleResources"); + yangModelCmHandle.resolveDmiServiceName(MODEL), + jsonWithDataAndDmiProperties, + yangModelCmHandle.getId(), + "moduleResources"); return asModuleNameToYangResourceMap(responseEntity); } @@ -112,12 +112,12 @@ public class DmiModelOperations extends DmiOperations { final String cmHandle, final String resourceName) { final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, - jsonRequestBody, OperationType.READ, null); + return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, + OperationType.READ, null); } private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences, - final List<YangModelCmHandle.Property> dmiProperties) { + final List<YangModelCmHandle.Property> dmiProperties) { final JsonArray moduleReferencesAsJson = getModuleReferencesAsJson(newModuleReferences); final JsonObject data = new JsonObject(); data.add("modules", moduleReferencesAsJson); @@ -140,7 +140,7 @@ public class DmiModelOperations extends DmiOperations { } private static JsonObject toJsonObject(final List<YangModelCmHandle.Property> - dmiProperties) { + dmiProperties) { final JsonObject asJsonObject = new JsonObject(); for (final YangModelCmHandle.Property additionalProperty : dmiProperties) { asJsonObject.addProperty(additionalProperty.getName(), additionalProperty.getValue()); @@ -173,7 +173,7 @@ public class DmiModelOperations extends DmiOperations { final YangResource yangResource = jsonObjectMapper.convertToValueType(yangResourceAsMap, YangResource.class); yangResourcesModuleNameToContentMap.put(yangResource.getModuleName(), - yangResource.getYangSource()); + yangResource.getYangSource()); }); } return yangResourcesModuleNameToContentMap; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java index 912c52ca9..c8d73eac6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java @@ -23,7 +23,7 @@ package org.onap.cps.ncmp.api.impl.operations; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration.DmiProperties; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.utils.JsonObjectMapper; @@ -35,7 +35,7 @@ public class DmiOperations { protected final InventoryPersistence inventoryPersistence; protected final JsonObjectMapper jsonObjectMapper; - protected final DmiProperties dmiProperties; + protected final NcmpConfiguration.DmiProperties dmiProperties; protected final DmiRestClient dmiRestClient; protected final DmiServiceUrlBuilder dmiServiceUrlBuilder; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java index e0c956803..04acaa5e9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -20,14 +20,12 @@ package org.onap.cps.ncmp.api.impl.utils; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration.DmiProperties; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.spi.utils.CpsValidator; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; @@ -37,7 +35,8 @@ import org.springframework.web.util.UriComponentsBuilder; @Component @RequiredArgsConstructor public class DmiServiceUrlBuilder { - private final DmiProperties dmiProperties; + + private final NcmpConfiguration.DmiProperties dmiProperties; private final CpsValidator cpsValidator; /** @@ -142,7 +141,8 @@ public class DmiServiceUrlBuilder { final String optionsParamInQuery, final String topicParamInQuery) { final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); - getQueryParamConsumer().accept("resourceIdentifier", resourceId, queryParams); + getQueryParamConsumer().accept("resourceIdentifier", + resourceId, queryParams); getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams); if (Strings.isNotEmpty(topicParamInQuery)) { getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); @@ -168,7 +168,7 @@ public class DmiServiceUrlBuilder { private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() { return (paramName, paramValue, paramMap) -> { if (Strings.isNotEmpty(paramValue)) { - paramMap.add(paramName, URLEncoder.encode(paramValue, StandardCharsets.UTF_8)); + paramMap.add(paramName, paramValue); } }; } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy index 003dbf5b0..c8e34b1a5 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -24,21 +24,20 @@ package org.onap.cps.ncmp.api.impl.client 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.impl.config.DmiWebClientConfiguration; +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.utils.TestUtils import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest - +import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestPropertySource import org.springframework.web.client.HttpServerErrorException -import org.springframework.web.reactive.function.client.WebClient -import reactor.core.publisher.Mono +import org.springframework.web.client.RestTemplate import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ @@ -46,49 +45,43 @@ import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE @SpringBootTest -@ContextConfiguration(classes = [DmiWebClientConfiguration, DmiRestClient, ObjectMapper]) +@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper]) class DmiRestClientSpec extends Specification { static final NO_AUTH_HEADER = null static final BASIC_AUTH_HEADER = 'Basic c29tZS11c2VyOnNvbWUtcGFzc3dvcmQ=' static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token' + @SpringBean + RestTemplate mockRestTemplate = Mock(RestTemplate) + @Autowired - DmiWebClientConfiguration.DmiProperties dmiProperties + NcmpConfiguration.DmiProperties dmiProperties @Autowired DmiRestClient objectUnderTest - @SpringBean - WebClient mockWebClient = Mock(WebClient); - @Autowired ObjectMapper objectMapper - def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec) - def mockResponseSpec = Mock(WebClient.ResponseSpec) - def mockResponseEntity = Mock(ResponseEntity) - def monoSpec = Mono.just(mockResponseEntity) + def responseFromRestTemplate = Mock(ResponseEntity) + def 'DMI POST operation with JSON.'() { - given: 'the web client returns a valid response entity for the expected parameters' - mockWebClient.post() >> mockRequestBodyUriSpec - mockWebclientResponse() - mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec - mockResponseSpec.toEntity(Object.class) >> monoSpec - monoSpec.block() >> mockResponseEntity + given: 'the rest template returns a valid response entity for the expected parameters' + mockRestTemplate.postForEntity('my url', _ as HttpEntity, Object.class) >> responseFromRestTemplate when: 'POST operation is invoked' - def result = objectUnderTest.postOperationWithJsonData('/my/url', 'some json', READ, null) + def result = objectUnderTest.postOperationWithJsonData('my url', 'some json', READ, null) then: 'the output of the method is equal to the output from the test template' - result == mockResponseEntity + result == responseFromRestTemplate } def 'Failing DMI POST operation.'() { given: 'the rest template returns a valid response entity' def serverResponse = 'server response'.getBytes() def httpServerErrorException = new HttpServerErrorException(HttpStatus.FORBIDDEN, 'status text', serverResponse, null) - mockWebClient.post() >> { throw httpServerErrorException } + mockRestTemplate.postForEntity(*_) >> { throw httpServerErrorException } when: 'POST operation is invoked' - def result = objectUnderTest.postOperationWithJsonData('/some', 'some json', operation, null) + def result = objectUnderTest.postOperationWithJsonData('some url', 'some json', operation, null) then: 'a Http Client Exception is thrown' def thrown = thrown(HttpClientRequestException) and: 'the exception has the relevant details from the error response' @@ -104,20 +97,16 @@ class DmiRestClientSpec extends Specification { def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json') def jsonNode = objectMapper.readValue(dmiPluginHealthCheckResponseJsonData, JsonNode.class) ((ObjectNode) jsonNode).put('status', 'my status') - def monoResponse = Mono.just(jsonNode) - mockWebClient.get() >> mockRequestBodyUriSpec - mockWebclientResponse() - mockResponseSpec.bodyToMono(_) >> monoResponse - monoResponse.block() >> jsonNode + mockRestTemplate.getForObject(*_) >> {jsonNode} when: 'get trust level of the dmi plugin' - def result = objectUnderTest.getDmiHealthStatus('some/url') + def result = objectUnderTest.getDmiHealthStatus('some url') then: 'the status value from the json is return' assert result == 'my status' } def 'Failing to get dmi plugin health status #scenario'() { given: 'rest template with #scenario' - mockWebClient.get() >> healthStatusResponse + mockRestTemplate.getForObject(*_) >> healthStatusResponse when: 'attempt to get health status of the dmi plugin' def result = objectUnderTest.getDmiHealthStatus('some url') then: 'result will be empty' @@ -144,9 +133,4 @@ class DmiRestClientSpec extends Specification { 'DMI basic auth disabled, with NCMP basic auth' | false | BASIC_AUTH_HEADER || NO_AUTH_HEADER } - def mockWebclientResponse() { - mockRequestBodyUriSpec.uri(_) >> mockRequestBodyUriSpec - mockRequestBodyUriSpec.headers(_) >> mockRequestBodyUriSpec - mockRequestBodyUriSpec.retrieve() >> mockResponseSpec - } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy deleted file mode 100644 index 2ce5b5009..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy +++ /dev/null @@ -1,63 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.config - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestPropertySource -import org.springframework.web.reactive.function.client.WebClient -import spock.lang.Specification - -@SpringBootTest -@ContextConfiguration(classes = [DmiWebClientConfiguration.DmiProperties]) -@TestPropertySource(properties = ["ncmp.dmi.httpclient.connectionTimeoutInSeconds=1", "ncmp.dmi.httpclient.maximumInMemorySizeInMegabytes=1"]) -class DmiWebClientConfigurationSpec extends Specification { - - @Autowired - DmiWebClientConfiguration.DmiProperties dmiProperties - - def objectUnderTest = new DmiWebClientConfiguration() - - def setup() { - objectUnderTest.connectionTimeoutInSeconds = 10 - objectUnderTest.maximumInMemorySizeInMegabytes = 1 - } - - def 'DMI Properties.'() { - expect: 'properties are set to values in test configuration yaml file' - dmiProperties.authUsername == 'some-user' - dmiProperties.authPassword == 'some-password' - } - - def 'Web Client Configuration construction.'() { - expect: 'the system can create an instance' - new DmiWebClientConfiguration() != null - } - - def 'Creating a WebClient instance.'() { - given: 'WebClient configuration invoked' - def webClientInstance = objectUnderTest.webClient() - expect: 'the system can create an instance' - assert webClientInstance != null - assert webClientInstance instanceof WebClient - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy index f588e2ed2..74e342405 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy @@ -31,10 +31,13 @@ import org.springframework.web.client.RestTemplate import spock.lang.Specification @SpringBootTest -@ContextConfiguration(classes = [ HttpClientConfiguration]) +@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration]) class NcmpConfigurationSpec extends Specification{ @Autowired + NcmpConfiguration.DmiProperties dmiProperties + + @Autowired HttpClientConfiguration httpClientConfiguration def mockRestTemplateBuilder = new RestTemplateBuilder() @@ -44,6 +47,12 @@ class NcmpConfigurationSpec extends Specification{ new NcmpConfiguration() != null } + def 'DMI Properties.'() { + expect: 'properties are set to values in test configuration yaml file' + dmiProperties.authUsername == 'some-user' + dmiProperties.authPassword == 'some-password' + } + def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() { when: 'a rest template is created' def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy index cd9b8ddf4..cfb28a0ad 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiInEventProducerSpec.groovy @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.Cmhandle diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy index 523ec767c..488879db7 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDmiOutEventConsumerSpec.groovy @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import io.cloudevents.core.builder.CloudEventBuilder import org.apache.kafka.clients.consumer.ConsumerRecord +import org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer.CmNotificationSubscriptionDmiOutEventConsumer import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent @@ -48,10 +49,11 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe @Autowired ObjectMapper objectMapper - @SpringBean - DmiCmNotificationSubscriptionCacheHandler mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler) + def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler) + def mockCmNotificationSubscriptionEventsHandler = Mock(CmNotificationSubscriptionEventsHandler) + def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler) - def objectUnderTest = new CmNotificationSubscriptionDmiOutEventConsumer(mockDmiCmNotificationSubscriptionCacheHandler) + def objectUnderTest = new CmNotificationSubscriptionDmiOutEventConsumer(mockDmiCmNotificationSubscriptionCacheHandler, mockCmNotificationSubscriptionEventsHandler, mockCmNotificationSubscriptionMappersHandler) def logger = Spy(ListAppender<ILoggingEvent>) void setup() { @@ -101,6 +103,10 @@ class CmNotificationSubscriptionDmiOutEventConsumerSpec extends MessagingBaseSpe expectedCacheCalls * mockDmiCmNotificationSubscriptionCacheHandler.updateDmiCmNotificationSubscriptionStatusPerDmi('sub-1','test-dmi-plugin-name', subscriptionStatus) and: 'correct number of calls to persist cache' expectedPersistenceCalls * mockDmiCmNotificationSubscriptionCacheHandler.persistIntoDatabasePerDmi('sub-1','test-dmi-plugin-name') + and: 'correct number of calls to map the ncmp out event' + 1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent('sub-1', _) + and: 'correct number of calls to publish the ncmp out event to client' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent('sub-1', 'subscriptionCreateResponse', _, false) where: 'the following parameters are used' scenario | subscriptionStatus | statusCode || expectedCacheCalls | expectedPersistenceCalls 'Accepted Status' | CmNotificationSubscriptionStatus.ACCEPTED | '1' || 1 | 1 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandlerSpec.groovy new file mode 100644 index 000000000..788a7a7da --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionEventsHandlerSpec.groovy @@ -0,0 +1,58 @@ +/* + * ============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.impl.events.cmsubscription + +import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer +import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionDmiInEventProducer +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent +import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent +import spock.lang.Specification + +class CmNotificationSubscriptionEventsHandlerSpec extends Specification { + + def mockCmNotificationSubscriptionNcmpOutEventProducer = Mock(CmNotificationSubscriptionNcmpOutEventProducer) + def mockCmNotificationSubscriptionDmiInEventProducer = Mock(CmNotificationSubscriptionDmiInEventProducer) + + def objectUnderTest = new CmNotificationSubscriptionEventsHandler(mockCmNotificationSubscriptionNcmpOutEventProducer, + mockCmNotificationSubscriptionDmiInEventProducer) + + def 'Publish cm notification subscription ncmp out event'() { + given: 'an ncmp out event' + def cmNotificationSubscriptionNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent() + when: 'the method to publish cm notification subscription ncmp out event is called' + objectUnderTest.publishCmNotificationSubscriptionNcmpOutEvent("some-id", + "some-event", cmNotificationSubscriptionNcmpOutEvent, true) + then: 'the parameters is delegated to the correct method once' + 1 * mockCmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent( + "some-id", "some-event", cmNotificationSubscriptionNcmpOutEvent, true) + } + + def 'Publish cm notification subscription dmi in event'() { + given: 'a dmi in event' + def cmNotificationSubscriptionDmiInEvent = new CmNotificationSubscriptionDmiInEvent() + when: 'the method to publish cm notification subscription ncmp out event is called' + objectUnderTest.publishCmNotificationSubscriptionDmiInEvent("some-id", + "some-dmi", "some-event", cmNotificationSubscriptionDmiInEvent) + then: 'the parameters is delegated to the correct method once' + 1 * mockCmNotificationSubscriptionDmiInEventProducer.publishCmNotificationSubscriptionDmiInEvent("some-id", + "some-dmi", "some-event", cmNotificationSubscriptionDmiInEvent) + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy new file mode 100644 index 000000000..bdc54bd1e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy @@ -0,0 +1,64 @@ +/* + * ============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.impl.events.cmsubscription + +import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper +import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper +import spock.lang.Specification + +class CmNotificationSubscriptionMappersHandlerSpec extends Specification{ + + def mockCmNotificationDmiInEventMapper = Mock(CmNotificationSubscriptionDmiInEventMapper) + def mockCmNotificationNcmpOutEventMapper = Mock(CmNotificationSubscriptionNcmpOutEventMapper) + + def objectUnderTest = new CmNotificationSubscriptionMappersHandler(mockCmNotificationDmiInEventMapper, + mockCmNotificationNcmpOutEventMapper) + + def 'Get cm notification subscription DMI in event'() { + given: 'a list of predicates' + def testListOfPredicates = [] + when: 'method to create a cm notification subscription dmi in event is called with predicates' + objectUnderTest.toCmNotificationSubscriptionDmiInEvent(testListOfPredicates) + then: 'the parameters is delegated to the correct dmi in event mapper method' + 1 * mockCmNotificationDmiInEventMapper.toCmNotificationSubscriptionDmiInEvent(testListOfPredicates) + } + + def 'Get cm notification subscription ncmp out event'() { + given: 'a subscription details map' + def testSubscriptionDetailsMap = [:] + when: 'method to create cm notification subscription ncmp out event is called with the following parameters' + objectUnderTest.toCmNotificationSubscriptionNcmpOutEvent("test-id", testSubscriptionDetailsMap) + then: 'the parameters is delegated to the correct ncmp out event mapper method' + 1 * mockCmNotificationNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEvent("test-id", + testSubscriptionDetailsMap) + } + + def 'Get cm notification subscription ncmp out event for a rejected request'() { + given: 'a list of target filters' + def testRejectedTargetFilters = [] + when: 'method to create cm notification subscription ncmp out event is called with the following parameters' + objectUnderTest.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + "test-id", testRejectedTargetFilters) + then: 'the parameters is delegated to the correct ncmp out event mapper method' + 1 * mockCmNotificationNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + "test-id", testRejectedTargetFilters) + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy index 8210cf32a..9c84c51b2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import io.cloudevents.core.builder.CloudEventBuilder import org.apache.kafka.clients.consumer.ConsumerRecord +import org.onap.cps.ncmp.api.impl.events.cmsubscription.consumer.CmNotificationSubscriptionNcmpInEventConsumer import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionHandlerService import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent @@ -79,7 +80,7 @@ class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpe def loggingEvent = getLoggingEvent() assert loggingEvent.level == Level.INFO and: 'the log indicates the task completed successfully' - assert loggingEvent.formattedMessage == 'Subscription for source some-resource with subscription id cm-subscription-001 ...' + assert loggingEvent.formattedMessage == 'Subscription for source some-resource with subscription id test-id ...' and: 'the subscription handler service is called once' 1 * mockCmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(_) } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy index 970d7e67b..77bbe7ebc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy @@ -3,8 +3,7 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper -import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails +import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationSubscriptionNcmpOutEventProducer import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.Data @@ -15,10 +14,11 @@ class CmNotificationSubscriptionNcmpOutEventProducerSpec extends Specification { def mockEventsPublisher = Mock(EventsPublisher) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def mockCmNotificationSubscriptionCache = Mock(Map<String, Map<String, DmiCmNotificationSubscriptionDetails>>) - def mockCmNotificationSubscriptionNcmpOutEventMapper = Mock(CmNotificationSubscriptionNcmpOutEventMapper) + def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler) + def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler) - def objectUnderTest = new CmNotificationSubscriptionNcmpOutEventProducer(mockEventsPublisher, jsonObjectMapper, mockCmNotificationSubscriptionCache, mockCmNotificationSubscriptionNcmpOutEventMapper) + def objectUnderTest = new CmNotificationSubscriptionNcmpOutEventProducer(mockEventsPublisher, jsonObjectMapper, + mockCmNotificationSubscriptionMappersHandler, mockDmiCmNotificationSubscriptionCacheHandler) def 'Create and #scenario Cm Notification Subscription NCMP out event'() { given: 'a cm subscription response for the client' @@ -80,6 +80,8 @@ class CmNotificationSubscriptionNcmpOutEventProducerSpec extends Specification { assert CloudEventMapper.toTargetEvent(cmNotificationSubscriptionNcmpOutEventAsCloudEvent, CmNotificationSubscriptionNcmpOutEvent) == cmNotificationSubscriptionNcmpOutEvent } } + then: 'the cache handler is called once to remove accepted and rejected entries in cache' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries(subscriptionId) } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy index 10e060fee..43568be50 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy @@ -25,6 +25,7 @@ import io.cloudevents.CloudEvent import io.cloudevents.core.builder.CloudEventBuilder import org.apache.kafka.clients.consumer.ConsumerRecord import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle @@ -75,6 +76,37 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { assert testCache.containsKey(subscriptionId) } + def 'Get cache entry via subscription id'() { + given: 'the cache contains value for some-id' + testCache.put('some-id',[:]) + when: 'the get method is called' + def result = objectUnderTest.get('some-id') + then: 'correct value is returned as expected' + assert result == [:] + } + + def 'Remove accepted and rejected entries from cache via subscription id'() { + given: 'a map as the value for cache entry for some-id' + def testMap = [:] + testMap.put("dmi-1", + new DmiCmNotificationSubscriptionDetails([],CmNotificationSubscriptionStatus.ACCEPTED)) + testMap.put("dmi-2", + new DmiCmNotificationSubscriptionDetails([],CmNotificationSubscriptionStatus.REJECTED)) + testMap.put("dmi-3", + new DmiCmNotificationSubscriptionDetails([],CmNotificationSubscriptionStatus.PENDING)) + testCache.put("test-id", testMap) + assert testCache.get("test-id").size() == 3 + when: 'the method to remove accepted and rejected entries for test-id is called' + objectUnderTest.removeAcceptedAndRejectedDmiCmNotificationSubscriptionEntries("test-id") + then: 'all entries with status accepted/rejected are no longer present for test-id' + testCache.get("test-id").each { key, testResultMap -> + assert testResultMap.cmNotificationSubscriptionStatus != CmNotificationSubscriptionStatus.ACCEPTED + || testResultMap.cmNotificationSubscriptionStatus != CmNotificationSubscriptionStatus.REJECTED + } + and: 'the size of the map for cache entry test-id is as expected' + assert testCache.get("test-id").size() == 1 + } + def 'Create map for DMI cm notification subscription per DMI service name'() { given: 'list of predicates from the create subscription event' def predicates = cmNotificationSubscriptionNcmpInEvent.getData().getPredicates() diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy index 1020f55ea..98b4ee267 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy @@ -21,10 +21,17 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionNcmpOutEventProducer +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionDelta +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler +import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscriptionCacheHandler +import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionDmiInEventMapper import org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus +import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent +import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent +import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification @@ -33,31 +40,58 @@ class CmNotificationSubscriptionHandlerServiceImplSpec extends Specification{ def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) def mockCmNotificationSubscriptionPersistenceService = Mock(CmNotificationSubscriptionPersistenceService); - def mockCmNotificationSubscriptionNcmpOutEventMapper = Mock(CmNotificationSubscriptionNcmpOutEventMapper); - def mockCmNotificationSubscriptionNcmpOutEventProducer = Mock(CmNotificationSubscriptionNcmpOutEventProducer); + def mockCmNotificationSubscriptionDelta = Mock(CmNotificationSubscriptionDelta); + def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler); + def mockCmNotificationSubscriptionEventsHandler = Mock(CmNotificationSubscriptionEventsHandler); def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler); - def objectUnderTest = new CmNotificationSubscriptionHandlerServiceImpl(mockCmNotificationSubscriptionPersistenceService, mockCmNotificationSubscriptionNcmpOutEventMapper, mockCmNotificationSubscriptionNcmpOutEventProducer, mockDmiCmNotificationSubscriptionCacheHandler) + def objectUnderTest = new CmNotificationSubscriptionHandlerServiceImpl(mockCmNotificationSubscriptionPersistenceService, + mockCmNotificationSubscriptionDelta, mockCmNotificationSubscriptionMappersHandler, + mockCmNotificationSubscriptionEventsHandler, mockDmiCmNotificationSubscriptionCacheHandler) + + def testSubscriptionDetailsMap = ["dmi-1":new DmiCmNotificationSubscriptionDetails([], CmNotificationSubscriptionStatus.PENDING)] + def testListOfDeltaPredicates = [] def 'Consume valid and unique CmNotificationSubscriptionNcmpInEvent create message'() { - given: 'a cmNotificationSubscriptionNcmp in event' + given: 'a cmNotificationSubscriptionNcmp in event with unique subscription id' def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) - mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId('cm-subscription-001') >> true + mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId("test-id") >> true + and: 'the cache handler returns for relevant subscription id' + 1 * mockDmiCmNotificationSubscriptionCacheHandler.get("test-id") >> testSubscriptionDetailsMap + and: 'the delta predicates is returned' + 1 * mockCmNotificationSubscriptionDelta.getDelta(_) >> testListOfDeltaPredicates + and: 'the DMI in event mapper returns cm notification subscription event' + def testDmiInEvent = new CmNotificationSubscriptionDmiInEvent() + 1 * mockCmNotificationSubscriptionMappersHandler + .toCmNotificationSubscriptionDmiInEvent(testListOfDeltaPredicates) >> testDmiInEvent when: 'the valid and unique event is consumed' objectUnderTest.processSubscriptionCreateRequest(testEventConsumed) then: 'the subscription cache handler is called once' - 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('cm-subscription-001',_) + 1 * mockDmiCmNotificationSubscriptionCacheHandler.add('test-id',_) + and: 'the events handler method to publish DMI event is called correct number of times with the correct parameters' + testSubscriptionDetailsMap.size() * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent( + "test-id", "dmi-1", "subscriptionCreateRequest", testDmiInEvent) + and: 'we schedule to send the response after configured time from the cache' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( + "test-id", "subscriptionCreateResponse", null, true) } def 'Consume valid and but non-unique CmNotificationSubscription create message'() { given: 'a cmNotificationSubscriptionNcmp in event' def jsonData = TestUtils.getResourceFileContent('cmSubscription/cmNotificationSubscriptionNcmpInEvent.json') def testEventConsumed = jsonObjectMapper.convertJsonString(jsonData, CmNotificationSubscriptionNcmpInEvent.class) - mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId('cm-subscription-001') >> false + mockCmNotificationSubscriptionPersistenceService.isUniqueSubscriptionId('test-id') >> false + and: 'the NCMP out in event mapper returns an event for rejected request' + def testNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent() + 1 * mockCmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + "test-id",_) >> testNcmpOutEvent when: 'the valid but non-unique event is consumed' objectUnderTest.processSubscriptionCreateRequest(testEventConsumed) - then: 'the subscription out event publisher is called once' - 1 * mockCmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent('cm-subscription-001', 'subscriptionCreateResponse', _, false) + then: 'the events handler method to publish DMI event is never called' + 0 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionDmiInEvent(_,_,_,_) + and: 'the events handler method to publish NCMP out event is called once' + 1 * mockCmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent( + 'test-id', 'subscriptionCreateResponse', testNcmpOutEvent, false) } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy index cbc985f78..9907e9ab2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -22,6 +22,14 @@ package org.onap.cps.ncmp.api.impl.inventory +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService @@ -29,6 +37,7 @@ import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.ncmp.api.impl.exception.NoAlternateIdParentFoundException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.ModuleDefinition @@ -37,19 +46,10 @@ import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared import spock.lang.Specification - import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS - class InventoryPersistenceImplSpec extends Specification { def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) @@ -303,6 +303,41 @@ class InventoryPersistenceImplSpec extends Specification { assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode() } + def 'Find cm handle parent data node using alternate ids'() { + given: 'cm handle in the registry with alternateId /a/b' + def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='/a/b']" + mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()] + and: 'no other cm handle' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] + expect: 'querying for alternate id a matching result found' + assert objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') != null + where: 'the following parameters are used' + scenario | alternateId + 'exact match' | '/a/b' + 'exact match with trailing separator' | '/a/b/' + 'child match' | '/a/b/c' + } + + def 'Find cm handle parent data node using alternate ids mismatches'() { + given: 'cm handle in the registry with alternateId' + def matchingCpsPath = "/dmi-registry/cm-handles[@alternate-id='${cpsPath}]" + mockCmHandleQueries.queryNcmpRegistryByCpsPath(matchingCpsPath, OMIT_DESCENDANTS) >> [new DataNode()] + and: 'no other cm handle' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] + when: 'attempt to find alternateId' + objectUnderTest.getCmHandleDataNodeByLongestMatchAlternateId(alternateId, '/') + then: 'no alternate id found exception thrown' + def thrown = thrown(NoAlternateIdParentFoundException) + and: 'the exception has the relevant details from the error response' + assert thrown.message == 'No matching (parent) cm handle found using alternate ids' + assert thrown.details == 'cannot find a datanode with alternate id ' + alternateId + where: 'the following parameters are used' + scenario | alternateId | cpsPath + 'no match for parent only' | '/a' | '/a/b' + 'no match at all' | '/x/y/z' | '/a/b' + 'no match with trailing separator' | '/c/d/' | '/c/d' + } + def 'Attempt to get non existing cm handle data node by alternate id'() { given: 'query service is invoked and returns empty collection of data nodes' mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] @@ -340,5 +375,4 @@ class InventoryPersistenceImplSpec extends Specification { then: 'the cps data service method to delete data nodes is invoked once with the same xPaths' 1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP); } - } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy index e2062bc80..eb6c7a0f4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy @@ -23,7 +23,7 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext @@ -52,7 +52,7 @@ import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE @SpringBootTest -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiWebClientConfiguration.DmiProperties, DmiDataOperations]) +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, NcmpConfiguration.DmiProperties, DmiDataOperations]) class DmiDataOperationsSpec extends DmiOperationsBaseSpec { @SpringBean diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index a2ec9d1f0..e99e8a3d0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -23,7 +23,7 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -37,7 +37,7 @@ import spock.lang.Shared import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ @SpringBootTest -@ContextConfiguration(classes = [DmiWebClientConfiguration.DmiProperties, DmiModelOperations]) +@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations]) class DmiModelOperationsSpec extends DmiOperationsBaseSpec { @Shared diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy index 061878e95..b7af502de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy @@ -22,7 +22,6 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder @@ -51,7 +50,7 @@ abstract class DmiOperationsBaseSpec extends Specification { ObjectMapper spyObjectMapper = Spy() @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new DmiWebClientConfiguration.DmiProperties(), mockCpsValidator) + DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new NcmpConfiguration.DmiProperties(), mockCpsValidator) def yangModelCmHandle = new YangModelCmHandle() def static dmiServiceName = 'some service name' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy index 54e9f210d..fbf2c3d78 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy @@ -20,13 +20,12 @@ package org.onap.cps.ncmp.api.impl.utils -import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration - import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import spock.lang.Specification @@ -35,7 +34,7 @@ class DmiServiceUrlBuilderSpec extends Specification { static YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id'),'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier') - DmiWebClientConfiguration.DmiProperties dmiProperties = new DmiWebClientConfiguration.DmiProperties() + NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties() def mockCpsValidator = Mock(CpsValidator) @@ -86,7 +85,7 @@ class DmiServiceUrlBuilderSpec extends Specification { when: 'a URL is created' def result = objectUnderTest.getDataOperationRequestUrl(batchRequestQueryParams, batchRequestUriVariables) then: 'it is formed correctly' - assert result.toString() == 'some-service/testBase/v1/data?topic=some+topic&requestId=some+id' + assert result.toString() == 'some-service/testBase/v1/data?topic=some topic&requestId=some id' } def 'Populate batch uri variables.'() { diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index eca28b991..574b49982 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -38,7 +38,6 @@ ncmp: dmi: httpclient: connectionTimeoutInSeconds: 180 - maximumInMemorySizeInMegabytes: 16 auth: username: some-user password: some-password diff --git a/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json b/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json index 09796e2f4..6b665495c 100644 --- a/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json +++ b/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json @@ -1,6 +1,6 @@ { "data": { - "subscriptionId": "cm-subscription-001", + "subscriptionId": "test-id", "predicates": [ { "targetFilter": ["ch1","ch2"], diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index ca466d704..594aa1f0f 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -32,7 +32,7 @@ <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml index 93166ec9c..3176d9a47 100644 --- a/cps-path-parser/pom.xml +++ b/cps-path-parser/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml index 90422d737..ccfb77fb6 100644 --- a/cps-rest/pom.xml +++ b/cps-rest/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java b/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java index f8d709647..af0efe2e2 100644 --- a/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java +++ b/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java @@ -30,11 +30,11 @@ public interface DateTimeUtility { DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); static OffsetDateTime toOffsetDateTime(String datetTimestampAsString) { - return ! StringUtils.hasLength(datetTimestampAsString) - ? null : OffsetDateTime.parse(datetTimestampAsString, ISO_TIMESTAMP_FORMATTER); + return !StringUtils.hasLength(datetTimestampAsString) ? null + : OffsetDateTime.parse(datetTimestampAsString, ISO_TIMESTAMP_FORMATTER); } static String toString(OffsetDateTime offsetDateTime) { return offsetDateTime != null ? ISO_TIMESTAMP_FORMATTER.format(offsetDateTime) : null; } -} +}
\ No newline at end of file diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml index 583bad0ab..221e1a9bf 100644 --- a/cps-ri/pom.xml +++ b/cps-ri/pom.xml @@ -26,7 +26,7 @@ <parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.4.8-SNAPSHOT</version>
+ <version>3.5.0-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-service/pom.xml b/cps-service/pom.xml index adca61763..422496877 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -29,7 +29,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -37,6 +37,10 @@ <dependencies> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> </dependency> @@ -75,6 +79,18 @@ <artifactId>micrometer-core</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> + <dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> </dependency> diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index b3f42279d..f556f4064 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -3,7 +3,7 @@ * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,8 @@ import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDeltaService; import org.onap.cps.cpspath.parser.CpsPathUtil; +import org.onap.cps.events.CpsDataUpdateEventsService; +import org.onap.cps.events.model.Data.Operation; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataValidationException; @@ -61,7 +63,9 @@ public class CpsDataServiceImpl implements CpsDataService { private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L; private final CpsDataPersistenceService cpsDataPersistenceService; + private final CpsDataUpdateEventsService cpsDataUpdateEventsService; private final CpsAnchorService cpsAnchorService; + private final CpsValidator cpsValidator; private final YangParser yangParser; private final CpsDeltaService cpsDeltaService; @@ -81,6 +85,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType); cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes); + sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp); } @Override @@ -99,6 +104,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType); cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes); + sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp); } @Override @@ -116,6 +122,7 @@ public class CpsDataServiceImpl implements CpsDataService { cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath, listElementDataNodeCollection); } + sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp); } @Override @@ -151,6 +158,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream() .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves)); cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves); + sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp); } @Override @@ -167,6 +175,7 @@ public class CpsDataServiceImpl implements CpsDataService { for (final DataNode dataNodeUpdate : dataNodeUpdates) { processDataNodeUpdate(anchor, dataNodeUpdate); } + sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp); } @Override @@ -216,6 +225,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes); + sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp); } @Override @@ -228,6 +238,8 @@ public class CpsDataServiceImpl implements CpsDataService { final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes); + nodesJsonData.keySet().forEach(nodeXpath -> + sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp)); } @Override @@ -248,7 +260,9 @@ public class CpsDataServiceImpl implements CpsDataService { public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes); + sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp); } @Override @@ -258,6 +272,8 @@ public class CpsDataServiceImpl implements CpsDataService { final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); + sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp); } @Override @@ -267,8 +283,12 @@ public class CpsDataServiceImpl implements CpsDataService { final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); + dataNodeXpaths.forEach(dataNodeXpath -> + sendDataUpdatedEvent(anchor, dataNodeXpath, Operation.DELETE, observedTimestamp)); } + @Override @Timed(value = "cps.data.service.datanode.delete.anchor", description = "Time taken to delete all datanodes for an anchor") @@ -276,6 +296,8 @@ public class CpsDataServiceImpl implements CpsDataService { final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); + sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp); } @Override @@ -286,6 +308,9 @@ public class CpsDataServiceImpl implements CpsDataService { cpsValidator.validateNameCharacters(dataspaceName); cpsValidator.validateNameCharacters(anchorNames); cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames); + for (final Anchor anchor : cpsAnchorService.getAnchors(dataspaceName, anchorNames)) { + sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.DELETE, observedTimestamp); + } } @Override @@ -295,6 +320,8 @@ public class CpsDataServiceImpl implements CpsDataService { final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); + sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp); } private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) { @@ -345,4 +372,12 @@ public class CpsDataServiceImpl implements CpsDataService { } } + private void sendDataUpdatedEvent(final Anchor anchor, final String xpath, + final Operation operation, final OffsetDateTime observedTimestamp) { + try { + cpsDataUpdateEventsService.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp); + } catch (final Exception exception) { + log.error("Failed to send message to notification service", exception); + } + } } diff --git a/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java b/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java new file mode 100644 index 000000000..109783488 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java @@ -0,0 +1,113 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 TechMahindra Ltd. + * 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.events; + +import io.cloudevents.CloudEvent; +import io.micrometer.core.annotation.Timed; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.events.model.CpsDataUpdatedEvent; +import org.onap.cps.events.model.Data; +import org.onap.cps.events.model.Data.Operation; +import org.onap.cps.spi.model.Anchor; +import org.onap.cps.utils.DateTimeUtility; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CpsDataUpdateEventsService { + + private final EventsPublisher<CpsDataUpdatedEvent> eventsPublisher; + + @Value("${app.cps.data-updated.topic:cps-data-updated-events}") + private String topicName; + + @Value("${app.cps.data-updated.change-event-notifications-enabled:true}") + private boolean cpsChangeEventNotificationsEnabled; + + @Value("${notification.enabled:false}") + private boolean notificationsEnabled; + + /** + * Publish the cps data update event with header to the public topic. + * + * @param anchor Anchor of the updated data + * @param xpath xpath of the updated data + * @param operation operation performed on the data + * @param observedTimestamp timestamp when data was updated. + */ + @Timed(value = "cps.dataupdate.events.publish", description = "Time taken to publish Data Update event") + public void publishCpsDataUpdateEvent(final Anchor anchor, final String xpath, + final Operation operation, final OffsetDateTime observedTimestamp) { + if (notificationsEnabled && cpsChangeEventNotificationsEnabled) { + final CpsDataUpdatedEvent cpsDataUpdatedEvent = createCpsDataUpdatedEvent(anchor, + observedTimestamp, xpath, operation); + final String updateEventId = anchor.getDataspaceName() + ":" + anchor.getName(); + final Map<String, String> extensions = createUpdateEventExtensions(updateEventId); + final CloudEvent cpsDataUpdatedEventAsCloudEvent = + CpsEvent.builder().type(CpsDataUpdatedEvent.class.getTypeName()).data(cpsDataUpdatedEvent) + .extensions(extensions).build().asCloudEvent(); + eventsPublisher.publishCloudEvent(topicName, updateEventId, cpsDataUpdatedEventAsCloudEvent); + } else { + log.debug("State of Overall Notifications : {} and Cps Change Event Notifications : {}", + notificationsEnabled, cpsChangeEventNotificationsEnabled); + } + } + + private CpsDataUpdatedEvent createCpsDataUpdatedEvent(final Anchor anchor, final OffsetDateTime observedTimestamp, + final String xpath, + final Operation rootNodeOperation) { + final CpsDataUpdatedEvent cpsDataUpdatedEvent = new CpsDataUpdatedEvent(); + final Data updateEventData = new Data(); + updateEventData.setObservedTimestamp(DateTimeUtility.toString(observedTimestamp)); + updateEventData.setDataspaceName(anchor.getDataspaceName()); + updateEventData.setAnchorName(anchor.getName()); + updateEventData.setSchemaSetName(anchor.getSchemaSetName()); + updateEventData.setOperation(getRootNodeOperation(xpath, rootNodeOperation)); + updateEventData.setXpath(xpath); + cpsDataUpdatedEvent.setData(updateEventData); + return cpsDataUpdatedEvent; + } + + private Map<String, String> createUpdateEventExtensions(final String eventKey) { + final Map<String, String> extensions = new HashMap<>(); + extensions.put("correlationid", eventKey); + return extensions; + } + + private Operation getRootNodeOperation(final String xpath, final Operation operation) { + return isRootXpath(xpath) || isRootContainerNodeXpath(xpath) ? operation : Operation.UPDATE; + } + + private static boolean isRootXpath(final String xpath) { + return "/".equals(xpath) || "".equals(xpath); + } + + private static boolean isRootContainerNodeXpath(final String xpath) { + return 0 == xpath.lastIndexOf('/'); + } +} diff --git a/cps-service/src/main/java/org/onap/cps/events/CpsEvent.java b/cps-service/src/main/java/org/onap/cps/events/CpsEvent.java new file mode 100644 index 000000000..c19abc176 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/events/CpsEvent.java @@ -0,0 +1,66 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 TechMahindra Ltd. + * ================================================================================ + * 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.events; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import java.net.URI; +import java.util.Map; +import java.util.UUID; +import lombok.Builder; +import org.apache.commons.lang3.StringUtils; +import org.onap.cps.utils.JsonObjectMapper; + +@Builder +public class CpsEvent { + + private Object data; + private Map<String, String> extensions; + private String type; + @Builder.Default + private static final String CLOUD_EVENT_SPEC_VERSION_V1 = "1.0.0"; + @Builder.Default + private static final String CLOUD_EVENT_SOURCE = "CPS"; + + private final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()); + + /** + * Creates ncmp cloud event with provided attributes. + * + * @return Cloud Event + */ + + public CloudEvent asCloudEvent() { + final CloudEventBuilder cloudEventBuilder = io.cloudevents.core.builder + .CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create(CLOUD_EVENT_SOURCE)) + .withType(type) + .withDataSchema(URI.create("urn:cps:" + type + ":" + CLOUD_EVENT_SPEC_VERSION_V1)) + .withData(jsonObjectMapper.asJsonBytes(data)); + extensions.entrySet().stream() + .filter(extensionEntry -> StringUtils.isNotBlank(extensionEntry.getValue())) + .forEach(extensionEntry -> + cloudEventBuilder.withExtension(extensionEntry.getKey(), extensionEntry.getValue())); + return cloudEventBuilder.build(); + } +} diff --git a/cps-service/src/test/java/org/onap/cps/utils/DateTimeUtility.java b/cps-service/src/main/java/org/onap/cps/utils/DateTimeUtility.java index f8d709647..c2310707a 100644 --- a/cps-service/src/test/java/org/onap/cps/utils/DateTimeUtility.java +++ b/cps-service/src/main/java/org/onap/cps/utils/DateTimeUtility.java @@ -1,12 +1,13 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2021 Bell Canada. + * 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 + * 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, @@ -22,18 +23,12 @@ package org.onap.cps.utils; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -import org.springframework.util.StringUtils; public interface DateTimeUtility { String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); - static OffsetDateTime toOffsetDateTime(String datetTimestampAsString) { - return ! StringUtils.hasLength(datetTimestampAsString) - ? null : OffsetDateTime.parse(datetTimestampAsString, ISO_TIMESTAMP_FORMATTER); - } - static String toString(OffsetDateTime offsetDateTime) { return offsetDateTime != null ? ISO_TIMESTAMP_FORMATTER.format(offsetDateTime) : null; } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index b2b2d7d44..fcbfd0561 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -3,7 +3,7 @@ * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada. - * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,13 @@ package org.onap.cps.api.impl +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.core.read.ListAppender import org.onap.cps.TestUtils import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDeltaService +import org.onap.cps.events.CpsDataUpdateEventsService import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.ConcurrencyException @@ -41,6 +45,8 @@ import org.onap.cps.utils.YangParser import org.onap.cps.utils.YangParserHelper import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Shared import spock.lang.Specification import java.time.OffsetDateTime @@ -52,13 +58,28 @@ class CpsDataServiceImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache) def mockCpsDeltaService = Mock(CpsDeltaService); + def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService) - def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService) + def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService) + + def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class) + def loggingListAppender + def applicationContext = new AnnotationConfigApplicationContext() def setup() { mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1 mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_2) >> anchor2 + logger.setLevel(Level.DEBUG) + loggingListAppender = new ListAppender() + logger.addAppender(loggingListAppender) + loggingListAppender.start() + applicationContext.refresh() + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(CpsDataServiceImpl.class)).detachAndStopAllAppenders() + applicationContext.close() } @Shared @@ -459,6 +480,19 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L) } + def 'Exception is thrown while publishing the notification.'(){ + given: 'schema set for given anchor and dataspace references test-tree model' + setupSchemaSetMocks('test-tree.yang') + when: 'publisher set to throw an exception' + mockDataUpdateEventsService.publishCpsDataUpdateEvent(_, _, _, _) >> { throw new Exception("publishing failed")} + and: 'an update event is performed' + objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp) + then: 'the exception is not bubbled up' + noExceptionThrown() + and: "the exception message is logged" + def logs = loggingListAppender.list.toString() + assert logs.contains('Failed to send message to notification service') + } def setupSchemaSetMocks(String... yangResources) { def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 140dfaac9..57f2f8ea7 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -3,7 +3,7 @@ * Copyright (C) 2021-2024 Nordix Foundation.
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
+import org.onap.cps.events.CpsDataUpdateEventsService
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
import org.onap.cps.spi.model.Anchor
@@ -50,7 +51,8 @@ class E2ENetworkSliceSpec extends Specification { def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,
mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)
+ def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
diff --git a/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy new file mode 100644 index 000000000..11842645c --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy @@ -0,0 +1,121 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 TechMahindra Ltd. + * 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.events + +import static org.onap.cps.events.model.Data.Operation.CREATE +import static org.onap.cps.events.model.Data.Operation.DELETE +import static org.onap.cps.events.model.Data.Operation.UPDATE + +import com.fasterxml.jackson.databind.ObjectMapper +import io.cloudevents.CloudEvent +import io.cloudevents.core.CloudEventUtils +import io.cloudevents.jackson.PojoCloudEventDataMapper +import org.onap.cps.events.model.CpsDataUpdatedEvent +import org.onap.cps.spi.model.Anchor +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +import java.time.OffsetDateTime + +@ContextConfiguration(classes = [ObjectMapper, JsonObjectMapper]) +class CpsDataUpdateEventsServiceSpec extends Specification { + def mockEventsPublisher = Mock(EventsPublisher) + def objectMapper = new ObjectMapper(); + + def objectUnderTest = new CpsDataUpdateEventsService(mockEventsPublisher) + + def 'Create and Publish cps update event where events are #scenario'() { + given: 'an anchor, operation and observed timestamp' + def anchor = new Anchor('anchor01', 'dataspace01', 'schema01'); + def operation = operationInRequest + def observedTimestamp = OffsetDateTime.now() + and: 'notificationsEnabled is #notificationsEnabled and it will be true as default' + objectUnderTest.notificationsEnabled = true + and: 'cpsChangeEventNotificationsEnabled is also true' + objectUnderTest.cpsChangeEventNotificationsEnabled = true + when: 'service is called to publish data update event' + objectUnderTest.topicName = "cps-core-event" + objectUnderTest.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp) + then: 'the event contains the required attributes' + 1 * mockEventsPublisher.publishCloudEvent('cps-core-event', 'dataspace01:anchor01', _) >> { + args -> + { + def cpsDataUpdatedEvent = (args[2] as CloudEvent) + assert cpsDataUpdatedEvent.getExtension('correlationid') == 'dataspace01:anchor01' + assert cpsDataUpdatedEvent.type == 'org.onap.cps.events.model.CpsDataUpdatedEvent' + assert cpsDataUpdatedEvent.source.toString() == 'CPS' + def actualEventOperation = CloudEventUtils.mapData(cpsDataUpdatedEvent, PojoCloudEventDataMapper.from(objectMapper, CpsDataUpdatedEvent.class)).getValue().data.operation + assert actualEventOperation == expectedOperation + } + } + where: 'the following values are used' + scenario | xpath | operationInRequest || expectedOperation + 'empty xpath' | '' | CREATE || CREATE + 'root xpath and create operation' | '/' | CREATE || CREATE + 'root xpath and update operation' | '/' | UPDATE || UPDATE + 'root xpath and delete operation' | '/' | DELETE || DELETE + 'not root xpath and update operation' | 'test' | UPDATE || UPDATE + 'root node xpath and create operation' | '/test' | CREATE || CREATE + 'non root node xpath and update operation' | '/test/path' | CREATE || UPDATE + 'non root node xpath and delete operation' | '/test/path' | DELETE || UPDATE + } + + def 'publish cps update event when #scenario'() { + given: 'an anchor, operation and observed timestamp' + def anchor = new Anchor('anchor01', 'dataspace01', 'schema01'); + def operation = CREATE + def observedTimestamp = OffsetDateTime.now() + and: 'notificationsEnabled is #notificationsEnabled' + objectUnderTest.notificationsEnabled = notificationsEnabled + and: 'cpsChangeEventNotificationsEnabled is #cpsChangeEventNotificationsEnabled' + objectUnderTest.cpsChangeEventNotificationsEnabled = cpsChangeEventNotificationsEnabled + when: 'service is called to publish data update event' + objectUnderTest.topicName = "cps-core-event" + objectUnderTest.publishCpsDataUpdateEvent(anchor, '/', operation, observedTimestamp) + then: 'the event contains the required attributes' + expectedCallToPublisher * mockEventsPublisher.publishCloudEvent('cps-core-event', 'dataspace01:anchor01', _) + where: 'below scenarios are present' + scenario | notificationsEnabled | cpsChangeEventNotificationsEnabled || expectedCallToPublisher + 'both notifications enabled' | true | true || 1 + 'both notifications disabled' | false | false || 0 + 'only CPS change event notification enabled' | false | true || 0 + 'only overall notification enabled' | true | false || 0 + + } + + def 'publish cps update event when no timestamp provided'() { + given: 'an anchor, operation and null timestamp' + def anchor = new Anchor('anchor01', 'dataspace01', 'schema01'); + def operation = CREATE + def observedTimestamp = null + and: 'notificationsEnabled is true' + objectUnderTest.notificationsEnabled = true + and: 'cpsChangeEventNotificationsEnabled is true' + objectUnderTest.cpsChangeEventNotificationsEnabled = true + when: 'service is called to publish data update event' + objectUnderTest.topicName = "cps-core-event" + objectUnderTest.publishCpsDataUpdateEvent(anchor, '/', operation, observedTimestamp) + then: 'the event is published' + 1 * mockEventsPublisher.publishCloudEvent('cps-core-event', 'dataspace01:anchor01', _) + } +} diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml index bb7d3e56f..e62c01d6c 100644 --- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml +++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>dmi-plugin-demo-and-csit-stub</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> </parent> <artifactId>dmi-plugin-demo-and-csit-stub-app</artifactId> diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml index 288159cf6..c0b4eadb1 100644 --- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml +++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml @@ -21,7 +21,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>dmi-plugin-demo-and-csit-stub</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> </parent> <artifactId>dmi-plugin-demo-and-csit-stub-service</artifactId> diff --git a/dmi-plugin-demo-and-csit-stub/pom.xml b/dmi-plugin-demo-and-csit-stub/pom.xml index d8576459b..071927252 100644 --- a/dmi-plugin-demo-and-csit-stub/pom.xml +++ b/dmi-plugin-demo-and-csit-stub/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index 8a6c13ebe..4f5180d51 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -26,7 +26,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -190,7 +190,7 @@ components: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index da0b0b3f9..9f6a1b2b6 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -70,7 +70,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -216,7 +216,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -331,7 +331,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -449,7 +449,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -571,7 +571,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -615,7 +615,8 @@ paths: post: description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId - to identify the relevant messages. + to identify the relevant messages. A maximum of 50 cm handles per operation + is supported. operationId: executeDataOperationForCmHandles parameters: - allowReserved: true @@ -652,7 +653,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -668,6 +669,17 @@ paths: schema: $ref: '#/components/schemas/ErrorMessage' description: Forbidden + "413": + content: + application/json: + example: + status: 413 + message: Payload Too Large error message + details: Payload Too Large error details + schema: + $ref: '#/components/schemas/ErrorMessage' + description: The request is larger than the server is willing or able to + process "500": content: application/json: @@ -776,7 +788,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -843,7 +855,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -981,7 +993,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1034,7 +1046,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1087,7 +1099,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1164,7 +1176,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1227,7 +1239,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1289,7 +1301,7 @@ paths: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1603,7 +1615,7 @@ components: content: application/json: example: - status: 400 BAD_REQUEST + status: 400 message: Bad request error message details: Bad request error details schema: @@ -1662,6 +1674,16 @@ components: schema: $ref: '#/components/schemas/ErrorMessage' description: The specified resource was not found + PayloadTooLarge: + content: + application/json: + example: + status: 413 + message: Payload Too Large error message + details: Payload Too Large error details + schema: + $ref: '#/components/schemas/ErrorMessage' + description: The request is larger than the server is willing or able to process schemas: ErrorMessage: properties: @@ -1743,6 +1765,8 @@ components: type: string targetIds: items: + description: "targeted cm handles, maximum of 50 supported. If this limit\ + \ is exceeded the request wil be refused." example: "[\"da310eecdb8d44c2acc0ddaae01174b1\",\"c748c58f8e0b438f9fd1f28370b17d47\"\ ]" type: string diff --git a/docs/release-notes.rst b/docs/release-notes.rst index f04f977c4..82a890d79 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -16,6 +16,61 @@ CPS Release Notes .. * * * NEW DELHI * * * .. ========================= +Version: 3.4.10 +=============== + +Release Data +------------ + ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.4.10 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.4.10 New Delhi | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | Not yet released | +| | | ++--------------------------------------+--------------------------------------------------------+ + +Bug Fixes +--------- +3.4.10 + +Features +-------- + +Version: 3.4.9 +============== + +Release Data +------------ + ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.4.9 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.4.9 New Delhi | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | 2024 May 14 | +| | | ++--------------------------------------+--------------------------------------------------------+ + +Bug Fixes +--------- +3.4.9 + - `CPS-2211 <https://jira.onap.org/browse/CPS-2211>`_ Toggle switch to disable CPS Core change events if not used by application. Set CPS_CHANGE_EVENT_NOTIFICATIONS_ENABLED environment variable for the same. + +Features +-------- + Version: 3.4.8 ============== @@ -32,7 +87,7 @@ Release Data | **Release designation** | 3.4.8 New Delhi | | | | +--------------------------------------+--------------------------------------------------------+ -| **Release date** | Not yet released | +| **Release date** | 2024 May 1 | | | | +--------------------------------------+--------------------------------------------------------+ @@ -40,6 +95,9 @@ Bug Fixes --------- 3.4.8 - `CPS-2186 <https://jira.onap.org/browse/CPS-2186>`_ Report async task failures to client topic during data operations request + - `CPS-2190 <https://jira.onap.org/browse/CPS-2190>`_ Improve performance of NCMP module searches + - `CPS-2194 <https://jira.onap.org/browse/CPS-2194>`_ Added defaults for CPS and DMI username and password + - `CPS-2204 <https://jira.onap.org/browse/CPS-2204>`_ Added error handling for yang module upgrade operation Features -------- diff --git a/integration-test/pom.xml b/integration-test/pom.xml index d4ee0cc68..f228e0119 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -33,15 +33,6 @@ <dependencies> <!-- T E S T D E P E N D E N C I E S --> <dependency> - <groupId>org.codehaus.groovy</groupId> - <artifactId>groovy</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter</artifactId> - </dependency> - <dependency> <groupId>${project.groupId}</groupId> <artifactId>cps-rest</artifactId> <scope>test</scope> @@ -67,6 +58,26 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>kafka</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>postgresql</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <scope>test</scope> @@ -93,19 +104,9 @@ </dependency> <dependency> <groupId>org.testcontainers</groupId> - <artifactId>postgresql</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.testcontainers</groupId> <artifactId>spock</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.testcontainers</groupId> - <artifactId>kafka</artifactId> - <scope>test</scope> - </dependency> </dependencies> <profiles> 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 51b02387e..44fc25835 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 @@ -20,8 +20,13 @@ package org.onap.cps.integration.base +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT + import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import okhttp3.mockwebserver.MockWebServer import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsDataspaceService @@ -48,23 +53,12 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.annotation.ComponentScan import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.client.ExpectedCount -import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.servlet.MockMvc -import org.springframework.web.client.RestTemplate import org.testcontainers.spock.Testcontainers import spock.lang.Shared import spock.lang.Specification import spock.util.concurrent.PollingConditions -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = [CpsDataspaceService]) @Testcontainers @EnableAutoConfiguration @@ -111,17 +105,16 @@ abstract class CpsIntegrationSpecBase extends Specification { NetworkCmProxyQueryService networkCmProxyQueryService @Autowired - RestTemplate restTemplate - - @Autowired ModuleSyncWatchdog moduleSyncWatchdog @Autowired JsonObjectMapper jsonObjectMapper - MockRestServiceServer mockDmiServer = null + MockWebServer mockDmiServer = null + DmiDispatcher dmiDispatcher = new DmiDispatcher() + + def DMI_URL = null - static DMI_URL = 'http://mock-dmi-server' static NO_MODULE_SET_TAG = '' static GENERAL_TEST_DATASPACE = 'generalTestDataspace' static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet' @@ -135,7 +128,14 @@ abstract class CpsIntegrationSpecBase extends Specification { createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE) initialized = true } - mockDmiServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build() + mockDmiServer = new MockWebServer() + mockDmiServer.setDispatcher(dmiDispatcher) + mockDmiServer.start() + DMI_URL = String.format("http://%s:%s", mockDmiServer.getHostName(), mockDmiServer.getPort()) + } + + def cleanup() { + mockDmiServer.shutdown() } def static readResourceDataFile(filename) { @@ -217,23 +217,6 @@ abstract class CpsIntegrationSpecBase extends Specification { networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } - def mockDmiResponsesForModuleSync(dmiPlugin, cmHandleId, dmiModuleReferencesResponse, dmiModuleResourcesResponse) { - mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/modules")) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(dmiModuleReferencesResponse)) - mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/moduleResources")) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(dmiModuleResourcesResponse)) - } - - def mockDmiIsNotAvailableForModuleSync(dmiPlugin, cmHandleId) { - mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/modules")) - .andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE)) - } - - def mockDmiWillRespondToHealthChecks(dmiPlugin) { - mockDmiServer.expect(ExpectedCount.between(0, Integer.MAX_VALUE), requestTo("${dmiPlugin}/actuator/health")) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body('{"status":"UP"}')) - } - def overrideCmHandleLastUpdateTime(cmHandleId, newUpdateTime) { String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); 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 new file mode 100644 index 000000000..6676cb74c --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy @@ -0,0 +1,105 @@ +/* + * ============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 static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile + +import org.springframework.http.HttpHeaders +import java.util.regex.Matcher +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType + +/** + * This class simulates responses from the DMI server in NCMP integration tests. + * + * It is to be used with a MockWebServer, using mockWebServer.setDispatcher(new DmiDispatcher()). + * + * It currently implements the following endpoints: + * - /actuator/health: healthcheck endpoint that responds with 200 OK / {"status":"UP"} + * - /dmi/v1/ch/{cmHandleId}/modules: returns module references for a CM handle + * - /dmi/v1/ch/{cmHandleId}/moduleResources: returns modules resources for a CM handle + * + * The module resource/reference responses are generated based on the module names in the map moduleNamesPerCmHandleId. + * To configure the DMI so that CM handle 'ch-1' will have modules 'M1' and 'M2', you may use: + * dmiDispatcher.moduleNamesPerCmHandleId.put('ch-1', ['M1', 'M2']); + * + * To simulate the DMI not being available, the boolean isAvailable may be set to false, in which case the mock server + * will always respond with 503 Service Unavailable. + */ +class DmiDispatcher extends Dispatcher { + + static final MODULE_REFERENCES_RESPONSE_TEMPLATE = readResourceDataFile('mock-dmi-responses/moduleReferencesTemplate.json') + static final MODULE_RESOURCES_RESPONSE_TEMPLATE = readResourceDataFile('mock-dmi-responses/moduleResourcesTemplate.json') + + def isAvailable = true + + Map<String, List<String>> moduleNamesPerCmHandleId = [:] + + @Override + MockResponse dispatch(RecordedRequest request) { + if (!isAvailable) { + return new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.value()) + } + switch (request.path) { + case ~/^\/dmi\/v1\/ch\/(.*)\/modules$/: + def cmHandleId = Matcher.lastMatcher[0][1] + return getModuleReferencesResponse(cmHandleId) + + case ~/^\/dmi\/v1\/ch\/(.*)\/moduleResources$/: + def cmHandleId = Matcher.lastMatcher[0][1] + return getModuleResourcesResponse(cmHandleId) + + default: + throw new IllegalArgumentException('Mock DMI does not handle path ' + request.path) + } + } + + private getModuleReferencesResponse(cmHandleId) { + def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect { + MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) + }.join(',') + ']}' + return mockOkResponseWithBody(moduleReferences) + } + + private getModuleResourcesResponse(cmHandleId) { + def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect { + MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) + }.join(',') + ']' + return mockOkResponseWithBody(moduleResources) + } + + private getModuleNamesForCmHandle(cmHandleId) { + if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) { + throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId) + } + return moduleNamesPerCmHandleId.get(cmHandleId) + } + + private static mockOkResponseWithBody(responseBody) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.value()) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .setBody(responseBody) + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy index d8585fb97..4ffe586a9 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy @@ -20,38 +20,45 @@ package org.onap.cps.integration.functional -import spock.lang.Ignore - -import java.time.Duration -import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.client.match.MockRestRequestMatchers - import static org.springframework.http.HttpMethod.GET import static org.springframework.http.HttpMethod.DELETE import static org.springframework.http.HttpMethod.PATCH import static org.springframework.http.HttpMethod.POST import static org.springframework.http.HttpMethod.PUT -import static org.springframework.test.web.client.match.MockRestRequestMatchers.method -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -@Ignore +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.jetbrains.annotations.NotNull +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import spock.util.concurrent.PollingConditions + class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { - static final MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') + def lastAuthHeaderReceived = null def setup() { - mockDmiWillRespondToHealthChecks(DMI_URL) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE, MODULE_RESOURCES_RESPONSE) + dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] registerCmHandle(DMI_URL, 'ch-1', NO_MODULE_SET_TAG) - mockDmiServer.reset() - mockDmiWillRespondToHealthChecks(DMI_URL) + + mockDmiServer.setDispatcher(new Dispatcher() { + @Override + MockResponse dispatch(@NotNull RecordedRequest request) throws InterruptedException { + if (request.path == '/actuator/health') { + return new MockResponse() + .addHeader("Content-Type", MediaType.APPLICATION_JSON).setBody('{"status":"UP"}') + .setResponseCode(HttpStatus.OK.value()) + } else { + lastAuthHeaderReceived = request.getHeader('Authorization') + return new MockResponse().setResponseCode(HttpStatus.OK.value()) + } + } + }) } def cleanup() { @@ -59,12 +66,6 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { } def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() { - given: 'DMI will expect to receive a request with a bearer token' - def targetDmiUrl = "$DMI_URL/dmi/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=my-resource-id" - mockDmiServer.expect(requestTo(targetDmiUrl)) - .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) - when: 'a pass-through data request is sent to NCMP with a bearer token' mvc.perform(request(httpMethod, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') .queryParam('resourceIdentifier', 'my-resource-id') @@ -74,19 +75,13 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with bearer token' - mockDmiServer.verify() + lastAuthHeaderReceived == 'Bearer some-bearer-token' where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] } def 'Basic auth header is NOT passed from NCMP to DMI in pass-through data operations.'() { - given: 'DMI will expect to receive a request with no authorization header' - def targetDmiUrl = "$DMI_URL/dmi/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=my-resource-id" - mockDmiServer.expect(requestTo(targetDmiUrl)) - .andExpect(MockRestRequestMatchers.headerDoesNotExist(HttpHeaders.AUTHORIZATION)) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) - when: 'a pass-through data request is sent to NCMP with basic authentication' mvc.perform(request(httpMethod, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') .queryParam('resourceIdentifier', 'my-resource-id') @@ -96,18 +91,13 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with no authorization header' - mockDmiServer.verify() + lastAuthHeaderReceived == null where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] } def 'Bearer token is passed from NCMP to DMI in async batch pass-through data operation.'() { - given: 'DMI will expect to receive a request with a bearer token' - mockDmiServer.expect(method(POST)) - .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) - when: 'a pass-through async data request is sent to NCMP with a bearer token' def requestBody = """{"operations": [{ "operation": "read", @@ -124,7 +114,9 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI will receive the async request with bearer token' - mockDmiServer.verify(Duration.ofSeconds(1)) + new PollingConditions().within(3, () -> { + assert lastAuthHeaderReceived == 'Bearer some-bearer-token' + }) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy index 4e291490d..5c337f179 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy @@ -20,8 +20,6 @@ package org.onap.cps.integration.functional -import spock.lang.Ignore - import java.time.Duration import java.time.OffsetDateTime import org.apache.kafka.common.TopicPartition @@ -37,26 +35,19 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.v1.LcmEvent import spock.util.concurrent.PollingConditions -@Ignore class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { NetworkCmProxyDataService objectUnderTest def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class) - static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') - static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json') - static final MODULE_RESOURCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json') - def setup() { objectUnderTest = networkCmProxyDataService - mockDmiWillRespondToHealthChecks(DMI_URL) } def 'CM Handle registration is successful.'() { given: 'DMI will return modules when requested' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) + dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] and: 'consumer subscribed to topic' kafkaConsumer.subscribe(['ncmp-events']) @@ -91,16 +82,13 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { and: 'the CM-handle has expected modules' assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - and: 'DMI received expected requests' - mockDmiServer.verify() - cleanup: 'deregister CM handle' deregisterCmHandle(DMI_URL, 'ch-1') } def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() { given: 'DMI is not available to handle requests' - mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-1') + dmiDispatcher.isAvailable = false when: 'a CM-handle is registered for creation' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') @@ -125,13 +113,11 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { } def 'Create a CM-handle with existing moduleSetTag.'() { - given: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B) + given: 'DMI will return modules when requested' + dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] + and: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"' registerCmHandle(DMI_URL, 'ch-1', 'A') registerCmHandle(DMI_URL, 'ch-2', 'B') - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort() when: 'a CM-handle is registered for creation with moduleSetTag "B"' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B') @@ -155,11 +141,7 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { def 'CM Handle retry after failed module sync.'() { given: 'DMI is not initially available to handle requests' - mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-1') - mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-2') - and: 'DMI will be available for retry' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B) + dmiDispatcher.isAvailable = false when: 'CM-handles are registered for creation' def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')] @@ -182,7 +164,11 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.ADVISED assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.ADVISED - when: 'module sync runs' + when: 'DMI is available for retry' + dmiDispatcher.isAvailable = true + and: 'DMI will return expected modules' + dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] + and: 'module sync runs' moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' new PollingConditions().within(3, () -> { @@ -195,8 +181,6 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { and: 'CM-handles have expected module set tags (blank)' assert objectUnderTest.getNcmpServiceCmHandle('ch-1').moduleSetTag == '' assert objectUnderTest.getNcmpServiceCmHandle('ch-2').moduleSetTag == '' - and: 'DMI received expected requests' - mockDmiServer.verify() cleanup: 'deregister CM handle' deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2']) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy index 68c354e02..4d1d77e69 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy @@ -27,40 +27,26 @@ import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.UpgradedCmHandles -import org.springframework.http.HttpStatus -import spock.lang.Ignore import spock.util.concurrent.PollingConditions -import static org.springframework.test.web.client.match.MockRestRequestMatchers.anything -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus - -@Ignore class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { NetworkCmProxyDataService objectUnderTest - static final INITIAL_MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final INITIAL_MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') - static final UPDATED_MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json') - static final UPDATED_MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json') static final CM_HANDLE_ID = 'ch-1' static final CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG = 'ch-2' def setup() { objectUnderTest = networkCmProxyDataService - mockDmiWillRespondToHealthChecks(DMI_URL) } def 'Upgrade CM-handle with new moduleSetTag or no moduleSetTag.'() { - given: 'DMI will return modules for initial registration' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) - and: 'DMI returns different modules for upgrade' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, UPDATED_MODULE_REFERENCES_RESPONSE, UPDATED_MODULE_RESOURCES_RESPONSE) - - when: 'a CM-handle is created with expected initial modules: M1 and M2' + given: 'a CM-handle is created with expected initial modules: M1 and M2' + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag) assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() - and: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" + + when: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule( new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) @@ -74,14 +60,16 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE assert cmHandleCompositeState.lockReason.details == "Upgrade to ModuleSetTag: ${updatedModuleSetTag}" - when: 'module sync runs' + when: 'DMI will return different modules for upgrade: M1 and M3' + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3'] + and: 'module sync runs' moduleSyncWatchdog.resetPreviouslyFailedCmHandles() moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to READY state' - new PollingConditions().within(3, () -> { + new PollingConditions().eventually { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState - }) + } and: 'the CM-handle has expected moduleSetTag' assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag @@ -89,9 +77,6 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { and: 'CM-handle has expected updated modules: M1 and M3' assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() - and: 'DMI received expected requests' - mockDmiServer.verify() - cleanup: 'deregister CM-handle' deregisterCmHandle(DMI_URL, CM_HANDLE_ID) @@ -105,8 +90,8 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { def 'Upgrade CM-handle with existing moduleSetTag.'() { given: 'DMI will return modules for registration' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, UPDATED_MODULE_REFERENCES_RESPONSE, UPDATED_MODULE_RESOURCES_RESPONSE) + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG] = ['M1', 'M3'] and: "an existing CM-handle handle with moduleSetTag '${updatedModuleSetTag}'" registerCmHandle(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag) assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG).moduleName.sort() @@ -127,9 +112,9 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to READY state' - new PollingConditions().within(3, () -> { + new PollingConditions().eventually { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState - }) + } and: 'the CM-handle has expected moduleSetTag' assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag @@ -148,7 +133,7 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { def 'Skip upgrade of CM-handle with same moduleSetTag as before.'() { given: 'an existing CM-handle with expected initial modules: M1 and M2' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] registerCmHandle(DMI_URL, CM_HANDLE_ID, 'same') assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() @@ -171,14 +156,13 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { } def 'Upgrade of CM-handle fails due to DMI error.'() { - given: 'DMI will return modules for initial registration' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) - and: 'DMI returns error code for upgrade' - mockDmiServer.expect(anything()).andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE)) - - when: 'a CM-handle is created' + given: 'a CM-handle exists' + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] registerCmHandle(DMI_URL, CM_HANDLE_ID, 'oldTag') - and: 'the CM-handle is upgraded' + and: 'DMI is not available for upgrade' + dmiDispatcher.isAvailable = false + + when: 'the CM-handle is upgraded' def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag') networkCmProxyDataService.updateDmiRegistrationAndSyncModule( new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) @@ -188,11 +172,11 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED' - new PollingConditions().within(3, () -> { + new PollingConditions(timeout: 3).eventually { def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID) assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE_FAILED - }) + } and: 'the CM-handle has same moduleSetTag as before' assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'oldTag' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy index 14b9f6560..302c7e512 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy @@ -1,13 +1,30 @@ -package org.onap.cps.integration.functional +/* + * ============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========================================================= + */ -import spock.lang.Ignore +package org.onap.cps.integration.functional import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; import org.onap.cps.integration.base.CpsIntegrationSpecBase; import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService; import org.springframework.beans.factory.annotation.Autowired; -@Ignore class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { @Autowired diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy index 5e250b0e5..950cd65e7 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy @@ -22,7 +22,6 @@ package org.onap.cps.integration.functional import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.springframework.http.MediaType -import spock.lang.Ignore import spock.util.concurrent.PollingConditions import static org.hamcrest.Matchers.containsInAnyOrder import static org.hamcrest.Matchers.hasSize @@ -31,23 +30,15 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -@Ignore class NcmpRestApiSpec extends CpsIntegrationSpecBase { - static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') - static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json') - static final MODULE_RESOURCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json') - - def setup() { - mockDmiWillRespondToHealthChecks(DMI_URL) - } - def 'Register CM Handles using REST API.'() { given: 'DMI will return modules' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-3', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B) + dmiDispatcher.moduleNamesPerCmHandleId = [ + 'ch-1': ['M1', 'M2'], + 'ch-2': ['M1', 'M2'], + 'ch-3': ['M1', 'M3'] + ] and: 'a POST request is made to register the CM Handles' def requestBody = '{"dmiPlugin":"'+DMI_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) @@ -55,10 +46,12 @@ class NcmpRestApiSpec extends CpsIntegrationSpecBase { when: 'module sync runs' moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' - new PollingConditions(timeout: 3, delay: 0.5).eventually { - mvc.perform(get('/ncmp/v1/ch/ch-1')) - .andExpect(status().isOk()) - .andExpect(jsonPath('$.state.cmHandleState').value('READY')) + new PollingConditions().eventually { + (1..3).each { + mvc.perform(get('/ncmp/v1/ch/ch-'+it)) + .andExpect(status().isOk()) + .andExpect(jsonPath('$.state.cmHandleState').value('READY')) + } } } @@ -73,7 +66,7 @@ class NcmpRestApiSpec extends CpsIntegrationSpecBase { ] }""".formatted(moduleName) expect: "a search for module ${moduleName} returns expected CM handles" - mvc.perform(post(DMI_URL+'/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) + mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy index 0c1e1f5d8..135586936 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy @@ -47,7 +47,7 @@ class CpsDataServiceLimitsPerfTest extends CpsPerfTestBase { resourceMeter.stop() def durationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'the operation completes within 12 seconds' - recordAndAssertResourceUsage("Creating 33,000 books", 12, durationInSeconds, 150, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Creating 33,000 books", 18.891, durationInSeconds, 150, resourceMeter.getTotalMemoryUsageInMB()) } def 'Get data nodes from multiple xpaths 32K (2^15) limit exceeded.'() { @@ -88,7 +88,7 @@ class CpsDataServiceLimitsPerfTest extends CpsPerfTestBase { resourceMeter.stop() def durationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'test data is deleted in 1 second' - recordAndAssertResourceUsage("Deleting test data", 1, durationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Deleting test data", 0.141, durationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB()) } def countDataNodes() { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy index 2efbf7d3f..1b449b8da 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy @@ -40,7 +40,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def setupDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'setup duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete test setup', 100, setupDurationInSeconds, 800, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete test setup', 103, setupDurationInSeconds, 800, resourceMeter.getTotalMemoryUsageInMB()) } def 'Delete 100 container nodes'() { @@ -56,7 +56,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete 100 containers', 2.5, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete 100 containers', 3.9, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB()) } def 'Batch delete 100 container nodes'() { @@ -70,7 +70,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Batch delete 100 containers', 0.6, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Batch delete 100 containers', 0.87, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB()) } def 'Delete 100 list elements'() { @@ -86,7 +86,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete 100 lists elements', 2.5, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete 100 lists elements', 4.22, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB()) } def 'Batch delete 100 list elements'() { @@ -100,7 +100,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Batch delete 100 lists elements', 0.6, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Batch delete 100 lists elements', 0.87, deleteDurationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB()) } def 'Delete 100 whole lists'() { @@ -116,7 +116,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete 100 whole lists', 6, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete 100 whole lists', 5.4, deleteDurationInSeconds, 20, resourceMeter.getTotalMemoryUsageInMB()) } def 'Batch delete 100 whole lists'() { @@ -130,7 +130,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Batch delete 100 whole lists', 5, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Batch delete 100 whole lists', 1.98, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB()) } def 'Delete 1 large data node'() { @@ -140,7 +140,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete one large node', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete one large node', 2.35, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) } def 'Delete root node with many descendants'() { @@ -150,7 +150,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete root node', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete root node', 2.23, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) } def 'Delete data nodes for an anchor'() { @@ -160,7 +160,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete data nodes for anchor', 2, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete data nodes for anchor', 2.25, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) } def 'Batch delete 100 non-existing nodes'() { @@ -174,7 +174,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Batch delete 100 non-existing', 7, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Batch delete 100 non-existing', 0.74, deleteDurationInSeconds, 3, resourceMeter.getTotalMemoryUsageInMB()) } def 'Clean up test data'() { @@ -186,7 +186,7 @@ class DeletePerfTest extends CpsPerfTestBase { resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' - recordAndAssertResourceUsage('Delete test cleanup', 10, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage('Delete test cleanup', 11.09, deleteDurationInSeconds, 1, resourceMeter.getTotalMemoryUsageInMB()) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy index 8a228a353..060c9cb6d 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/GetPerfTest.groovy @@ -44,9 +44,9 @@ class GetPerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Read datatrees with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: 'the following parameters are used' scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes - 'no descendants' | OMIT_DESCENDANTS || 0.02 | 1 | 1 + 'no descendants' | OMIT_DESCENDANTS || 0.01 | 1 | 1 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.06 | 5 | 1 + OPENROADM_DEVICES_PER_ANCHOR - 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.47 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE } def 'Read data trees for multiple xpaths'() { @@ -60,7 +60,7 @@ class GetPerfTest extends CpsPerfTestBase { then: 'requested nodes and their descendants are returned' assert countDataNodesInTree(result) == OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE and: 'all data is read within expected time and memory used is within limit' - recordAndAssertResourceUsage("Read datatrees for multiple xpaths", 4 , durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Read datatrees for multiple xpaths", 2.2, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB()) } def 'Read for multiple xpaths to non-existing datanodes'() { @@ -74,7 +74,7 @@ class GetPerfTest extends CpsPerfTestBase { then: 'no data is returned' assert result.isEmpty() and: 'the operation completes within within expected time' - recordAndAssertResourceUsage("Read non-existing xpaths", 0.02, durationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Read non-existing xpaths", 0.01, durationInSeconds, 2, resourceMeter.getTotalMemoryUsageInMB()) } def 'Read complete data trees using #scenario.'() { @@ -88,9 +88,9 @@ class GetPerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Read datatrees using ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: 'the following xpaths are used' scenario | xpath || durationLimit | memoryLimit | expectedNumberOfDataNodes - 'openroadm root' | '/' || 2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE - 'openroadm top element' | '/openroadm-devices' || 2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE - 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 3 | 250 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'openroadm root' | '/' || 1.28 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'openroadm top element' | '/openroadm-devices' || 1.3 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 1.51 | 250 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy index 0ae018d46..f31c6a988 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy @@ -45,11 +45,11 @@ class QueryPerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Query 1 anchor ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: 'the following parameters are used' scenario | cpsPath || durationLimit | memoryLimit | expectedNumberOfDataNodes - 'top element' | '/openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 - 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE - 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 - 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 2.5 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 - 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.1 | 1 | 0 + 'top element' | '/openroadm-devices' || 1.27 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 + 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 1.3 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 1.46 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 + 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 1.32 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 + 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.01 | 1 | 0 } def 'Query complete data trees across all anchors with #scenario.'() { @@ -64,10 +64,10 @@ class QueryPerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Query across anchors ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: 'the following parameters are used' scenario | cpspath || durationLimit | memoryLimit | expectedNumberOfDataNodes - 'top element' | '/openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) - 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE) - 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) - 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 7 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) + 'top element' | '/openroadm-devices' || 3.76 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) + 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 3.3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE) + 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 3.96 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) + 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 3.76 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) } def 'Query with leaf condition and #scenario.'() { @@ -83,8 +83,8 @@ class QueryPerfTest extends CpsPerfTestBase { where: 'the following parameters are used' scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes 'no descendants' | OMIT_DESCENDANTS || 0.1 | 6 | OPENROADM_DEVICES_PER_ANCHOR - 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.2 | 12 | OPENROADM_DEVICES_PER_ANCHOR * 2 - 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.16 | 12 | OPENROADM_DEVICES_PER_ANCHOR * 2 + 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.4 | 200 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE } def 'Query ancestors with #scenario.'() { @@ -99,9 +99,9 @@ class QueryPerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Query ancestors with ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: 'the following parameters are used' scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes - 'no descendants' | OMIT_DESCENDANTS || 0.1 | 3 | 1 - 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.2 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR - 'all descendants' | INCLUDE_ALL_DESCENDANTS || 2.5 | 400 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'no descendants' | OMIT_DESCENDANTS || 0.09 | 3 | 1 + 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.11 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR + 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.34 | 400 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy index 69f64773f..1d3943f36 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy @@ -78,13 +78,13 @@ class UpdatePerfTest extends CpsPerfTestBase { timeLimit, resourceMeter.getTotalTimeInSeconds(), memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: - scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit - 'Replace 0 nodes with 100' | 100 | 1 | false || 2.5 | 200 - 'Replace 100 using same data' | 100 | 1 | false || 3.0 | 200 - 'Replace 100 with new leaf values' | 100 | 1 | true || 3.0 | 200 - 'Replace 100 with 100 new nodes' | 100 | 101 | false || 6.0 | 200 - 'Replace 50 existing and 50 new' | 100 | 151 | true || 4.5 | 200 - 'Replace 100 nodes with 0' | 0 | 1 | false || 3.0 | 200 + scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit + 'Replace 0 nodes with 100' | 100 | 1 | false || 3.99 | 200 + 'Replace 100 using same data' | 100 | 1 | false || 7.46 | 200 + 'Replace 100 with new leaf values' | 100 | 1 | true || 7.87 | 200 + 'Replace 100 with 100 new nodes' | 100 | 101 | false || 13.85 | 200 + 'Replace 50 existing and 50 new' | 100 | 151 | true || 10.82 | 200 + 'Replace 100 nodes with 0' | 0 | 1 | false || 8.91 | 200 } def 'Replace list content: #scenario.'() { @@ -104,13 +104,13 @@ class UpdatePerfTest extends CpsPerfTestBase { timeLimit, resourceMeter.getTotalTimeInSeconds(), memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) where: - scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit - 'Replace list of 0 with 100' | 100 | 1 | false || 2.5 | 200 - 'Replace list of 100 using same data' | 100 | 1 | false || 3.0 | 200 - 'Replace list of 100 with new leaf values' | 100 | 1 | true || 3.0 | 200 - 'Replace list with 100 new nodes' | 100 | 101 | false || 6.0 | 200 - 'Replace list with 50 existing and 50 new' | 100 | 151 | true || 4.5 | 200 - 'Replace list of 100 nodes with 1' | 1 | 1 | false || 3.0 | 200 + scenario | totalNodes | startId | changeLeaves || timeLimit | memoryLimit + 'Replace list of 0 with 100' | 100 | 1 | false || 4.01 | 200 + 'Replace list of 100 using same data' | 100 | 1 | false || 5.53 | 200 + 'Replace list of 100 with new leaf values' | 100 | 1 | true || 6.96 | 200 + 'Replace list with 100 new nodes' | 100 | 101 | false || 12.82 | 200 + 'Replace list with 50 existing and 50 new' | 100 | 151 | true || 10.42 | 200 + 'Replace list of 100 nodes with 1' | 1 | 1 | false || 9.26 | 200 } def 'Update leaves for 100 data nodes.'() { @@ -127,7 +127,7 @@ class UpdatePerfTest extends CpsPerfTestBase { assert 100 == countDataNodes('/openroadm-devices/openroadm-device[@status="fail"]') and: 'update completes within expected time and memory used is within limit' recordAndAssertResourceUsage('Update leaves for 100 data nodes', - 0.4, resourceMeter.getTotalTimeInSeconds(), + 0.35, resourceMeter.getTotalTimeInSeconds(), 120, resourceMeter.getTotalMemoryUsageInMB()) } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy index 96f85ffd7..019561174 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy @@ -44,10 +44,10 @@ class WritePerfTest extends CpsPerfTestBase { cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR) where: totalNodes || expectedDuration | memoryLimit - 50 || 2 | 100 - 100 || 4 | 200 - 200 || 7 | 400 - 400 || 14 | 500 + 50 || 1.98 | 100 + 100 || 3.84 | 200 + 200 || 8.6 | 400 + 400 || 16.37 | 500 } def 'Writing bookstore data has exponential time.'() { @@ -69,10 +69,10 @@ class WritePerfTest extends CpsPerfTestBase { cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR) where: totalBooks || expectedDuration | memoryLimit - 800 || 0.5 | 50 - 1600 || 1.5 | 100 - 3200 || 6.0 | 150 - 6400 || 18.0 | 200 + 800 || 0.38 | 50 + 1600 || 0.95 | 100 + 3200 || 2.71 | 150 + 6400 || 8.08 | 200 } def 'Writing openroadm list data using saveListElements.'() { @@ -97,10 +97,10 @@ class WritePerfTest extends CpsPerfTestBase { cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR) where: totalNodes || expectedDuration | memoryLimit - 50 || 2 | 100 - 100 || 4 | 200 - 200 || 7 | 400 - 400 || 14 | 500 + 50 || 1.8 | 100 + 100 || 3.93 | 200 + 200 || 7.77 | 400 + 400 || 16.59 | 500 } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy index 579394be8..fc2f8cf00 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmDataSubscriptionsPerfTest.groovy @@ -52,7 +52,7 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase { matches.size() == numberOfFiltersPerCmHandle * numberOfCmHandlesPerCmDataSubscription and: 'query all subscribers within 1 second' def durationInSeconds = resourceMeter.getTotalTimeInSeconds() - recordAndAssertResourceUsage("Query all subscribers", 1.2, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Query all subscribers", 2.56, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB()) } def 'Worst case subscription update (200x10 matching entries).'() { @@ -95,7 +95,7 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase { def resultAfter = objectUnderTest.queryDataNodes(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, cpsPath, INCLUDE_ALL_DESCENDANTS) assert resultAfter.collect {it.leaves.subscribers.size()}.sum() == totalNumberOfEntries * (1 + numberOfCmDataSubscribers) and: 'update matching subscription within 15 seconds' - recordAndAssertResourceUsage("Update matching subscription", 15, durationInSeconds, 1000, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Update matching subscription", 13.86, durationInSeconds, 1000, resourceMeter.getTotalMemoryUsageInMB()) } def 'Worst case new subscription (200x10 new entries).'() { @@ -109,7 +109,7 @@ class CmDataSubscriptionsPerfTest extends NcmpPerfTestBase { resourceMeter.stop() def durationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'insert new subscription with 1 second' - recordAndAssertResourceUsage("Insert new subscription", 2, durationInSeconds, 100, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("Insert new subscription", 1.28, durationInSeconds, 100, resourceMeter.getTotalMemoryUsageInMB()) } def querySubscriptionsByIteration(Collection<DataNode> allSubscriptionsAsDataNodes, targetSubscriptionSequenceNumber) { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy index d95ac7319..d518234f5 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy @@ -20,21 +20,15 @@ package org.onap.cps.integration.performance.ncmp -import org.apache.commons.lang3.StringUtils -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncService -import org.onap.cps.ncmp.api.impl.utils.YangDataConverter -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.DataNode -import org.springframework.beans.factory.annotation.Autowired -import java.util.stream.Collectors import org.onap.cps.api.CpsQueryService import org.onap.cps.integration.ResourceMeter import org.onap.cps.integration.performance.base.NcmpPerfTestBase -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS +import java.util.stream.Collectors + import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS class CmHandleQueryPerfTest extends NcmpPerfTestBase { @@ -74,7 +68,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { resourceMeter.stop() def durationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'the required operations are performed within required time' - recordAndAssertResourceUsage("CpsPath Registry attributes Query", 2, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB()) + recordAndAssertResourceUsage("CpsPath Registry attributes Query", 3.96, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB()) and: 'all nodes are returned' result.size() == TOTAL_CM_HANDLES and: 'the tree contains all the expected descendants too' @@ -98,7 +92,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { expectedAverageResponseTime, averageResponseTime, 15, resourceMeter.totalMemoryUsageInMB) where: - expectedAverageResponseTime = 1 * MILLISECONDS + expectedAverageResponseTime = 8 * MILLISECONDS } def 'CM-handle is looked up by alternate-id.'() { @@ -118,7 +112,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { expectedAverageResponseTime, averageResponseTime, 15, resourceMeter.totalMemoryUsageInMB) where: - expectedAverageResponseTime = 10 * MILLISECONDS + expectedAverageResponseTime = 20 * MILLISECONDS } def 'A batch of CM-handles is looked up by alternate-id.'() { @@ -136,7 +130,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { expectedAverageResponseTime, averageResponseTime, 15, resourceMeter.totalMemoryUsageInMB) where: - expectedAverageResponseTime = 1 * MILLISECONDS + expectedAverageResponseTime = 4 * MILLISECONDS } def 'Find any CM-handle given moduleSetTag when there are 20K READY handles with same moduleSetTag.'() { @@ -157,7 +151,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { expectedAverageResponseTime, averageResponseTime, 500, resourceMeter.totalMemoryUsageInMB) where: - expectedAverageResponseTime = 100 * MILLISECONDS + expectedAverageResponseTime = 438 * MILLISECONDS } } diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml index 407210fa3..6fd3bcae4 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -169,7 +169,6 @@ ncmp: maximumConnectionsPerRoute: 50 maximumConnectionsTotal: 100 idleConnectionEvictionThresholdInSeconds: 5 - maximumInMemorySizeInMegabytes: 16 auth: username: dmi password: dmi diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json deleted file mode 100644 index 5d713914d..000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "moduleName": "M1", - "revision": "2024-01-01", - "yangSource": "module M1 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M1\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-01 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - }, - { - "moduleName": "M2", - "revision": "2024-01-02", - "yangSource": "module M2 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M2\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-02 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - } -]
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json deleted file mode 100644 index 9f20564f0..000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schemas": [ - { - "moduleName": "M1", - "revision": "2024-01-01" - }, - { - "moduleName": "M2", - "revision": "2024-01-02" - } - ] -}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json deleted file mode 100644 index ef9b85f92..000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "moduleName": "M1", - "revision": "2024-01-01", - "yangSource": "module M1 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M1\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-01 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - }, - { - "moduleName": "M3", - "revision": "2024-01-03", - "yangSource": "module M3 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M3\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-03 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - } -]
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json deleted file mode 100644 index 513c749a2..000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schemas": [ - { - "moduleName": "M1", - "revision": "2024-01-01" - }, - { - "moduleName": "M3", - "revision": "2024-01-03" - } - ] -}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/moduleReferencesTemplate.json b/integration-test/src/test/resources/data/mock-dmi-responses/moduleReferencesTemplate.json new file mode 100644 index 000000000..2551ad4e3 --- /dev/null +++ b/integration-test/src/test/resources/data/mock-dmi-responses/moduleReferencesTemplate.json @@ -0,0 +1,4 @@ +{ + "moduleName": "<MODULE_NAME>", + "revision": "2024-01-01" +} diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/moduleResourcesTemplate.json b/integration-test/src/test/resources/data/mock-dmi-responses/moduleResourcesTemplate.json new file mode 100644 index 000000000..1e189f188 --- /dev/null +++ b/integration-test/src/test/resources/data/mock-dmi-responses/moduleResourcesTemplate.json @@ -0,0 +1,5 @@ +{ + "moduleName": "<MODULE_NAME>", + "revision": "2024-01-01", + "yangSource": "module <MODULE_NAME> {\n\n namespace \"urn:ietf:params:xml:ns:yang:<MODULE_NAME>\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-01 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" +} diff --git a/jacoco-report/pom.xml b/jacoco-report/pom.xml index 9919e8ccc..447614f39 100644 --- a/jacoco-report/pom.xml +++ b/jacoco-report/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -32,7 +32,7 @@ <groupId>org.onap.cps</groupId>
<artifactId>cps-aggregator</artifactId>
- <version>3.4.8-SNAPSHOT</version>
+ <version>3.5.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cps</name>
diff --git a/releases/3.4.8-container.yaml b/releases/3.4.8-container.yaml new file mode 100644 index 000000000..c5cccb463 --- /dev/null +++ b/releases/3.4.8-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.4.8 +project: cps +log_dir: cps-maven-docker-stage-master/940/ +ref: ad46c250eebd2eec9a99991371f825c778336182 +containers: + - name: 'cps-and-ncmp' + version: '3.4.8-20240501T114419Z' diff --git a/releases/3.4.8.yaml b/releases/3.4.8.yaml new file mode 100644 index 000000000..a711ff7cf --- /dev/null +++ b/releases/3.4.8.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/948/ +project: cps +version: 3.4.8 diff --git a/releases/3.4.9-container.yaml b/releases/3.4.9-container.yaml new file mode 100644 index 000000000..ab557665e --- /dev/null +++ b/releases/3.4.9-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.4.9 +project: cps +log_dir: cps-maven-docker-stage-master/941/ +ref: 85de6a351eadb3b3a48aabc776c892926333db60 +containers: + - name: 'cps-and-ncmp' + version: '3.4.9-20240513T163732Z'
\ No newline at end of file diff --git a/releases/3.4.9.yaml b/releases/3.4.9.yaml new file mode 100644 index 000000000..81217f4bd --- /dev/null +++ b/releases/3.4.9.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/949/ +project: cps +version: 3.4.9
\ No newline at end of file diff --git a/spotbugs/pom.xml b/spotbugs/pom.xml index 02c60793c..bab955190 100644 --- a/spotbugs/pom.xml +++ b/spotbugs/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>spotbugs</artifactId> - <version>3.4.8-SNAPSHOT</version> + <version>3.5.0-SNAPSHOT</version> <properties> <nexusproxy>https://nexus.onap.org</nexusproxy> diff --git a/version.properties b/version.properties index 767a534df..5fdc4e357 100644 --- a/version.properties +++ b/version.properties @@ -21,8 +21,8 @@ # because they are used in Jenkins, whose plug-in doesn't support this major=3 -minor=4 -patch=8 +minor=5 +patch=0 base_version=${major}.${minor}.${patch} |