[DMI] Enabling datasync flag when the cmhandle is READY #2 37/140837/11
authormpriyank <priyank.maheshwari@est.tech>
Fri, 18 Apr 2025 11:30:37 +0000 (12:30 +0100)
committerPriyank Maheshwari <priyank.maheshwari@est.tech>
Tue, 10 Jun 2025 08:39:55 +0000 (08:39 +0000)
- 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 <priyank.maheshwari@est.tech>
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumer.java [new file with mode: 0644]
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java
dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java
dmi-service/src/main/resources/application.yml
dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/cmstack/lcm/LcmEventConsumerSpec.groovy [new file with mode: 0644]
dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy
dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy
dmi-service/src/test/resources/application.yml
dmi-service/src/test/resources/sampleLcmEvent.json [new file with mode: 0644]

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 (file)
index 0000000..5cee8b4
--- /dev/null
@@ -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
index 83ef6f8..dee3c75 100644 (file)
@@ -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}")
index f0826a8..b0dd80e 100644 (file)
@@ -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<String> cmHandles);
 
+    /**
+     * This method is used to enable data synchronization for
+     * the given {@code CmHandles}.
+     *
+     * @param cmHandles list of cm-handles
+     */
+    void enableNcmpDataSyncForCmHandles(List<String> cmHandles);
+
+
     /**
      * Get module resources for the given cm handle and modules.
      *
index 6acbe09..6bc6cee 100644 (file)
@@ -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<String> 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());
index 18445e5..88f7800 100644 (file)
@@ -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<String> 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<String> enableNcmpDataSync(final String cmHandleId) {
+        final String dataSyncEnabledUrl = buildDataSyncEnabledUrl(cmHandleId);
+        return performNcmpRequest(dataSyncEnabledUrl, HttpMethod.PUT, null);
+    }
+
+    private ResponseEntity<String> 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<String> httpEntity = new HttpEntity<>(jsonData, httpHeaders);
-        return restTemplate.exchange(ncmpRegistrationUrl, HttpMethod.POST, httpEntity, String.class);
+        final HttpEntity<String> 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
index 6a91cc7..d639932 100644 (file)
@@ -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 (file)
index 0000000..6904eb3
--- /dev/null
@@ -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(_)
+
+    }
+}
index 9d80b71..9637371 100644 (file)
@@ -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.'() {
index 4d7e27e..1a5304f 100644 (file)
@@ -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
index 6cd5169..2a2cf96 100644 (file)
@@ -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 (file)
index 0000000..d4b0817
--- /dev/null
@@ -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