Migrate cm-handles.additional-properties to cm-handles.dmi-properties 54/142754/4
authoregernug <gerard.nugent@est.tech>
Tue, 9 Dec 2025 12:26:42 +0000 (12:26 +0000)
committeregernug <gerard.nugent@est.tech>
Tue, 16 Dec 2025 09:56:30 +0000 (09:56 +0000)
- Split bulkUpdateCmHandleStatesAndProperties and updateCmHandleFields to perform separate tasks
- updateCmHandleFields updates fields in previous use cases
- bulkUpdateCmHandleStatesAndProperties only updates fields for migration purposes
- Added dmiProperties to CmHandleStateAndDmiPropertiesUpdate
- Added convertAdditionalPropertiesToJson to convert additionalProperties to dmiProperties

Issue-ID: CPS-3063

Change-Id: I7560a289150147d1cda40e75a41d52c2723316e8
Signed-off-by: egernug <gerard.nugent@est.tech>
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleMigrationDetail.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleStateUpdate.java with 91% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/DataMigration.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/DataMigrationSpec.groovy
docs/api/swagger/cps/openapi.yaml

index f10cc18..db517e0 100644 (file)
@@ -30,7 +30,7 @@ import org.onap.cps.api.model.ModuleReference;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
 import org.onap.cps.ncmp.api.inventory.models.CompositeState;
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
-import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate;
+import org.onap.cps.ncmp.impl.models.CmHandleMigrationDetail;
 
 public interface InventoryPersistence extends NcmpPersistence {
 
@@ -175,9 +175,11 @@ public interface InventoryPersistence extends NcmpPersistence {
     /**
      * Method to update a batch of cm handles status to the value in CompositeState.
      *
-     * @param cmHandleStateUpdates               the cmHandleId and state change being performed on it
+     * @param cmHandleMigrationDetails               the cmHandleId and state or dimProperties
+     *                                               change being performed on it
      */
-    void bulkUpdateCmHandleStates(List<CmHandleStateUpdate> cmHandleStateUpdates);
+    void cmHandleBulkMigrate(
+            List<CmHandleMigrationDetail> cmHandleMigrationDetails);
 
 
 }
index 213094b..0072cf8 100644 (file)
@@ -49,7 +49,7 @@ import org.onap.cps.api.parameters.FetchDescendantsOption;
 import org.onap.cps.ncmp.api.inventory.models.CompositeState;
 import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder;
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
-import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate;
+import org.onap.cps.ncmp.impl.models.CmHandleMigrationDetail;
 import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.CpsValidator;
@@ -218,35 +218,49 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     @Override
     public void updateCmHandleFields(final String fieldName, final Map<String, String> newValuePerCmHandleId) {
         if (!newValuePerCmHandleId.isEmpty()) {
-            final Map<String, Object> targetCmHandleStatePerCmHandleId = new HashMap<>();
-            final List<Map<String, String>> targetCmHandleStatesPerCmHandleIds = new ArrayList<>();
-
+            final List<Map<String, String>> targetCmHandleFieldChangesPerCmHandleIds = new ArrayList<>();
             for (final Map.Entry<String, String> entry : newValuePerCmHandleId.entrySet()) {
                 final Map<String, String> cmHandleData = new HashMap<>();
                 cmHandleData.put("id", entry.getKey());
                 cmHandleData.put(fieldName, entry.getValue());
-                targetCmHandleStatesPerCmHandleIds.add(cmHandleData);
+                targetCmHandleFieldChangesPerCmHandleIds.add(cmHandleData);
                 log.debug("Updating {} for cmHandle {} to {}", fieldName, entry.getKey(), entry.getValue());
             }
-            targetCmHandleStatePerCmHandleId.put("cm-handles", targetCmHandleStatesPerCmHandleIds);
             cpsDataService.updateNodeLeaves(
                     NCMP_DATASPACE_NAME,
                     NCMP_DMI_REGISTRY_ANCHOR,
                     NCMP_DMI_REGISTRY_PARENT,
-                    jsonObjectMapper.asJsonString(targetCmHandleStatePerCmHandleId),
+                    jsonObjectMapper.asJsonString(Map.of("cm-handles", targetCmHandleFieldChangesPerCmHandleIds)),
                     OffsetDateTime.now(),
                     ContentType.JSON);
+
         }
     }
 
     @Override
-    public void bulkUpdateCmHandleStates(final List<CmHandleStateUpdate> cmHandleStateUpdates) {
-        final Map<String, String> mappedCmHandleStateUpdates = cmHandleStateUpdates.stream()
-            .collect(Collectors.toMap(
-                    CmHandleStateUpdate::cmHandleId,
-                    CmHandleStateUpdate::state
-            ));
-        updateCmHandleFields("cm-handle-state", mappedCmHandleStateUpdates);
+    public void cmHandleBulkMigrate(
+            final List<CmHandleMigrationDetail> cmHandleMigrationDetails) {
+        if (cmHandleMigrationDetails.isEmpty()) {
+            return;
+        }
+        final List<Map<String, String>> cmHandleMigrationDetailAsMaps =
+                new ArrayList<>(cmHandleMigrationDetails.size());
+        for (final CmHandleMigrationDetail cmHandleMigrationDetail : cmHandleMigrationDetails) {
+            final Map<String, String> cmHandleMigrationDetailAsMap = new HashMap<>();
+            cmHandleMigrationDetailAsMap.put("id", cmHandleMigrationDetail.cmHandleId());
+            cmHandleMigrationDetailAsMap.put("cm-handle-state", cmHandleMigrationDetail.state());
+            if (cmHandleMigrationDetail.dmiProperties() != null) {
+                cmHandleMigrationDetailAsMap.put("dmi-properties", cmHandleMigrationDetail.dmiProperties());
+            }
+            cmHandleMigrationDetailAsMaps.add(cmHandleMigrationDetailAsMap);
+        }
+        cpsDataService.updateNodeLeaves(
+                NCMP_DATASPACE_NAME,
+                NCMP_DMI_REGISTRY_ANCHOR,
+                NCMP_DMI_REGISTRY_PARENT,
+                jsonObjectMapper.asJsonString(Map.of("cm-handles", cmHandleMigrationDetailAsMaps)),
+                OffsetDateTime.now(),
+                ContentType.JSON);
     }
 
     private static String getXPathForCmHandleById(final String cmHandleId) {
index 45e8125..b91eab0 100644 (file)
@@ -23,13 +23,15 @@ package org.onap.cps.ncmp.init;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade;
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService;
 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate;
+import org.onap.cps.ncmp.impl.models.CmHandleMigrationDetail;
+import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Component;
 
 @Slf4j
@@ -37,9 +39,10 @@ import org.springframework.stereotype.Component;
 @RequiredArgsConstructor
 public class DataMigration {
 
-    public final InventoryPersistence inventoryPersistence;
+    private final InventoryPersistence inventoryPersistence;
     private final CmHandleQueryService cmHandleQueryService;
     private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade;
+    private final JsonObjectMapper jsonObjectMapper;
 
 
     /**
@@ -61,26 +64,31 @@ public class DataMigration {
 
     private void migrateBatch(final List<String> cmHandleIds) {
         log.debug("Processing batch of {} Cm Handles", cmHandleIds.size());
-        final List<CmHandleStateUpdate> cmHandleStateUpdates = new ArrayList<>();
+        final List<CmHandleMigrationDetail> cmHandleMigrationDetails =
+                new ArrayList<>(cmHandleIds.size());
         for (final String cmHandleId : cmHandleIds) {
             try {
                 final NcmpServiceCmHandle ncmpServiceCmHandle =
                         networkCmProxyInventoryFacade.getNcmpServiceCmHandle(cmHandleId);
-                final String valueFromOldModel = ncmpServiceCmHandle.getCompositeState().getCmHandleState().name();
-                cmHandleStateUpdates.add(new CmHandleStateUpdate(
+                cmHandleMigrationDetails.add(new CmHandleMigrationDetail(
                         ncmpServiceCmHandle.getCmHandleId(),
-                        valueFromOldModel
+                        ncmpServiceCmHandle.getCompositeState().getCmHandleState().name(),
+                        convertAdditionalPropertiesToJson(ncmpServiceCmHandle.getAdditionalProperties())
                 ));
             } catch (final Exception e) {
                 log.error("Failed to process CM handle {} state", cmHandleId, e);
             }
         }
         try {
-            inventoryPersistence.bulkUpdateCmHandleStates(cmHandleStateUpdates);
+            inventoryPersistence.cmHandleBulkMigrate(cmHandleMigrationDetails);
             log.debug("Successfully updated Cm Handles");
         } catch (final Exception e) {
             log.error("Failed to perform bulk update for batch", e);
         }
     }
+
+    private String convertAdditionalPropertiesToJson(final Map<String, String> additionalProperties) {
+        return jsonObjectMapper.asJsonString(additionalProperties);
+    }
 }
 
index 866ff71..72dba7a 100644 (file)
@@ -35,7 +35,7 @@ import org.onap.cps.api.model.ModuleReference
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.api.inventory.models.CompositeState
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
-import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate
+import org.onap.cps.ncmp.impl.models.CmHandleMigrationDetail
 import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.CpsValidator
 import org.onap.cps.utils.JsonObjectMapper
@@ -75,8 +75,8 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def cmHandleId = 'ch-1'
     def updates = [
-            new CmHandleStateUpdate("ch-1", "READY"),
-            new CmHandleStateUpdate("ch-2", "DELETING")
+            new CmHandleMigrationDetail("ch-1", "READY", "some-dmi-properties"),
+            new CmHandleMigrationDetail("ch-2", "DELETING", "some-dmi-properties")
     ]
     def alternateId = 'some-alternate-id'
     def leaves = ["id":cmHandleId, "alternateId":alternateId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
@@ -402,14 +402,23 @@ class InventoryPersistenceImplSpec extends Specification {
         when: 'update is called.'
             objectUnderTest.updateCmHandleField('ch-1', 'my field', 'my new value')
         then: 'call is delegated to updateCmHandleFields'
-        1 * objectUnderTest.updateCmHandleFields('my field', ['ch-1':'my new value'])
+            1 * objectUnderTest.updateCmHandleFields('my field', ['ch-1':'my new value'])
     }
 
     def 'Bulk update cm handle state.'(){
         when: 'bulk update is called'
-            objectUnderTest.bulkUpdateCmHandleStates(updates)
+            objectUnderTest.cmHandleBulkMigrate(updates)
         then: 'call is made to update the fileds of the cm handle'
-            1 * objectUnderTest.updateCmHandleFields('cm-handle-state', ['ch-1':'READY','ch-2':'DELETING'])
+        1 * mockCpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, { jsonString ->
+            jsonString.contains('"cm-handle-state":"READY"') && jsonString.contains('"cm-handle-state":"DELETING"')
+        }, _, ContentType.JSON)
+    }
+
+    def 'Bulk update with empty list.'() {
+        when: 'bulk update is called with empty list'
+            objectUnderTest.cmHandleBulkMigrate([])
+        then: 'no database call is made'
+            0 * mockCpsDataService.updateNodeLeaves(*_)
     }
 
 }
index 45fb482..a32b05f 100644 (file)
@@ -24,11 +24,13 @@ import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.Logger
 import ch.qos.logback.classic.spi.ILoggingEvent
 import ch.qos.logback.core.read.ListAppender
+import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
 import org.onap.cps.ncmp.api.inventory.models.CompositeState
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService
 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.utils.JsonObjectMapper
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
 import spock.lang.Subject
@@ -41,16 +43,16 @@ class DataMigrationSpec extends Specification{
     def mockCmHandleQueryService = Mock(CmHandleQueryService)
     def mockNetworkCmProxyInventoryFacade = Mock(NetworkCmProxyInventoryFacade)
     def mockInventoryPersistence = Mock(InventoryPersistence)
-    def cmHandle1 = new NcmpServiceCmHandle(cmHandleId: 'ch-1', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: READY))
-    def cmHandle2 = new NcmpServiceCmHandle(cmHandleId: 'ch-2', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: ADVISED))
-    def cmHandle3 = new NcmpServiceCmHandle(cmHandleId: 'ch-3', dmiServiceName: 'dmi2', compositeState: new CompositeState(cmHandleState: READY))
-
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    def cmHandleWithAdditionalProperty = new NcmpServiceCmHandle(cmHandleId: 'ch-1', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: READY), additionalProperties: [id: '4'])
+    def cmHandleWithAdditionalPropertyEmpty = new NcmpServiceCmHandle(cmHandleId: 'ch-2', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: ADVISED), additionalProperties: [:])
+    def cmHandleWithAdditionalPropertyNull = new NcmpServiceCmHandle(cmHandleId: 'ch-3', dmiServiceName: 'dmi2', compositeState: new CompositeState(cmHandleState: READY), additionalProperties:  null)
     def logger = Spy(ListAppender<ILoggingEvent>)
 
     def setup() {
-        mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-1') >> cmHandle1
-        mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-2') >> cmHandle2
-        mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-3') >> cmHandle3
+        mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-1') >> cmHandleWithAdditionalProperty
+        mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-2') >> cmHandleWithAdditionalPropertyEmpty
+        mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-3') >> cmHandleWithAdditionalPropertyNull
         setupLogger(Level.ERROR)
     }
 
@@ -60,7 +62,7 @@ class DataMigrationSpec extends Specification{
 
 
     @Subject
-    def objectUnderTest = Spy(new DataMigration(mockInventoryPersistence, mockCmHandleQueryService, mockNetworkCmProxyInventoryFacade))
+    def objectUnderTest = Spy(new DataMigration(mockInventoryPersistence, mockCmHandleQueryService, mockNetworkCmProxyInventoryFacade, jsonObjectMapper))
 
     def 'CM Handle migration.'() {
         given:  'a list of CM handle IDs'
@@ -69,14 +71,18 @@ class DataMigrationSpec extends Specification{
         when:   'migration is performed'
             objectUnderTest.migrateInventoryToModelRelease20250722(3)
         then:   'handles are processed in bulk'
-            1 * mockInventoryPersistence.bulkUpdateCmHandleStates({ cmHandleStateUpdates ->
-                def actualData = cmHandleStateUpdates.collect { [id: it.cmHandleId, state: it.state] }
+            1 * mockInventoryPersistence.cmHandleBulkMigrate({ cmHandleStateUpdates ->
+                def actualData = cmHandleStateUpdates.collect { [id: it.cmHandleId, state: it.state, dmiProperties: it.dmiProperties] }
                 assert actualData.size() == 3
-                assert actualData.containsAll([
-                    [id: 'ch-1', state: 'READY'],
-                    [id: 'ch-2', state: 'ADVISED'],
-                    [id: 'ch-3', state: 'READY']
-                ])
+                assert actualData[0].id == 'ch-1'
+                assert actualData[0].state == 'READY'
+                assert actualData[0].dmiProperties == '{"id":"4"}'
+                assert actualData[1].id == 'ch-2'
+                assert actualData[1].state == 'ADVISED'
+                assert actualData[1].dmiProperties == '{}'
+                assert actualData[2].id == 'ch-3'
+                assert actualData[2].state == 'READY'
+                assert actualData[2].dmiProperties == 'null'
             })
     }
 
@@ -89,7 +95,7 @@ class DataMigrationSpec extends Specification{
         when: 'migration is performed'
             objectUnderTest.migrateInventoryToModelRelease20250722(2)
         then: 'migration processes no batches'
-            1 * mockInventoryPersistence.bulkUpdateCmHandleStates([])
+            1 * mockInventoryPersistence.cmHandleBulkMigrate([])
     }
 
     def 'Migrate batch with error.'() {
@@ -97,7 +103,7 @@ class DataMigrationSpec extends Specification{
             def cmHandleIds = ['ch-1']
             mockCmHandleQueryService.getAllCmHandleReferences(false) >> cmHandleIds
         and: 'an exception happens updating cm handle states'
-            mockInventoryPersistence.bulkUpdateCmHandleStates(*_) >> {
+            mockInventoryPersistence.cmHandleBulkMigrate(*_) >> {
                 throw new RuntimeException('Simulated failure')
             }
         when: 'migration is performed'
index ebf3c63..0088b26 100644 (file)
@@ -1829,6 +1829,13 @@ paths:
               schema:
                 type: object
           description: OK
+        "204":
+          content:
+            application/json:
+              schema:
+                example: my-resource
+                type: string
+          description: Created
         "400":
           content:
             application/json: