Synchronize CompositeState and cm-handle-state 08/143208/9
authoregernug <gerard.nugent@est.tech>
Tue, 6 Jan 2026 16:16:30 +0000 (16:16 +0000)
committeregernug <gerard.nugent@est.tech>
Mon, 23 Feb 2026 10:42:03 +0000 (10:42 +0000)
- Inverted behaviour of flag to ignore instead of enable new model
- Amended method to sync both CompositeState and cm-handle-state if new model is used

Issue-ID: CPS-3106

Change-Id: I93e08ffdd06c5a5566f3559b5892d6e26e5fccdc
Signed-off-by: egernug <gerard.nugent@est.tech>
Synchronize CompositeState and cm-handle-state

- Inverted behaviour of flag to ignore instead of enable new model
- Amended method to sync both CompositeState and cm-handle-state if new model is used

Issue-ID: CPS-3106

Change-Id: I93e08ffdd06c5a5566f3559b5892d6e26e5fccdc
Signed-off-by: egernug <gerard.nugent@est.tech>
cps-charts/values.yaml
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/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.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/InventoryModelLoaderSpec.groovy
docker-compose/docker-compose.yml
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/inventory/DataMigrationIntegrationSpec.groovy

index 87513b8..4b03d5a 100644 (file)
@@ -108,7 +108,7 @@ cps:
     CPS_MONITORING_MICROMETER_JVM_EXTRAS: "true"
     JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0"
     HAZELCAST_MODE_KUBERNETES_ENABLED: "true"
-    NCMP_INVENTORY_MODEL_UPGRADE_R20250722_ENABLED: 'false'
+    IGNORE_R20250722_MODEL: "true"
 
 kafka:
   enabled: true
index 07e0118..4537157 100644 (file)
@@ -51,7 +51,7 @@ public interface InventoryPersistence extends NcmpPersistence {
     void saveCmHandleState(String cmHandleId, CompositeState compositeState);
 
     /**
-     * Save all cm handles states in batch.
+     * Save all cm handle CompositeStates and cmHandleStatus in batch.
      *
      * @param cmHandleStatePerCmHandleId contains cm handle id and updated state
      */
index 238436d..b3e9b11 100644 (file)
@@ -55,6 +55,7 @@ import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 @Slf4j
@@ -67,6 +68,9 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     private final CpsValidator cpsValidator;
     private final IMap<String, String> cmHandleIdPerAlternateId;
 
+    @Value("${ignore.r20250722.model:true}")
+    private boolean ignoreModelR20250722;
+
     /**
      * initialize an inventory persistence object.
      *
@@ -100,25 +104,39 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
 
     @Override
     public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
-        final String cmHandleJsonData = createStateJsonData(jsonObjectMapper.asJsonString(compositeState));
-        cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                getXPathForCmHandleById(cmHandleId), cmHandleJsonData, OffsetDateTime.now(), ContentType.JSON);
+        saveCmHandleStateBatch(Collections.singletonMap(cmHandleId, compositeState));
     }
 
     @Override
     public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) {
-        final Map<String, String> cmHandlesJsonDataMap = new HashMap<>();
-        cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> {
+        final Map<String, String> cmHandlesJsonDataMap = new HashMap<>(cmHandleStatePerCmHandleId.size());
+        final List<Map<String, String>> topLevelStateUpdates = new ArrayList<>(cmHandleStatePerCmHandleId.size());
+
+        for (final Map.Entry<String, CompositeState> entry : cmHandleStatePerCmHandleId.entrySet()) {
+            final String cmHandleId = entry.getKey();
+            final CompositeState compositeState = entry.getValue();
             if (exists(cmHandleId)) {
                 cmHandlesJsonDataMap.put(getXPathForCmHandleById(cmHandleId),
                         createStateJsonData(jsonObjectMapper.asJsonString(compositeState)));
+                if (!ignoreModelR20250722) {
+                    final Map<String, String> topLevelUpdate = new HashMap<>();
+                    topLevelUpdate.put("id", cmHandleId);
+                    topLevelUpdate.put("cm-handle-state", compositeState.getCmHandleState().name());
+                    topLevelStateUpdates.add(topLevelUpdate);
+                }
             } else {
                 log.warn("Failure to save state for cmHandle id '{}' as it does not exist in cache", cmHandleId);
             }
-        });
+        }
         if (!cmHandlesJsonDataMap.isEmpty()) {
             cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                     cmHandlesJsonDataMap, OffsetDateTime.now(), ContentType.JSON);
+            if (!ignoreModelR20250722) {
+                cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                        NCMP_DMI_REGISTRY_PARENT,
+                        jsonObjectMapper.asJsonString(Map.of("cm-handles", topLevelStateUpdates)),
+                        OffsetDateTime.now(), ContentType.JSON);
+            }
         }
     }
 
index 37e1ae3..ba7d185 100644 (file)
@@ -102,8 +102,8 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState
             if (isNew(cmHandleTransitionPair.currentYangModelCmHandle().getCompositeState())) {
                 newCmHandles.add(cmHandleTransitionPair.targetYangModelCmHandle());
             } else if (!isDeleted(cmHandleTransitionPair.targetYangModelCmHandle().getCompositeState())) {
-                compositeStatePerCmHandleId.put(cmHandleTransitionPair.targetYangModelCmHandle().getId(),
-                        cmHandleTransitionPair.targetYangModelCmHandle().getCompositeState());
+                final YangModelCmHandle targetCmHandle = cmHandleTransitionPair.targetYangModelCmHandle();
+                compositeStatePerCmHandleId.put(targetCmHandle.getId(), targetCmHandle.getCompositeState());
             }
         });
         inventoryPersistence.saveCmHandleBatch(newCmHandles);
index 6d08e2c..6bf1008 100644 (file)
@@ -43,13 +43,13 @@ public class InventoryModelLoader extends AbstractModelLoader {
     private final DataMigration dataMigration;
     private final ApplicationEventPublisher applicationEventPublisher;
 
-    private static final String PREVIOUS_SCHEMA_SET_NAME = "dmi-registry-2024-02-23";
+    private static final String SCHEMA_SET_NAME = "dmi-registry-2024-02-23";
     private static final String NEW_INVENTORY_SCHEMA_SET_NAME = "dmi-registry-2025-07-22";
     private static final String INVENTORY_YANG_MODULE_NAME = "dmi-registry";
     private static final int MIGRATION_BATCH_SIZE = 300;
 
-    @Value("${ncmp.inventory.model.upgrade.r20250722.enabled:false}")
-    private boolean newRevisionEnabled;
+    @Value("${ignore.r20250722.model:true}")
+    private boolean ignoreModelR20250722;
 
     /**
      * Creates a new {@code InventoryModelLoader} instance responsible for onboarding or upgrading
@@ -75,13 +75,14 @@ public class InventoryModelLoader extends AbstractModelLoader {
         if (isMaster) {
             log.info("Model Loader #2 Started: NCMP Inventory Models");
             final String schemaToInstall =
-                newRevisionEnabled ? NEW_INVENTORY_SCHEMA_SET_NAME : PREVIOUS_SCHEMA_SET_NAME;
+                ignoreModelR20250722 ? SCHEMA_SET_NAME : NEW_INVENTORY_SCHEMA_SET_NAME;
             final String moduleRevision = getModuleRevision(schemaToInstall);
+            log.info("Model Loader #2 Schema Set: {}", schemaToInstall);
 
             if (isModuleRevisionInstalled(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, INVENTORY_YANG_MODULE_NAME,
                 moduleRevision)) {
                 log.info("Model Loader #2: Revision {} is already installed.", moduleRevision);
-            } else if (newRevisionEnabled && doesAnchorExist(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR)) {
+            } else if (!ignoreModelR20250722 && doesAnchorExist(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR)) {
                 log.info("Model Loader #2: Upgrading already installed inventory to revision {}.", moduleRevision);
                 upgradeAndMigrateInventoryModel();
             } else {
index f0b2440..cca16c8 100644 (file)
@@ -39,6 +39,7 @@ 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
+import org.springframework.test.util.ReflectionTestUtils
 import spock.lang.Shared
 import spock.lang.Specification
 
@@ -183,15 +184,31 @@ class InventoryPersistenceImplSpec extends Specification {
         given: 'a cm handle and a composite state'
             def cmHandleId = 'ch-1'
             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
+        and: 'alternate id cache contains the given cm handle reference'
+            mockCmHandleIdPerAlternateId.containsKey(_) >> true
         when: 'update cm handle state is invoked with the #scenario state'
             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
-        then: 'update node leaves is invoked with the correct params'
-            1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'ch-1\']', expectedJsonData, _ as OffsetDateTime, ContentType.JSON)
+        then: 'update data nodes and descendants is invoked with the correct params'
+            1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['/dmi-registry/cm-handles[@id=\'ch-1\']': expectedJsonData], _ as OffsetDateTime, ContentType.JSON)
         where: 'the following states are used'
             scenario    | cmHandleState          || expectedJsonData
             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
-            'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
+            'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'}
+
+
+    def 'Update Cm Handle State when model upgrade is enabled.'() {
+        given: 'a cm handle and a composite state'
+            def cmHandleId = 'ch-1'
+            def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, lastUpdateTime: formattedDateAndTime)
+        and: 'alternate id cache contains the given cm handle reference'
+            mockCmHandleIdPerAlternateId.containsKey(_) >> true
+        when: 'update cm handle state is invoked'
+            objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
+        then: 'update data nodes and descendants is invoked'
+            1 * mockCpsDataService.updateDataNodesAndDescendants(*_)
+        and: 'update node leaves is also invoked for top-level state'
+            1 * mockCpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', _, _ as OffsetDateTime, ContentType.JSON)
     }
 
     def 'Update Cm Handles with #scenario States.'() {
@@ -203,7 +220,7 @@ class InventoryPersistenceImplSpec extends Specification {
         when: 'update cm handle state is invoked with the #scenario state'
             def cmHandleStateMap = ['ch-11' : compositeState1, 'ch-12' : compositeState2]
             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
-        then: 'update node leaves is invoked with the correct params'
+        then: 'update data nodes and descendants is invoked with the correct params'
             1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime, ContentType.JSON)
         where: 'the following states are used'
             scenario    | cmHandleState          || cmHandlesJsonDataMap
@@ -221,14 +238,15 @@ class InventoryPersistenceImplSpec extends Specification {
             mockCmHandleIdPerAlternateId.containsValue(_) >> valueExists
         when: 'we update the state of a cm handle when #scenario'
             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
-        then: 'update node leaves is invoked correct number of times'
+        then: 'update data nodes and descendants is invoked correct number of times'
             expectedCalls * mockCpsDataService.updateDataNodesAndDescendants(*_)
+        and: 'update node leaves is invoked correct number of times'
+            expectedCalls * mockCpsDataService.updateNodeLeaves(*_)
         where: 'the following cm handle ids are used'
             scenario            | keyExists | valueExists || expectedCalls
             'id exists as key'  | true      | false       || 1
             'id exists as value'| false     | true        || 1
             'id does not exist' | false     | false       || 0
-
     }
 
     def 'Getting module definitions by module.'() {
index bddb52d..45786bf 100644 (file)
@@ -72,7 +72,7 @@ class InventoryModelLoaderSpec extends Specification {
         objectUnderTest.isMaster = true
         expectedPreviousYangResourceToContentMap = objectUnderTest.mapYangResourcesToContent('dmi-registry@2024-02-23.yang')
         expectedNewYangResourceToContentMap = objectUnderTest.mapYangResourcesToContent('dmi-registry@2025-07-22.yang')
-        objectUnderTest.newRevisionEnabled = true
+        objectUnderTest.ignoreModelR20250722 = false
         logger.setLevel(Level.DEBUG)
         loggingListAppender = new ListAppender()
         logger.addAppender(loggingListAppender)
@@ -86,8 +86,8 @@ class InventoryModelLoaderSpec extends Specification {
     }
 
     def 'Onboard subscription model via application ready event.'() {
-        given: 'dataspace is ready for use with default newRevisionEnabled flag'
-            objectUnderTest.newRevisionEnabled = false
+        given: 'dataspace is ready for use with current model'
+            objectUnderTest.ignoreModelR20250722 = true
             mockCpsAdminService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('')
         and: 'module revision does not exist'
             mockCpsModuleService.getModuleDefinitionsByAnchorAndModule(_, _, _, _) >> Collections.emptyList()
index 4326335..e78a789 100644 (file)
@@ -74,7 +74,7 @@ services:
       JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0"
       ### DEBUG: Uncomment next line to enable java debugging (and comment out above line!)
       ###JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
-      NCMP_INVENTORY_MODEL_UPGRADE_R20250722_ENABLED: 'false'
+      IGNORE_R20250722_MODEL: 'true'
     restart: on-failure:3
     deploy:
       replicas: 0
index 8b31db6..95db10f 100644 (file)
 package org.onap.cps.integration.functional.ncmp.inventory
 
 import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
-import org.onap.cps.ncmp.init.DataMigration
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.init.InventoryModelLoader
 import org.onap.cps.utils.ContentType
 import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.test.context.TestPropertySource
+import org.springframework.test.util.ReflectionTestUtils
 
-@TestPropertySource(properties = ["ncmp.inventory.model.upgrade.r20250722.enabled=true"])
 class DataMigrationIntegrationSpec extends CpsIntegrationSpecBase {
 
     @Autowired
-    DataMigration objectUnderTest
+    InventoryModelLoader objectUnderTest
+
+    @Autowired
+    InventoryPersistence inventoryPersistence
 
     def 'Migrate inventory with batch processing.'() {
-        given: 'DMI will return modules when requested'
+        given: 'start with the old models (ignore upgrade)'
+            ReflectionTestUtils.setField(inventoryPersistence, 'ignoreModelR20250722', true)
+        and: 'DMI will return modules when requested'
             dmiDispatcher1.moduleNamesPerCmHandleId = (1..2).collectEntries{ ['ch-'+it, ['M1']] }
-        and: 'multiple CM handles registered, (top level) status is null'
+        and: 'multiple CM handles registered with old model'
             (1..2).each {
                 registerCmHandle(DMI1_URL, 'ch-'+it, NO_MODULE_SET_TAG)
                 cpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='ch-${it}']",
@@ -44,21 +48,19 @@ class DataMigrationIntegrationSpec extends CpsIntegrationSpecBase {
                 def someCmHandle = networkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-'+it)
                 assert someCmHandle.getCmHandleStatus() == null
                 assert someCmHandle.getCompositeState().getCmHandleState().name() == 'READY'
-                assert someCmHandle.getDmiProperties() == null
-                assert someCmHandle.getAdditionalProperties() == ['prop1':'value1']
             }
-        when: 'migration is executed'
-            objectUnderTest.migrateInventoryToModelRelease20250722(1)
-        then: 'all CM handles are processed successfully,' +
-                'the (top level) status is set to the same value as the name of the complex state value'
+        when: 'the new (more performant) model is enabled (no longer ignored)'
+            ReflectionTestUtils.setField(inventoryPersistence, 'ignoreModelR20250722', false)
+            ReflectionTestUtils.setField(objectUnderTest, 'ignoreModelR20250722', false)
+        and: 'inventory is upgraded to the new revision'
+            objectUnderTest.onboardOrUpgradeModel()
+        then: 'all CM handles have top-level cm-handle-state populated'
             (1..2).each {
                 def someCmHandle = networkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-'+it)
                 assert someCmHandle.getCmHandleStatus() == 'READY'
                 assert someCmHandle.getCompositeState().getCmHandleState().name() == 'READY'
-                assert someCmHandle.getDmiProperties() == '{"prop1":"value1"}'
-                assert someCmHandle.getAdditionalProperties() == ['prop1':'value1']
             }
         cleanup: 'deregister CM handles'
             deregisterCmHandles(DMI1_URL, (1..2).collect{ 'ch-'+it })
     }
-}
\ No newline at end of file
+}