Refactor Rollback logic to send stages in the reverse order 93/142393/2
authorFrancescoFioraEst <francesco.fiora@est.tech>
Thu, 6 Nov 2025 08:50:17 +0000 (08:50 +0000)
committerFrancescoFioraEst <francesco.fiora@est.tech>
Fri, 7 Nov 2025 09:39:05 +0000 (09:39 +0000)
Issue-ID: POLICY-5474
Change-Id: I93dc0afd3b061e357f7ae095d33a46ca3b039458
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
14 files changed:
models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java
models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmStageUtils.java
models/src/test/java/org/onap/policy/clamp/models/acm/utils/AcmStageUtilsTest.java
participant/participant-impl/participant-impl-simulator/src/test/java/org/onap/policy/clamp/acm/participant/sim/main/handler/AutomationCompositionElementHandlerTest.java
participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImpl.java
participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImplTest.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAcHandler.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionMigrationPublisher.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScanner.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAcHandlerTest.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/comm/SupervisionMessagesTest.java

index acb299e..c438d01 100644 (file)
@@ -40,6 +40,7 @@ public class AutomationCompositionMigration extends ParticipantMessage {
 
     private Boolean precheck = false;
     private Boolean rollback = false;
+    private Boolean firstStage = true;
     private Integer stage = 0;
 
     public AutomationCompositionMigration() {
index 9cfade4..fb9125b 100644 (file)
@@ -35,6 +35,7 @@ import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 public final class AcmStageUtils {
     private static final String STAGE_MIGRATE = "migrate";
     private static final String STAGE_PREPARE = "prepare";
+    private static final int MAX_STAGE = 1000;
 
     /**
      * Get the First StartPhase.
@@ -48,7 +49,7 @@ public final class AcmStageUtils {
      */
     public static int getFirstStartPhase(
             AutomationComposition automationComposition, ToscaServiceTemplate toscaServiceTemplate) {
-        var minStartPhase = 1000;
+        var minStartPhase = MAX_STAGE;
         var maxStartPhase = 0;
         for (var element : automationComposition.getElements().values()) {
             var toscaNodeTemplate = toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates()
@@ -108,6 +109,50 @@ public final class AcmStageUtils {
         return stageSet.stream().min(Integer::compare).orElse(0);
     }
 
+    /**
+     * Get the Last Stage from AutomationComposition.
+     *
+     * @param automationComposition the automation composition
+     * @param toscaServiceTemplate the ToscaServiceTemplate
+     * @return the Last stage
+     */
+    public static int getLastStage(AutomationComposition automationComposition,
+            ToscaServiceTemplate toscaServiceTemplate) {
+        var stages = automationComposition.getElements().values().stream()
+                .map(element -> getLastStage(element, toscaServiceTemplate, 0));
+        return stages.max(Integer::compare).orElse(0);
+    }
+
+    /**
+     * Get the Last Stage from AutomationCompositionElement.
+     *
+     * @param element the automation composition element
+     * @param toscaServiceTemplate the ToscaServiceTemplate
+     * @param defaultValue default Value is not present
+     * @return the Last stage
+     */
+    public static int getLastStage(
+            AutomationCompositionElement element, ToscaServiceTemplate toscaServiceTemplate, int defaultValue) {
+        var toscaNodeTemplate = toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates()
+                .get(element.getDefinition().getName());
+        if (toscaNodeTemplate == null) {
+            return defaultValue;
+        }
+        return getLastStage(toscaNodeTemplate.getProperties(), defaultValue);
+    }
+
+    /**
+     * Get the Last Stage.
+     *
+     * @param properties Map of properties
+     * @param defaultValue default Value is not present
+     * @return the Last stage
+     */
+    public static int getLastStage(Map<String, Object> properties, int defaultValue) {
+        var stageSet = findStageSetMigrate(properties);
+        return stageSet.stream().max(Integer::compare).orElse(defaultValue);
+    }
+
     /**
      * Finds startPhase from a map of properties.
      *
index fbcb568..45ec379 100644 (file)
@@ -21,6 +21,7 @@
 package org.onap.policy.clamp.models.acm.utils;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.util.Map;
 import org.junit.jupiter.api.Test;
@@ -128,6 +129,28 @@ class AcmStageUtilsTest {
         automationComposition.setSubState(SubState.PREPARING);
         result = AcmStageUtils.getFirstStage(automationComposition, serviceTemplate);
         assertThat(result).isZero();
+
+        serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().clear();
+        result = AcmStageUtils.getFirstStage(automationComposition.getElements().values().iterator().next(),
+                serviceTemplate);
+        assertThat(result).isZero();
+    }
+
+    @Test
+    void testGetLastStage() {
+        var serviceTemplate = CommonTestData.getToscaServiceTemplate(TOSCA_TEMPLATE_YAML);
+        var automationCompositions = CommonTestData.getJson(
+                ResourceUtils.getResourceAsString(AUTOMATION_COMPOSITION_JSON), AutomationCompositions.class);
+        assertThat(automationCompositions).isNotNull();
+        var automationComposition = automationCompositions.getAutomationCompositionList().get(0);
+        AcmStateUtils.setCascadedState(automationComposition, DeployState.MIGRATION_REVERTING, LockState.LOCKED);
+        var result = AcmStageUtils.getLastStage(automationComposition, serviceTemplate);
+        assertEquals(2, result);
+
+        serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().clear();
+        result = AcmStageUtils.getLastStage(automationComposition.getElements().values().iterator().next(),
+                serviceTemplate, 1);
+        assertEquals(1, result);
     }
 
     @Test
index d52f584..13afbcc 100644 (file)
@@ -395,10 +395,10 @@ class AutomationCompositionElementHandlerTest {
         var instanceElementRollback = new InstanceElementDto(INSTANCE_ELEMENT.instanceId(),
                 INSTANCE_ELEMENT.elementId(), Map.of(), new HashMap<>());
         acElementHandler.rollbackMigration(COMPOSITION_ELEMENT, compositionElementRollback, INSTANCE_ELEMENT,
-                instanceElementRollback, 1);
+                instanceElementRollback, 2);
         verify(intermediaryApi).updateAutomationCompositionElementStage(
                 INSTANCE_ELEMENT.instanceId(), INSTANCE_ELEMENT.elementId(),
-                StateChangeResult.NO_ERROR, 2, "stage 1 Migration rollback");
+                StateChangeResult.NO_ERROR, 1, "stage 2 Migration rollback");
     }
 
     @Test
index f4e3511..b09070c 100644 (file)
@@ -101,7 +101,14 @@ public class ParticipantIntermediaryApiImpl implements ParticipantIntermediaryAp
 
     @Override
     public int getRollbackNextStage(CompositionElementDto compositionElementRollback, int lastStage) {
-        return getMigrateNextStage(compositionElementRollback, lastStage);
+        var stageSet = AcmStageUtils.findStageSetMigrate(compositionElementRollback.inProperties());
+        var nextStage = -1;
+        for (var s : stageSet) {
+            if (s < lastStage) {
+                nextStage = Math.max(s, nextStage);
+            }
+        }
+        return nextStage == -1 ? lastStage : nextStage;
     }
 
     @Override
index 84a7738..786541e 100644 (file)
@@ -195,9 +195,9 @@ public class AutomationCompositionHandler {
     }
 
     private void rollbackAutomationComposition(AutomationComposition automationComposition,
-            ParticipantDeploy participantDeploy, int stage) {
+            ParticipantDeploy participantDeploy, int stage, int defaultValue) {
         for (var element : participantDeploy.getAcElementList()) {
-            var stageSet = getRollbackStageSet(element, automationComposition.getCompositionId());
+            var stageSet = getRollbackStageSet(element, automationComposition.getCompositionId(), defaultValue);
             if (stageSet.contains(stage)) {
                 migrateElement(element, automationComposition, stage, participantDeploy);
             }
@@ -310,9 +310,12 @@ public class AutomationCompositionHandler {
         automationComposition.setCompositionTargetId(migrationMsg.getCompositionTargetId());
         automationComposition.setDeployState(DeployState.MIGRATION_REVERTING);
         var automationCompositionCopy = new AutomationComposition(automationComposition);
+        var defaultValue =  Boolean.TRUE.equals(migrationMsg.getFirstStage())
+                ? migrationMsg.getStage() : migrationMsg.getStage() + 1;
         for (var participantDeploy : migrationMsg.getParticipantUpdatesList()) {
             if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
-                rollbackAutomationComposition(automationComposition, participantDeploy, migrationMsg.getStage());
+                rollbackAutomationComposition(
+                        automationComposition, participantDeploy, migrationMsg.getStage(), defaultValue);
                 callParticipantRollback(migrationMsg, participantDeploy.getAcElementList(), automationCompositionCopy);
             }
 
@@ -388,6 +391,8 @@ public class AutomationCompositionHandler {
 
     private void callParticipantRollback(AutomationCompositionMigration migrationMsg, List<AcElementDeploy> acElements,
             AutomationComposition automationCompositionCopy) {
+        var defaultValue =  Boolean.TRUE.equals(migrationMsg.getFirstStage())
+                ? migrationMsg.getStage() : migrationMsg.getStage() + 1;
         var automationComposition = cacheProvider.getAutomationComposition(automationCompositionCopy.getInstanceId());
         var instanceElementTargetMap = cacheProvider.getInstanceElementDtoMap(automationComposition);
         var compositionElementTargetMap = cacheProvider.getCompositionElementDtoMap(automationComposition);
@@ -395,7 +400,7 @@ public class AutomationCompositionHandler {
                 automationCompositionCopy.getCompositionTargetId());
         var instanceElementMap = cacheProvider.getInstanceElementDtoMap(automationCompositionCopy);
         for (var acElement : acElements) {
-            var stageSet = getRollbackStageSet(acElement, automationComposition.getCompositionId());
+            var stageSet = getRollbackStageSet(acElement, automationComposition.getCompositionId(), defaultValue);
             var removed = MigrationState.NEW.equals(acElement.getMigrationState());
             if (!removed) {
                 var commonProperties = cacheProvider
@@ -438,9 +443,9 @@ public class AutomationCompositionHandler {
         }
     }
 
-    private Set<Integer> getRollbackStageSet(AcElementDeploy acElement, UUID compositionId) {
+    private Set<Integer> getRollbackStageSet(AcElementDeploy acElement, UUID compositionId, int defaultValue) {
         if (MigrationState.NEW.equals(acElement.getMigrationState())) {
-            return Set.of(0);
+            return Set.of(defaultValue);
         } else {
             var commonProperties = cacheProvider.getCommonProperties(compositionId, acElement.getDefinition());
             return AcmStageUtils.findStageSetMigrate(commonProperties);
index 752b670..5d602fb 100644 (file)
@@ -272,9 +272,9 @@ class ParticipantIntermediaryApiImplTest {
         Map<String, Object> stageSet = Map.of("stage", migrate);
         var compositionElementTarget = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
                 stageSet, Map.of());
-        var result = apiImpl.getRollbackNextStage(compositionElementTarget, 0);
-        assertEquals(2, result);
-        result = apiImpl.getRollbackNextStage(compositionElementTarget, 2);
-        assertEquals(2, result);
+        var result = apiImpl.getRollbackNextStage(compositionElementTarget, 2);
+        assertEquals(0, result);
+        result = apiImpl.getRollbackNextStage(compositionElementTarget, 0);
+        assertEquals(0, result);
     }
 }
index 46002cb..852d6ab 100644 (file)
@@ -358,6 +358,7 @@ class AutomationCompositionHandlerTest {
             int stage, int expectedMigrated, boolean rollback) {
         var migrationMsg = new AutomationCompositionMigration();
         migrationMsg.setStage(stage);
+        migrationMsg.setFirstStage(rollback && stage == 2);
         migrationMsg.setCompositionId(acMigrate.getCompositionId());
         migrationMsg.setAutomationCompositionId(acMigrate.getInstanceId());
         migrationMsg.setCompositionTargetId(acMigrate.getCompositionTargetId());
@@ -430,14 +431,11 @@ class AutomationCompositionHandlerTest {
         cacheProvider.addElementDefinition(
                 automationComposition.getCompositionId(), acRollbackDefinitions, UUID.randomUUID());
 
-        // expected the new element deleted
-        testMigration(cacheProvider, acRollback, 0, 1, true);
-
         // expected default elements
         testMigration(cacheProvider, acRollback, 1, 4, true);
 
-        // expected default elements
-        testMigration(cacheProvider, acRollback, 2, 4, true);
+        // expected default elements and new element deleted
+        testMigration(cacheProvider, acRollback, 2, 5, true);
     }
 
 }
index 8531c6b..fdb9cd2 100644 (file)
@@ -251,7 +251,9 @@ public class AutomationCompositionInstantiationProvider {
                                       AutomationCompositionDefinition acDefinition, DeployState deployState) {
         AcmStateUtils.setCascadedState(acFromDb, deployState, LockState.LOCKED);
         acFromDb.setStateChangeResult(StateChangeResult.NO_ERROR);
-        var stage = AcmStageUtils.getFirstStage(acFromDb, acDefinition.getServiceTemplate());
+        var stage = DeployState.MIGRATION_REVERTING.equals(deployState)
+                ?  AcmStageUtils.getLastStage(acFromDb, acDefinition.getServiceTemplate())
+                : AcmStageUtils.getFirstStage(acFromDb, acDefinition.getServiceTemplate());
         acFromDb.setPhase(stage);
     }
 
index eb1d3f6..5dc13a9 100644 (file)
@@ -371,7 +371,7 @@ public class SupervisionAcHandler {
         executor.execute(() -> {
             encryptionUtils.decryptInstanceProperties(automationComposition);
             acCompositionMigrationPublisher.send(automationComposition, automationComposition.getPhase(),
-                    revisionIdComposition, revisionIdCompositionTarget);
+                    revisionIdComposition, revisionIdCompositionTarget, true);
         });
     }
 
@@ -385,6 +385,6 @@ public class SupervisionAcHandler {
     public void migratePrecheck(AutomationComposition automationComposition, UUID revisionIdComposition,
             UUID revisionIdCompositionTarget) {
         executor.execute(() -> acCompositionMigrationPublisher.send(automationComposition, 0,
-                revisionIdComposition, revisionIdCompositionTarget));
+                revisionIdComposition, revisionIdCompositionTarget, true));
     }
 }
index 046c44c..36731fa 100644 (file)
@@ -47,10 +47,11 @@ public class AutomationCompositionMigrationPublisher
             value = "publisher.automation_composition_migration",
             description = "AUTOMATION_COMPOSITION_MIGRATION messages published")
     public void send(AutomationComposition automationComposition, int stage, UUID revisionIdComposition,
-                     UUID revisionIdCompositionTarget) {
+                     UUID revisionIdCompositionTarget, boolean fisrtStage) {
         var acMigration = new AutomationCompositionMigration();
         var rollback = DeployState.MIGRATION_REVERTING.equals(automationComposition.getDeployState());
         acMigration.setRollback(rollback);
+        acMigration.setFirstStage(fisrtStage);
         acMigration.setPrecheck(Boolean.TRUE.equals(automationComposition.getPrecheck()));
         acMigration.setCompositionId(automationComposition.getCompositionId());
         acMigration.setAutomationCompositionId(automationComposition.getInstanceId());
index 3df9ccb..ae0250a 100644 (file)
@@ -30,6 +30,7 @@ import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionM
 import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
 import org.onap.policy.clamp.models.acm.concepts.SubState;
@@ -75,15 +76,17 @@ public class StageScanner extends AbstractScanner {
      */
     public void scanStage(final AutomationComposition automationComposition,
             AutomationCompositionDefinition acDefinition, UpdateSync updateSync, UUID revisionIdComposition) {
+        var rollback = DeployState.MIGRATION_REVERTING.equals(automationComposition.getDeployState());
+        var highStage = AcmStageUtils.getLastStage(automationComposition, acDefinition.getServiceTemplate());
+        var stageNotCompleted = rollback ? -1 : 1000; // min stage not completed
         var completed = true;
-        var minStageNotCompleted = 1000; // min stage not completed
         List<UUID> elementsDeleted = new ArrayList<>();
         for (var element : automationComposition.getElements().values()) {
             if (AcmStateUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
                     element.getSubState())) {
-                var firstStage = AcmStageUtils.getFirstStage(element, acDefinition.getServiceTemplate());
-                int stage = element.getStage() != null ? element.getStage() : firstStage;
-                minStageNotCompleted = Math.min(minStageNotCompleted, stage);
+                stageNotCompleted = rollback
+                        ? Math.max(stageNotCompleted, getRollbackCurrentStage(element, acDefinition, highStage))
+                        : Math.min(stageNotCompleted, getCurrentStage(element, acDefinition));
                 completed = false;
             } else if (element.getDeployState().equals(DeployState.DELETED)
                     && automationComposition.getStateChangeResult().equals(StateChangeResult.NO_ERROR)) {
@@ -95,11 +98,22 @@ public class StageScanner extends AbstractScanner {
         if (completed) {
             complete(automationComposition, updateSync);
         } else {
-            processNextStage(automationComposition, updateSync, minStageNotCompleted, revisionIdComposition,
+            processNextStage(automationComposition, updateSync, stageNotCompleted, revisionIdComposition,
                     acDefinition);
         }
     }
 
+    private int getCurrentStage(AutomationCompositionElement element, AutomationCompositionDefinition acDefinition) {
+        return element.getStage() != null
+                ? element.getStage() : AcmStageUtils.getFirstStage(element, acDefinition.getServiceTemplate());
+    }
+
+    private int getRollbackCurrentStage(
+            AutomationCompositionElement element, AutomationCompositionDefinition acDefinition, int defaultValue) {
+        return element.getStage() != null ? element.getStage()
+                : AcmStageUtils.getLastStage(element, acDefinition.getServiceTemplate(), defaultValue);
+    }
+
     private void processNextStage(AutomationComposition automationComposition, UpdateSync updateSync,
                                   int minStageNotCompleted, UUID revisionIdComposition,
                                   AutomationCompositionDefinition acDefinition) {
@@ -134,11 +148,11 @@ public class StageScanner extends AbstractScanner {
             LOGGER.debug("retry migrating message AutomationCompositionMigration");
             // acDefinition for migration is the Composition target
             acMigrationPublisher.send(automationComposition, minStageNotCompleted, revisionIdComposition,
-                    acDefinition.getRevisionId());
+                    acDefinition.getRevisionId(), false);
         } else if (DeployState.MIGRATION_REVERTING.equals(automationComposition.getDeployState())) {
             LOGGER.debug("retry rollback message AutomationCompositionMigration");
             acMigrationPublisher.send(automationComposition, minStageNotCompleted, acDefinition.getRevisionId(),
-                    revisionIdComposition);
+                    revisionIdComposition, false);
         } else if (SubState.PREPARING.equals(automationComposition.getSubState())) {
             LOGGER.debug("retry message AutomationCompositionPrepare");
             acPreparePublisher.sendPrepare(automationComposition, minStageNotCompleted, acDefinition.getRevisionId());
index b9c5f31..00d152a 100644 (file)
@@ -540,7 +540,7 @@ class SupervisionAcHandlerTest {
         automationComposition.setPhase(0);
         handler.migrate(automationComposition, UUID.randomUUID(), UUID.randomUUID());
         verify(acCompositionMigrationPublisher, timeout(1000))
-                .send(any(AutomationComposition.class), anyInt(), any(UUID.class), any(UUID.class));
+                .send(any(AutomationComposition.class), anyInt(), any(UUID.class), any(UUID.class), anyBoolean());
     }
 
     @Test
@@ -555,7 +555,7 @@ class SupervisionAcHandlerTest {
                 InstantiationUtils.getAutomationCompositionFromResource(AC_INSTANTIATION_CREATE_JSON, "Migrate");
         handler.migratePrecheck(automationComposition, UUID.randomUUID(), UUID.randomUUID());
         verify(acCompositionMigrationPublisher, timeout(1000))
-                .send(any(AutomationComposition.class), anyInt(), any(UUID.class), any(UUID.class));
+                .send(any(AutomationComposition.class), anyInt(), any(UUID.class), any(UUID.class), anyBoolean());
     }
 
     @Test
index 84b80ae..e5f01c8 100644 (file)
@@ -235,7 +235,7 @@ class SupervisionMessagesTest {
         publisher.active(topicSink);
         var automationComposition =
                 InstantiationUtils.getAutomationCompositionFromResource(AC_INSTANTIATION_UPDATE_JSON, "Crud");
-        publisher.send(automationComposition, 0, UUID.randomUUID(), UUID.randomUUID());
+        publisher.send(automationComposition, 0, UUID.randomUUID(), UUID.randomUUID(), true);
         verify(topicSink).send(anyString());
     }