diff options
82 files changed, 1285 insertions, 419 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml index b1c4379ee6..9ecfa2d329 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 e6e2f9a2a6..6804c7de65 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 7873cc459c..810068081e 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,8 +144,6 @@ 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/** 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 ec16ceefe8..c18a3ed10b 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 366d4c72f3..4548eb21c3 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 b7ec70b339..effaa06f62 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 680d0bbadc..ec0c96bc24 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 18f83ccf86..a3eaf63fa4 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 e12ab3bba8..3804e5bd21 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 ae755a3145..7b277cdb3a 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 b6c461c6de..cb655320f5 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 c2c789e5b1..441ce43f45 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 453da8fea9..e333344798 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 453abcabb1..5467eeffca 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 1d03be107d..7b850a7fff 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 1510be909b..fc41da3aee 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> 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 index 49396bca3a..50a5df537d 100644 --- 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 @@ -25,9 +25,9 @@ import org.onap.cps.ncmp.api.impl.events.cmsubscription.producer.CmNotificationS 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.Service; +import org.springframework.stereotype.Component; -@Service +@Component @RequiredArgsConstructor public class CmNotificationSubscriptionEventsHandler { private final CmNotificationSubscriptionNcmpOutEventProducer cmNotificationSubscriptionNcmpOutEventProducer; @@ -37,7 +37,7 @@ public class CmNotificationSubscriptionEventsHandler { * 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 subscriptionId Cm Subscription id * @param eventType Type of event * @param cmNotificationSubscriptionNcmpOutEvent Cm Notification Subscription Event for the * client @@ -45,9 +45,9 @@ public class CmNotificationSubscriptionEventsHandler { * or published now */ public void publishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, final String eventType, - final CmNotificationSubscriptionNcmpOutEvent - cmNotificationSubscriptionNcmpOutEvent, - final boolean isScheduledEvent) { + final CmNotificationSubscriptionNcmpOutEvent + cmNotificationSubscriptionNcmpOutEvent, + final boolean isScheduledEvent) { cmNotificationSubscriptionNcmpOutEventProducer.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent, isScheduledEvent); } @@ -55,7 +55,7 @@ public class CmNotificationSubscriptionEventsHandler { /** * 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 subscriptionId Cm Subscription id * @param dmiPluginName Dmi Plugin Name * @param eventType Type of event * @param cmNotificationSubscriptionDmiInEvent Cm Notification Subscription event for Dmi 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 0000000000..73f9563ecf --- /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 7058331b84..f7dd51e637 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 @@ -27,7 +27,6 @@ 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; @@ -41,7 +40,7 @@ public class CmNotificationSubscriptionNcmpOutEventPublishingTask implements Run private final String eventType; private final EventsPublisher<CloudEvent> eventsPublisher; private final JsonObjectMapper jsonObjectMapper; - private final CmNotificationSubscriptionNcmpOutEventMapper cmNotificationSubscriptionNcmpOutEventMapper; + private final CmNotificationSubscriptionMappersHandler cmNotificationSubscriptionMappersHandler; private final DmiCmNotificationSubscriptionCacheHandler dmiCmNotificationSubscriptionCacheHandler; /** @@ -53,7 +52,7 @@ public class CmNotificationSubscriptionNcmpOutEventPublishingTask implements Run final Map<String, DmiCmNotificationSubscriptionDetails> dmiCmNotificationSubscriptionDetailsMap = dmiCmNotificationSubscriptionCacheHandler.get(subscriptionId); final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent = - cmNotificationSubscriptionNcmpOutEventMapper.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEvent(subscriptionId, dmiCmNotificationSubscriptionDetailsMap); eventsPublisher.publishCloudEvent(topicName, subscriptionId, buildAndGetCmNotificationNcmpOutEventAsCloudEvent(jsonObjectMapper, subscriptionId, eventType, @@ -61,5 +60,4 @@ public class CmNotificationSubscriptionNcmpOutEventPublishingTask implements Run 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 34ffb5e19d..b5370bf1ed 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 @@ -122,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); + } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java index 66ac5d0c28..fb89aae3f8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java @@ -23,12 +23,17 @@ 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; @@ -38,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. @@ -66,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, @@ -77,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/producer/CmNotificationSubscriptionNcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java index 6ebbd87dcb..ac5de07f0a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java @@ -33,9 +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.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.api.impl.events.cmsubscription.mapper.CmNotificationSubscriptionNcmpOutEventMapper; 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; @@ -56,7 +56,7 @@ public class CmNotificationSubscriptionNcmpOutEventProducer { private final EventsPublisher<CloudEvent> eventsPublisher; private final JsonObjectMapper jsonObjectMapper; - 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<>(); @@ -96,7 +96,7 @@ public class CmNotificationSubscriptionNcmpOutEventProducer { cmNotificationSubscriptionNcmpOutEventPublishingTask = new CmNotificationSubscriptionNcmpOutEventPublishingTask(cmNotificationSubscriptionNcmpOutEventTopic, subscriptionId, eventType, eventsPublisher, jsonObjectMapper, - cmNotificationSubscriptionNcmpOutEventMapper, dmiCmNotificationSubscriptionCacheHandler); + cmNotificationSubscriptionMappersHandler, dmiCmNotificationSubscriptionCacheHandler); return scheduledExecutorService.schedule(cmNotificationSubscriptionNcmpOutEventPublishingTask, cmNotificationSubscriptionDmiOutEventTimeoutInMs, TimeUnit.MILLISECONDS); } 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 4cc74850be..7872ba0a34 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.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 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); + cmNotificationSubscriptionMappersHandler.toCmNotificationSubscriptionNcmpOutEventForRejectedRequest( + subscriptionId, new ArrayList<>(subscriptionTargetFilters)); cmNotificationSubscriptionEventsHandler.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, - "subscriptionCreateResponse", cmNotificationSubscriptionNcmpOutEvent, false); + "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 0000000000..2e6cd3308c --- /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 e230b3fcb3..184b12570e 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 3ae3dd9116..bf54fe5d96 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/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 c761f4db5d..488879db7a 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 @@ -49,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() { @@ -102,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/CmNotificationSubscriptionMappersHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionMappersHandlerSpec.groovy new file mode 100644 index 0000000000..bdc54bd1ee --- /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 bbfd1153ed..9c84c51b2d 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 @@ -80,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 c726b0d653..77bbe7ebc4 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,7 +3,6 @@ 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.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 @@ -15,11 +14,11 @@ class CmNotificationSubscriptionNcmpOutEventProducerSpec extends Specification { def mockEventsPublisher = Mock(EventsPublisher) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def mockCmNotificationSubscriptionNcmpOutEventMapper = Mock(CmNotificationSubscriptionNcmpOutEventMapper) + def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler) def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler) def objectUnderTest = new CmNotificationSubscriptionNcmpOutEventProducer(mockEventsPublisher, jsonObjectMapper, - mockCmNotificationSubscriptionNcmpOutEventMapper, mockDmiCmNotificationSubscriptionCacheHandler) + mockCmNotificationSubscriptionMappersHandler, mockDmiCmNotificationSubscriptionCacheHandler) def 'Create and #scenario Cm Notification Subscription NCMP out event'() { given: 'a cm subscription response for the client' 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 2ac9c416d1..98b4ee2671 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.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 mockCmNotificationSubscriptionDelta = Mock(CmNotificationSubscriptionDelta); + def mockCmNotificationSubscriptionMappersHandler = Mock(CmNotificationSubscriptionMappersHandler); def mockCmNotificationSubscriptionEventsHandler = Mock(CmNotificationSubscriptionEventsHandler); def mockDmiCmNotificationSubscriptionCacheHandler = Mock(DmiCmNotificationSubscriptionCacheHandler); - def objectUnderTest = new CmNotificationSubscriptionHandlerServiceImpl(mockCmNotificationSubscriptionPersistenceService, mockCmNotificationSubscriptionNcmpOutEventMapper, mockCmNotificationSubscriptionEventsHandler, 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 * mockCmNotificationSubscriptionEventsHandler.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 cbc985f783..9907e9ab21 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/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json b/cps-ncmp-service/src/test/resources/cmSubscription/cmNotificationSubscriptionNcmpInEvent.json index 09796e2f4f..6b665495c0 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 ca466d704b..594aa1f0f5 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 93166ec9ce..3176d9a471 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 90422d7370..ccfb77fb69 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 f8d709647c..af0efe2e21 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 583bad0ab9..221e1a9bf2 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 adca617636..4224968778 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 b3f42279df..f556f40647 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 0000000000..1097834880 --- /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 0000000000..c19abc1764 --- /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 f8d709647c..c2310707ab 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 b2b2d7d44c..fcbfd0561a 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 140dfaac96..57f2f8ea7c 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 0000000000..11842645c2 --- /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 bb7d3e56f8..e62c01d6ca 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 288159cf66..c0b4eadb1e 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 d8576459ba..0719272523 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 8a6c13ebec..4f5180d510 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 da0b0b3f99..9f6a1b2b66 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 b401aa52e3..82a890d79c 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 | | | | +--------------------------------------+--------------------------------------------------------+ @@ -42,6 +97,7 @@ Bug Fixes - `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 d4ee0cc685..f228e01194 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 51b02387ed..44fc258355 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 0000000000..6676cb74c2 --- /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 28c4280468..4ffe586a99 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,35 +20,45 @@ package org.onap.cps.integration.functional -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 +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() { @@ -56,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') @@ -71,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') @@ -93,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", @@ -121,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 a6b516cd74..5c337f179b 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 @@ -41,19 +41,13 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { 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']) @@ -88,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') @@ -122,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') @@ -152,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')] @@ -179,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, () -> { @@ -192,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 5421ad3237..4d1d77e694 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,38 +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.util.concurrent.PollingConditions -import static org.springframework.test.web.client.match.MockRestRequestMatchers.anything -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus - 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)) @@ -72,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 @@ -87,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) @@ -103,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() @@ -125,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 @@ -146,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() @@ -169,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)) @@ -186,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 9129f09fb5..302c7e512c 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,3 +1,23 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + package org.onap.cps.integration.functional import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; 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 d7f8771e18..950cd65e72 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 @@ -32,20 +32,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. 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)) @@ -53,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')) + } } } @@ -71,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 a4ee23ae94..135586936a 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", 16, 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", 0.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 f76c3c5159..1b449b8da5 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.2, 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.7, 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.1, 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.7, 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', 4, 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', 3, 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', 1.9, 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', 1, 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 cb7680dd2a..060c9cb6d5 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 @@ -45,8 +45,8 @@ class GetPerfTest extends CpsPerfTestBase { where: 'the following parameters are used' scenario | fetchDescendantsOption || durationLimit | memoryLimit | expectedNumberOfDataNodes 'no descendants' | OMIT_DESCENDANTS || 0.01 | 1 | 1 - 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.05 | 5 | 1 + OPENROADM_DEVICES_PER_ANCHOR - 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.2 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.06 | 5 | 1 + OPENROADM_DEVICES_PER_ANCHOR + '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", 1.8 , 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'() { @@ -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' | '/' || 1.0 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE - 'openroadm top element' | '/openroadm-devices' || 1.0 | 250 | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE - 'openroadm whole list' | '/openroadm-devices/openroadm-device' || 1.7 | 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 7b9bf62e34..f31c6a988a 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' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 - 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE - 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 - 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 1.1 | 400 | OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1 - 'non-existing data' | '/path/to/non-existing/node[@id="1"]' || 0.009 | 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' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) - 'leaf condition' | '//openroadm-device[@ne-state="inservice"]' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE) - 'ancestors' | '//openroadm-device/ancestor::openroadm-devices' || 3 | 600 | OPENROADM_ANCHORS * (OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE + 1) - 'leaf condition + ancestors' | '//openroadm-device[@status="success"]/ancestor::openroadm-devices' || 3 | 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.1 | 12 | OPENROADM_DEVICES_PER_ANCHOR * 2 - 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.1 | 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.08 | 3 | 1 - 'direct descendants' | DIRECT_CHILDREN_ONLY || 0.1 | 8 | 1 + OPENROADM_DEVICES_PER_ANCHOR - 'all descendants' | INCLUDE_ALL_DESCENDANTS || 1.1 | 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 360fecad9c..1d3943f36b 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 || 3.2 | 200 - 'Replace 100 using same data' | 100 | 1 | false || 5.6 | 200 - 'Replace 100 with new leaf values' | 100 | 1 | true || 5.5 | 200 - 'Replace 100 with 100 new nodes' | 100 | 101 | false || 10.0 | 200 - 'Replace 50 existing and 50 new' | 100 | 151 | true || 8.0 | 200 - 'Replace 100 nodes with 0' | 0 | 1 | false || 7.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 || 3.0 | 200 - 'Replace list of 100 using same data' | 100 | 1 | false || 5.4 | 200 - 'Replace list of 100 with new leaf values' | 100 | 1 | true || 5.6 | 200 - 'Replace list with 100 new nodes' | 100 | 101 | false || 9.9 | 200 - 'Replace list with 50 existing and 50 new' | 100 | 151 | true || 8.0 | 200 - 'Replace list of 100 nodes with 1' | 1 | 1 | false || 7.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.3, 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 c3dd2af146..0195611740 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 || 1.6 | 100 - 100 || 3.3 | 200 - 200 || 6.8 | 400 - 400 || 13.0 | 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.3 | 50 - 1600 || 0.8 | 100 - 3200 || 2.6 | 150 - 6400 || 6.7 | 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 || 1.5 | 100 - 100 || 3.0 | 200 - 200 || 6.3 | 400 - 400 || 14.0 | 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 53b2194239..fc2f8cf00d 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", 11, 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 91b28f9e12..d518234f5c 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 @@ -68,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", 3.4, 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' @@ -92,7 +92,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { expectedAverageResponseTime, averageResponseTime, 15, resourceMeter.totalMemoryUsageInMB) where: - expectedAverageResponseTime = 6 * MILLISECONDS + expectedAverageResponseTime = 8 * MILLISECONDS } def 'CM-handle is looked up by alternate-id.'() { @@ -130,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.'() { @@ -151,7 +151,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase { expectedAverageResponseTime, averageResponseTime, 500, resourceMeter.totalMemoryUsageInMB) where: - expectedAverageResponseTime = 360 * MILLISECONDS + expectedAverageResponseTime = 438 * MILLISECONDS } } 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 5d713914d6..0000000000 --- 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 9f20564f04..0000000000 --- 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 ef9b85f926..0000000000 --- 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 513c749a26..0000000000 --- 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 0000000000..2551ad4e3e --- /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 0000000000..1e189f1885 --- /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 9919e8cccd..447614f396 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 0000000000..c5cccb4633 --- /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 0000000000..a711ff7cfa --- /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 0000000000..ab557665ea --- /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 0000000000..81217f4bd4 --- /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 02c60793ca..bab9551909 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 767a534df3..5fdc4e3576 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} |