From d72657b8f23c2e99268c3fd42242112fea31f26b Mon Sep 17 00:00:00 2001 From: mpriyank Date: Fri, 18 Apr 2025 12:30:37 +0100 Subject: [PATCH] [DMI] Enabling datasync flag when the cmhandle is READY #2 - enabling the datasync flag once the cmhandle is successfully registered with CPS-NCMP and in READY state - data sync flag can only be enabled when it has fetched all the modules from the cm handles over the network - testware added for the same - removed the use of auth username and password when calling to register cm handle with NCMP Issue-ID: CPS-2757 Change-Id: Ib97483e51f1c314d663c1954c96a3ba96b699f2e Signed-off-by: mpriyank --- .../cps/ncmp/dmi/cmstack/lcm/LcmEventConsumer.java | 63 ++++++++++++++++ .../onap/cps/ncmp/dmi/config/DmiConfiguration.java | 4 +- .../org/onap/cps/ncmp/dmi/service/DmiService.java | 11 ++- .../onap/cps/ncmp/dmi/service/DmiServiceImpl.java | 8 +- .../ncmp/dmi/service/client/NcmpRestClient.java | 37 +++++++++- dmi-service/src/main/resources/application.yml | 5 +- .../dmi/cmstack/lcm/LcmEventConsumerSpec.groovy | 85 ++++++++++++++++++++++ .../ncmp/dmi/config/DmiConfigurationSpec.groovy | 3 +- .../dmi/service/client/NcmpRestClientSpec.groovy | 26 +++++-- dmi-service/src/test/resources/application.yml | 1 + dmi-service/src/test/resources/sampleLcmEvent.json | 18 +++++ 11 files changed, 246 insertions(+), 15 deletions(-) create mode 100644 dmi-service/src/main/java/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumer.java create mode 100644 dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumerSpec.groovy create mode 100644 dmi-service/src/test/resources/sampleLcmEvent.json diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumer.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumer.java new file mode 100644 index 00000000..5cee8b4c --- /dev/null +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumer.java @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (c) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * 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.dmi.cmstack.lcm; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.dmi.service.DmiService; +import org.onap.cps.ncmp.events.lcm.v1.LcmEvent; +import org.onap.cps.ncmp.events.lcm.v1.Values; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + + +@Slf4j +@Component +@RequiredArgsConstructor +public class LcmEventConsumer { + + private final DmiService dmiService; + + /** + * Consume the LCM event to enable the data synchronization for the ready cm handle. + * + * @param lcmEvent Lifecycle event of a cm handle id + */ + @KafkaListener(topics = "${app.ncmp.lcm.topic}", + containerFactory = "legacyEventConcurrentKafkaListenerContainerFactory", + properties = {"spring.json.value.default.type=org.onap.cps.ncmp.events.lcm.v1.LcmEvent"}) + public void consumeLcmEvent(final LcmEvent lcmEvent) { + + final Values newValues = lcmEvent.getEvent().getNewValues(); + + if (newValues == null) { + return; + } + + if (newValues.getCmHandleState() == Values.CmHandleState.READY) { + final String cmHandleId = lcmEvent.getEvent().getCmHandleId(); + log.info("Enabling data sync flag for cmHandleId : {}", cmHandleId); + dmiService.enableNcmpDataSyncForCmHandles(List.of(cmHandleId)); + } + } + +} \ No newline at end of file diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java index 83ef6f89..dee3c751 100644 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * 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,8 @@ public class DmiConfiguration { private String baseUrl; @Value("${cps-core.dmiRegistrationUrl}") private String dmiRegistrationUrl; + @Value("${cps-core.dataSyncEnabledUrl}") + private String dataSyncEnabledUrl; @Value("${cps-core.auth.username}") private String authUsername; @Value("${cps-core.auth.password}") diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java index f0826a81..b0dd80e3 100644 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,6 +56,15 @@ public interface DmiService { */ void registerCmHandles(List cmHandles); + /** + * This method is used to enable data synchronization for + * the given {@code CmHandles}. + * + * @param cmHandles list of cm-handles + */ + void enableNcmpDataSyncForCmHandles(List cmHandles); + + /** * Get module resources for the given cm handle and modules. * diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java index 6acbe09b..6bc6cee4 100644 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2021-2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -137,6 +137,12 @@ public class DmiServiceImpl implements DmiService { } } + @Override + public void enableNcmpDataSyncForCmHandles(final List cmHandles) { + log.info("Enabling NCMP dataSync flag for : {}", cmHandles); + cmHandles.forEach(cmHandleId -> ncmpRestClient.enableNcmpDataSync(cmHandleId)); + } + private ModuleSetSchemasInner toModuleSetSchemas(final ModuleSchema moduleSchema) { final ModuleSetSchemasInner moduleSetSchemas = new ModuleSetSchemasInner(); moduleSetSchemas.setModuleName(moduleSchema.getIdentifier()); diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java index 18445e5c..88f78008 100644 --- a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java +++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2025 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.dmi.service.client; +import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.dmi.config.DmiConfiguration.CpsProperties; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -30,6 +31,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +@Slf4j @Component public class NcmpRestClient { @@ -43,16 +45,34 @@ public class NcmpRestClient { /** * Register a cmHandle with NCMP using a HTTP call. + * * @param jsonData json data * @return the response entity */ + public ResponseEntity registerCmHandlesWithNcmp(final String jsonData) { final String ncmpRegistrationUrl = buildNcmpRegistrationUrl(); + return performNcmpRequest(ncmpRegistrationUrl, HttpMethod.POST, jsonData); + } + + /** + * Enable NCMP data sync flag to enable data sync for a cmHandle with NCMP using a HTTP call. + * + * @param cmHandleId cm handle identifier + * @return the response entity + */ + public ResponseEntity enableNcmpDataSync(final String cmHandleId) { + final String dataSyncEnabledUrl = buildDataSyncEnabledUrl(cmHandleId); + return performNcmpRequest(dataSyncEnabledUrl, HttpMethod.PUT, null); + } + + private ResponseEntity performNcmpRequest(final String ncmpUrl, final HttpMethod httpMethod, + final String requestBody) { final HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBasicAuth(cpsProperties.getAuthUsername(), cpsProperties.getAuthPassword()); httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - final HttpEntity httpEntity = new HttpEntity<>(jsonData, httpHeaders); - return restTemplate.exchange(ncmpRegistrationUrl, HttpMethod.POST, httpEntity, String.class); + final HttpEntity httpEntity = + (requestBody != null) ? new HttpEntity<>(requestBody, httpHeaders) : new HttpEntity<>(httpHeaders); + return restTemplate.exchange(ncmpUrl, httpMethod, httpEntity, String.class); } private String buildNcmpRegistrationUrl() { @@ -61,4 +81,13 @@ public class NcmpRestClient { .path(cpsProperties.getDmiRegistrationUrl()) .toUriString(); } + + private String buildDataSyncEnabledUrl(final String cmHandleId) { + final String dataSyncEnabledUrl = UriComponentsBuilder.fromUriString(cpsProperties.getBaseUrl()) + .path(cpsProperties.getDataSyncEnabledUrl()) + .buildAndExpand(cmHandleId) + .toUriString(); + log.debug("dataSyncEnabledUrl : {}", dataSyncEnabledUrl); + return dataSyncEnabledUrl; + } } \ No newline at end of file diff --git a/dmi-service/src/main/resources/application.yml b/dmi-service/src/main/resources/application.yml index 6a91cc77..d6399329 100644 --- a/dmi-service/src/main/resources/application.yml +++ b/dmi-service/src/main/resources/application.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021-2024 Nordix Foundation +# Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. # Modifications Copyright (C) 2021 Bell Canada. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); @@ -70,6 +70,8 @@ app: ncmp: async: topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m} + lcm: + topic: ${LCM_EVENTS_TOPIC:ncmp-events} dmi: avc: cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription} @@ -106,6 +108,7 @@ management: cps-core: baseUrl: http://${CPS_CORE_HOST}:${CPS_CORE_PORT} dmiRegistrationUrl : /ncmpInventory/v1/ch + dataSyncEnabledUrl: /ncmp/v1/ch/{cmHandleId}/data-sync?dataSyncEnabled=true auth: username: ${CPS_CORE_USERNAME} password: ${CPS_CORE_PASSWORD} diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumerSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumerSpec.groovy new file mode 100644 index 00000000..6904eb3d --- /dev/null +++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumerSpec.groovy @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (c) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * 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.dmi.cmstack.lcm + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.dmi.TestUtils +import org.onap.cps.ncmp.dmi.service.DmiService +import org.onap.cps.ncmp.events.lcm.v1.LcmEvent +import org.spockframework.spring.SpringBean +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers +import spock.lang.Specification + +import static org.onap.cps.ncmp.events.lcm.v1.Values.CmHandleState.LOCKED + +@SpringBootTest(classes = [ObjectMapper]) +@Testcontainers +@DirtiesContext +class LcmEventConsumerSpec extends Specification { + + def objectMapper = new ObjectMapper() + def dmiService = Mock(DmiService) + + def jsonData + + @SpringBean + LcmEventConsumer objectUnderTest = new LcmEventConsumer(dmiService) + + void setup() { + jsonData = TestUtils.getResourceFileContent('sampleLcmEvent.json') + } + + def 'Consume LCM message when the cm handle is READY( module sync done )'() { + given: 'LCM event is created and sent ' + def lcmEvent = objectMapper.readValue(jsonData, LcmEvent.class) + when: 'event is consumed' + objectUnderTest.consumeLcmEvent(lcmEvent) + then: 'cm handle(s) are enabled for data sync' + 1 * dmiService.enableNcmpDataSyncForCmHandles(['ch-1']) + + } + + def 'Consume LCM message when the cm handle is in LOCKED state'() { + given: 'LCM event is created and sent ' + def lcmEvent = objectMapper.readValue(jsonData, LcmEvent.class) + and: 'cm handle is in LOCKED state' + lcmEvent.event.newValues.cmHandleState = LOCKED + when: 'event is consumed' + objectUnderTest.consumeLcmEvent(lcmEvent) + then: 'data sync flag is not enabled as state is not READY' + 0 * dmiService.enableNcmpDataSyncForCmHandles(['ch-1']) + + } + + def 'Consume LCM message with no target state'() { + given: 'LCM event is created and sent ' + def lcmEvent = objectMapper.readValue(jsonData, LcmEvent.class) + and: 'the target state ( newValues ) is set to null' + lcmEvent.event.newValues = null + when: 'event is consumed' + objectUnderTest.consumeLcmEvent(lcmEvent) + then: 'data sync flag is not enabled as state is not READY' + 0 * dmiService.enableNcmpDataSyncForCmHandles(_) + + } +} diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy index 9d80b71f..9637371a 100644 --- a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy +++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ class DmiConfigurationSpec extends Specification { cpsProperties.dmiRegistrationUrl == 'some registration url' cpsProperties.authUsername == 'some cps core user' cpsProperties.authPassword == 'some cps core password' + cpsProperties.dataSyncEnabledUrl == 'some data sync url/{some-cm-handle}?dataSyncFlag=true' } def 'SDNC properties configuration.'() { diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy index 4d7e27e2..1a5304f1 100644 --- a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy +++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,16 +42,30 @@ class NcmpRestClientSpec extends Specification { and: 'configuration data' mockCpsProperties.baseUrl >> 'http://some-uri' mockCpsProperties.dmiRegistrationUrl >> 'some-url' - mockCpsProperties.authUsername >> 'some-username' - mockCpsProperties.authPassword >> 'some-password' and: 'the rest template returns a valid response entity' - def mockResponseEntity = Mock(ResponseEntity) + def mockResponseEntityFromRestTemplate = Mock(ResponseEntity) when: 'registering a cm handle' def result = objectUnderTest.registerCmHandlesWithNcmp(someRequestData) then: 'the rest template is called with the correct uri and original request data in the body' 1 * mockRestTemplate.exchange({ it.toString() == 'http://some-uri/some-url' }, - HttpMethod.POST, { it.body.contains(someRequestData) }, String.class) >> mockResponseEntity + HttpMethod.POST, { it.body.contains(someRequestData) }, String.class) >> mockResponseEntityFromRestTemplate and: 'the output of the method is equal to the output from the rest template service' - result == mockResponseEntity + result == mockResponseEntityFromRestTemplate + } + + def 'Enable data sync for a cm handle identifier.'() { + given: 'some cm handle id' + def someCmHandleId = 'some-cm-handle-id' + and: 'configuring the url to enable data sync' + mockCpsProperties.baseUrl >> 'http://my-base-uri' + mockCpsProperties.dataSyncEnabledUrl >> 'datasync-url/{test-parameter}/data-sync?dataSyncEnabled=true' + and: 'the rest template configured to return a response entity' + def mockResponseEntityFromRestTemplate = Mock(ResponseEntity) + mockRestTemplate.exchange({ it.toString() == 'http://my-base-uri/datasync-url/some-cm-handle-id/data-sync?dataSyncEnabled=true' }, + HttpMethod.PUT, _, String.class) >> mockResponseEntityFromRestTemplate + when: 'enabling the NCMP data sync flag' + def result = objectUnderTest.enableNcmpDataSync(someCmHandleId) + then: 'the output of the method is equal to the output from the rest template service' + assert result == mockResponseEntityFromRestTemplate } } \ No newline at end of file diff --git a/dmi-service/src/test/resources/application.yml b/dmi-service/src/test/resources/application.yml index 6cd5169f..2a2cf96b 100644 --- a/dmi-service/src/test/resources/application.yml +++ b/dmi-service/src/test/resources/application.yml @@ -36,6 +36,7 @@ sdnc: cps-core: baseUrl: some url for cps dmiRegistrationUrl: some registration url + dataSyncEnabledUrl: some data sync url/{some-cm-handle}?dataSyncFlag=true auth: username: some cps core user password: some cps core password diff --git a/dmi-service/src/test/resources/sampleLcmEvent.json b/dmi-service/src/test/resources/sampleLcmEvent.json new file mode 100644 index 00000000..d4b0817b --- /dev/null +++ b/dmi-service/src/test/resources/sampleLcmEvent.json @@ -0,0 +1,18 @@ +{ + "eventId": "test-uuid", + "eventCorrelationId": "pynts-o-du-o1", + "eventTime": "2022-12-31T20:30:40.000+0000", + "eventSource": "org.onap.ncmp", + "eventType": "org.onap.ncmp.cmhandle.lcm.event", + "eventSchema": "org.onap.ncmp.cmhandle.lcm.event", + "eventSchemaVersion": "v1", + "event": { + "cmHandleId": "ch-1", + "oldValues": { + "cmHandleState": "ADVISED" + }, + "newValues": { + "cmHandleState": "READY" + } + } +} \ No newline at end of file -- 2.16.6