Add monitoring implementation for synchronization events in ACM-r 24/140224/4
authorFrancescoFioraEst <francesco.fiora@est.tech>
Fri, 14 Feb 2025 11:35:17 +0000 (11:35 +0000)
committerFrancescoFioraEst <francesco.fiora@est.tech>
Fri, 14 Feb 2025 15:24:04 +0000 (15:24 +0000)
Issue-ID: POLICY-5277
Change-Id: Ie4c8a11b27a110b02972f6dd5a81c0be1ba8de3a
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
14 files changed:
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProvider.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProviderTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AbstractScanner.java [new file with mode: 0644]
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AcDefinitionScanner.java [new file with mode: 0644]
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/PhaseScanner.java [new file with mode: 0644]
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/SimpleScanner.java [new file with mode: 0644]
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScanner.java [new file with mode: 0644]
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/UpdateSync.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerTest.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AcDefinitionScannerTest.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/PhaseScannerTest.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/SimpleScannerTest.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScannerTest.java [new file with mode: 0644]

index bb05c46..6de27f7 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation.
+ *  Copyright (C) 2021-2025 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,6 +51,8 @@ import org.springframework.transaction.annotation.Transactional;
 @RequiredArgsConstructor
 public class AcDefinitionProvider {
 
+    private static final String NAME = "AutomationCompositionDefinition";
+
     private final AutomationCompositionDefinitionRepository acmDefinitionRepository;
     private final NodeTemplateStateRepository nodeTemplateStateRepository;
 
@@ -121,8 +123,8 @@ public class AcDefinitionProvider {
      */
     public void updateAcDefinition(AutomationCompositionDefinition acDefinition, String toscaCompositionName) {
         var jpaAcmDefinition = ProviderUtils.getJpaAndValidate(acDefinition, JpaAutomationCompositionDefinition::new,
-                "AutomationCompositionDefinition");
-        var validationResult = new BeanValidationResult("AutomationCompositionDefinition", acDefinition);
+                NAME);
+        var validationResult = new BeanValidationResult(NAME, acDefinition);
         ToscaServiceTemplateValidation.validate(validationResult, jpaAcmDefinition.getServiceTemplate(),
                 toscaCompositionName);
         if (! validationResult.isValid()) {
@@ -132,6 +134,18 @@ public class AcDefinitionProvider {
         acmDefinitionRepository.flush();
     }
 
+    /**
+     * Update Ac Definition with unchanged service template.
+     *
+     * @param acDefinition the AutomationCompositionDefinition to be updated
+     */
+    public void updateAcDefinitionState(AutomationCompositionDefinition acDefinition) {
+        var jpaAcmDefinition = ProviderUtils.getJpaAndValidate(acDefinition, JpaAutomationCompositionDefinition::new,
+                NAME);
+        acmDefinitionRepository.save(jpaAcmDefinition);
+        acmDefinitionRepository.flush();
+    }
+
     /**
      * Update Ac Definition AcTypeState, StateChangeResult and restarting.
      *
index 9581191..b6fbe09 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation.
+ *  Copyright (C) 2021-2025 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ package org.onap.policy.clamp.models.acm.persistence.provider;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -197,6 +198,10 @@ class AcDefinitionProviderTest {
         acDefinitionProvider.updateAcDefinitionState(acmDefinition.getCompositionId(), AcTypeState.PRIMED,
             StateChangeResult.NO_ERROR);
         verify(acmDefinitionRepository).save(jpa);
+
+        clearInvocations(acmDefinitionRepository);
+        acDefinitionProvider.updateAcDefinitionState(acmDefinition);
+        verify(acmDefinitionRepository).save(any());
     }
 
     @Test
index 9b030ea..3b17565 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation.
+ * Copyright (C) 2021-2025 Nordix Foundation.
  * ================================================================================
  * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
 
 package org.onap.policy.clamp.acm.runtime.supervision;
 
-import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
-import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionDeployPublisher;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionMigrationPublisher;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
-import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
+import lombok.RequiredArgsConstructor;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.AcDefinitionScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.PhaseScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.SimpleScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.StageScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.UpdateSync;
 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.DeployState;
-import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
 import org.onap.policy.clamp.models.acm.concepts.SubState;
 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
-import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,42 +48,16 @@ import org.springframework.stereotype.Component;
  * This class is used to scan the automation compositions in the database and check if they are in the correct state.
  */
 @Component
+@RequiredArgsConstructor
 public class SupervisionScanner {
     private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionScanner.class);
 
-    private final long maxStatusWaitMs;
-
     private final AutomationCompositionProvider automationCompositionProvider;
     private final AcDefinitionProvider acDefinitionProvider;
-    private final AutomationCompositionStateChangePublisher automationCompositionStateChangePublisher;
-    private final AutomationCompositionDeployPublisher automationCompositionDeployPublisher;
-    private final ParticipantSyncPublisher participantSyncPublisher;
-    private final AutomationCompositionMigrationPublisher automationCompositionMigrationPublisher;
-
-    /**
-     * Constructor for instantiating SupervisionScanner.
-     *
-     * @param automationCompositionProvider the provider to use to read automation compositions from the database
-     * @param acDefinitionProvider the Policy Models Provider
-     * @param automationCompositionStateChangePublisher the AutomationComposition StateChange Publisher
-     * @param automationCompositionDeployPublisher the AutomationCompositionUpdate Publisher
-     * @param acRuntimeParameterGroup the parameters for the automation composition runtime
-     */
-    public SupervisionScanner(final AutomationCompositionProvider automationCompositionProvider,
-            final AcDefinitionProvider acDefinitionProvider,
-            final AutomationCompositionStateChangePublisher automationCompositionStateChangePublisher,
-            final AutomationCompositionDeployPublisher automationCompositionDeployPublisher,
-            final ParticipantSyncPublisher participantSyncPublisher,
-            final AutomationCompositionMigrationPublisher automationCompositionMigrationPublisher,
-            final AcRuntimeParameterGroup acRuntimeParameterGroup) {
-        this.automationCompositionProvider = automationCompositionProvider;
-        this.acDefinitionProvider = acDefinitionProvider;
-        this.automationCompositionStateChangePublisher = automationCompositionStateChangePublisher;
-        this.automationCompositionDeployPublisher = automationCompositionDeployPublisher;
-        this.participantSyncPublisher = participantSyncPublisher;
-        this.automationCompositionMigrationPublisher = automationCompositionMigrationPublisher;
-        this.maxStatusWaitMs = acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs();
-    }
+    private final AcDefinitionScanner acDefinitionScanner;
+    private final StageScanner stageScanner;
+    private final SimpleScanner simpleScanner;
+    private final PhaseScanner phaseScanner;
 
     /**
      * Run Scanning.
@@ -93,251 +65,59 @@ public class SupervisionScanner {
     public void run() {
         LOGGER.debug("Scanning automation compositions in the database . . .");
 
-        var acDefinitionList = acDefinitionProvider.getAllAcDefinitionsInTransition();
-        for (var acDefinition : acDefinitionList) {
-            scanAutomationCompositionDefinition(acDefinition);
+        var acDefinitions = acDefinitionProvider.getAllAcDefinitionsInTransition();
+        for (var acDefinition : acDefinitions) {
+            scanAcDefinition(acDefinition.getCompositionId());
         }
 
-        var acList = automationCompositionProvider.getAcInstancesInTransition();
-        HashMap<UUID, AutomationCompositionDefinition> acDefinitionMap = new HashMap<>();
-        for (var automationComposition : acList) {
-            var compositionId = automationComposition.getCompositionTargetId() != null
-                    ? automationComposition.getCompositionTargetId() : automationComposition.getCompositionId();
-            var acDefinition = acDefinitionMap.computeIfAbsent(compositionId, acDefinitionProvider::getAcDefinition);
-            scanAutomationComposition(automationComposition, acDefinition.getServiceTemplate());
+        var instances = automationCompositionProvider.getAcInstancesInTransition();
+        Map<UUID, AutomationCompositionDefinition> acDefinitionMap = new HashMap<>();
+        for (var instance : instances) {
+            scanAutomationComposition(instance.getInstanceId(), acDefinitionMap);
         }
         LOGGER.debug("Automation composition scan complete . . .");
     }
 
-    private void scanAutomationCompositionDefinition(AutomationCompositionDefinition acDefinition) {
-        if (StateChangeResult.FAILED.equals(acDefinition.getStateChangeResult())) {
-            LOGGER.debug("automation definition {} scanned, OK", acDefinition.getCompositionId());
-            return;
-        }
+    private void scanAcDefinition(UUID compositionId) {
+        var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
+        var updateSync = new UpdateSync();
+        acDefinitionOpt.ifPresent(acDefinition ->
+                acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, updateSync));
+    }
 
-        boolean completed = true;
-        var finalState = AcTypeState.PRIMING.equals(acDefinition.getState())
-            || AcTypeState.PRIMED.equals(acDefinition.getState()) ? AcTypeState.PRIMED : AcTypeState.COMMISSIONED;
-        for (var element : acDefinition.getElementStateMap().values()) {
-            if (!finalState.equals(element.getState())) {
-                completed = false;
-                break;
-            }
-        }
-        if (completed) {
-            acDefinitionProvider.updateAcDefinitionState(acDefinition.getCompositionId(), finalState,
-                StateChangeResult.NO_ERROR);
-            participantSyncPublisher.sendSync(acDefinition, null);
-        } else {
-            handleTimeout(acDefinition);
+    private void scanAutomationComposition(UUID instanceId,
+            Map<UUID, AutomationCompositionDefinition> acDefinitionMap) {
+        var automationCompositionOpt = automationCompositionProvider.findAutomationComposition(instanceId);
+        var updateSync = new UpdateSync();
+        if (automationCompositionOpt.isPresent()) {
+            var automationComposition = automationCompositionOpt.get();
+            var compositionId = automationComposition.getCompositionTargetId() != null
+                    ? automationComposition.getCompositionTargetId() : automationComposition.getCompositionId();
+            var acDefinition = acDefinitionMap.computeIfAbsent(compositionId, acDefinitionProvider::getAcDefinition);
+            scanAutomationComposition(automationComposition, acDefinition.getServiceTemplate(), updateSync);
         }
     }
 
     private void scanAutomationComposition(final AutomationComposition automationComposition,
-            ToscaServiceTemplate serviceTemplate) {
+            ToscaServiceTemplate serviceTemplate, UpdateSync updateSync) {
         LOGGER.debug("scanning automation composition {} . . .", automationComposition.getInstanceId());
 
         if (!AcmUtils.isInTransitionalState(automationComposition.getDeployState(),
                 automationComposition.getLockState(), automationComposition.getSubState())
                 || StateChangeResult.FAILED.equals(automationComposition.getStateChangeResult())) {
             LOGGER.debug("automation composition {} scanned, OK", automationComposition.getInstanceId());
-
-            return;
+            simpleScanner.saveAndSync(automationComposition, updateSync);
         }
 
         if (DeployState.MIGRATING.equals(automationComposition.getDeployState())) {
-            scanStage(automationComposition, serviceTemplate);
+            stageScanner.scanStage(automationComposition, serviceTemplate, updateSync);
         } else if (DeployState.UPDATING.equals(automationComposition.getDeployState())
-                || SubState.PREPARING.equals(automationComposition.getSubState())
                 || SubState.REVIEWING.equals(automationComposition.getSubState())
+                || SubState.PREPARING.equals(automationComposition.getSubState())
                 || SubState.MIGRATION_PRECHECKING.equals(automationComposition.getSubState())) {
-            simpleScan(automationComposition);
-        } else {
-            scanWithPhase(automationComposition, serviceTemplate);
-        }
-    }
-
-    /**
-     * Scan with startPhase: DEPLOY, UNDEPLOY, LOCK and UNLOCK.
-     *
-     * @param automationComposition the AutomationComposition
-     * @param serviceTemplate the ToscaServiceTemplate
-     */
-    private void scanWithPhase(final AutomationComposition automationComposition,
-            ToscaServiceTemplate serviceTemplate) {
-        var completed = true;
-        var minSpNotCompleted = 1000; // min startPhase not completed
-        var maxSpNotCompleted = 0; // max startPhase not completed
-        var defaultMin = 1000; // min startPhase
-        var defaultMax = 0; // max startPhase
-        for (var element : automationComposition.getElements().values()) {
-            var toscaNodeTemplate = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()
-                    .get(element.getDefinition().getName());
-            int startPhase = toscaNodeTemplate != null
-                    && element.getDefinition().getVersion().equals(toscaNodeTemplate.getVersion())
-                    ? ParticipantUtils.findStartPhase(toscaNodeTemplate.getProperties()) : 0;
-            defaultMin = Math.min(defaultMin, startPhase);
-            defaultMax = Math.max(defaultMax, startPhase);
-            if (AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
-                    element.getSubState())) {
-                completed = false;
-                minSpNotCompleted = Math.min(minSpNotCompleted, startPhase);
-                maxSpNotCompleted = Math.max(maxSpNotCompleted, startPhase);
-            }
-        }
-
-        if (completed) {
-            complete(automationComposition);
-        } else {
-            LOGGER.debug("automation composition scan: transition state {} {} not completed",
-                    automationComposition.getDeployState(), automationComposition.getLockState());
-
-            var isForward =
-                    AcmUtils.isForward(automationComposition.getDeployState(), automationComposition.getLockState());
-
-            var nextSpNotCompleted = isForward ? minSpNotCompleted : maxSpNotCompleted;
-
-            if (nextSpNotCompleted != automationComposition.getPhase()) {
-                sendAutomationCompositionMsg(automationComposition, nextSpNotCompleted);
-            } else {
-                handleTimeout(automationComposition);
-            }
-        }
-    }
-
-    /**
-     * Simple scan: UPDATE, PREPARE, REVIEW, MIGRATE_PRECHECKING.
-     *
-     * @param automationComposition the AutomationComposition
-     */
-    private void simpleScan(final AutomationComposition automationComposition) {
-        var completed = automationComposition.getElements().values().stream()
-                .filter(element -> AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
-                        element.getSubState())).findFirst().isEmpty();
-
-        if (completed) {
-            complete(automationComposition);
-        } else {
-            handleTimeout(automationComposition);
-        }
-    }
-
-    /**
-     * Scan with stage: MIGRATE.
-     *
-     * @param automationComposition the AutomationComposition
-     * @param serviceTemplate the ToscaServiceTemplate
-     */
-    private void scanStage(final AutomationComposition automationComposition, ToscaServiceTemplate serviceTemplate) {
-        var completed = true;
-        var minStageNotCompleted = 1000; // min stage not completed
-        for (var element : automationComposition.getElements().values()) {
-            if (AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
-                    element.getSubState())) {
-                var toscaNodeTemplate = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()
-                    .get(element.getDefinition().getName());
-                var stageSet = ParticipantUtils.findStageSet(toscaNodeTemplate.getProperties());
-                var minStage = stageSet.stream().min(Comparator.comparing(Integer::valueOf)).orElse(0);
-                int stage = element.getStage() != null ? element.getStage() : minStage;
-                minStageNotCompleted = Math.min(minStageNotCompleted, stage);
-                completed = false;
-            }
-        }
-
-        if (completed) {
-            complete(automationComposition);
-        } else {
-            LOGGER.debug("automation composition scan: transition from state {} to {} not completed",
-                    automationComposition.getDeployState(), automationComposition.getLockState());
-
-            if (minStageNotCompleted != automationComposition.getPhase()) {
-                savePhase(automationComposition, minStageNotCompleted);
-                LOGGER.debug("retry message AutomationCompositionMigration");
-                automationCompositionMigrationPublisher.send(automationComposition, minStageNotCompleted);
-            } else {
-                handleTimeout(automationComposition);
-            }
-        }
-    }
-
-    private void complete(final AutomationComposition automationComposition) {
-        LOGGER.debug("automation composition scan: transition state {} {} {} completed",
-                automationComposition.getDeployState(), automationComposition.getLockState(),
-                automationComposition.getSubState());
-
-        var deployState = automationComposition.getDeployState();
-        if (DeployState.MIGRATING.equals(automationComposition.getDeployState())) {
-            // migration scenario
-            automationComposition.setCompositionId(automationComposition.getCompositionTargetId());
-            automationComposition.setCompositionTargetId(null);
-        }
-        automationComposition.setDeployState(AcmUtils.deployCompleted(deployState));
-        automationComposition.setLockState(AcmUtils.lockCompleted(deployState, automationComposition.getLockState()));
-        automationComposition.setPhase(null);
-        automationComposition.setSubState(SubState.NONE);
-        automationComposition.setPrecheck(null);
-        if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
-            automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        }
-        var acToUpdate = automationComposition;
-        if (DeployState.DELETED.equals(automationComposition.getDeployState())) {
-            automationCompositionProvider.deleteAutomationComposition(automationComposition.getInstanceId());
-        } else {
-            acToUpdate = automationCompositionProvider.updateAcState(acToUpdate);
-        }
-        participantSyncPublisher.sendSync(acToUpdate);
-    }
-
-    private void handleTimeout(AutomationCompositionDefinition acDefinition) {
-        if (StateChangeResult.TIMEOUT.equals(acDefinition.getStateChangeResult())) {
-            LOGGER.debug("The ac definition is in timeout {}", acDefinition.getCompositionId());
-            return;
-        }
-        var now = TimestampHelper.nowEpochMilli();
-        var lastMsg = TimestampHelper.toEpochMilli(acDefinition.getLastMsg());
-        if ((now - lastMsg) > maxStatusWaitMs) {
-            LOGGER.debug("Report timeout for the ac definition {}", acDefinition.getCompositionId());
-            acDefinition.setStateChangeResult(StateChangeResult.TIMEOUT);
-            acDefinitionProvider.updateAcDefinitionState(acDefinition.getCompositionId(),
-                acDefinition.getState(), acDefinition.getStateChangeResult());
-            participantSyncPublisher.sendSync(acDefinition, null);
-        }
-    }
-
-    private void handleTimeout(AutomationComposition automationComposition) {
-        LOGGER.debug("automation composition scan: transition from state {} to {} {} not completed",
-                automationComposition.getDeployState(), automationComposition.getLockState(),
-                automationComposition.getSubState());
-
-        if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
-            LOGGER.debug("The ac instance is in timeout {}", automationComposition.getInstanceId());
-            return;
-        }
-        var now = TimestampHelper.nowEpochMilli();
-        var lastMsg = TimestampHelper.toEpochMilli(automationComposition.getLastMsg());
-        if ((now - lastMsg) > maxStatusWaitMs) {
-            LOGGER.debug("Report timeout for the ac instance {}", automationComposition.getInstanceId());
-            automationComposition.setStateChangeResult(StateChangeResult.TIMEOUT);
-            automationCompositionProvider.updateAcState(automationComposition);
-            participantSyncPublisher.sendSync(automationComposition);
-        }
-    }
-
-    private void savePhase(AutomationComposition automationComposition, int startPhase) {
-        automationComposition.setLastMsg(TimestampHelper.now());
-        automationComposition.setPhase(startPhase);
-        automationCompositionProvider.updateAcState(automationComposition);
-    }
-
-    private void sendAutomationCompositionMsg(AutomationComposition automationComposition, int startPhase) {
-        savePhase(automationComposition, startPhase);
-
-        if (DeployState.DEPLOYING.equals(automationComposition.getDeployState())) {
-            LOGGER.debug("retry message AutomationCompositionDeploy");
-            automationCompositionDeployPublisher.send(automationComposition, startPhase, false);
+            simpleScanner.simpleScan(automationComposition, updateSync);
         } else {
-            LOGGER.debug("retry message AutomationCompositionStateChange");
-            automationCompositionStateChangePublisher.send(automationComposition, startPhase, false);
+            phaseScanner.scanWithPhase(automationComposition, serviceTemplate, updateSync);
         }
     }
 }
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AbstractScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AbstractScanner.java
new file mode 100644 (file)
index 0000000..136276e
--- /dev/null
@@ -0,0 +1,124 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
+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.DeployState;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.concepts.SubState;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.AcmUtils;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractScanner {
+
+    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractScanner.class);
+
+    protected final long maxStatusWaitMs;
+
+    protected final AutomationCompositionProvider acProvider;
+    private final ParticipantSyncPublisher participantSyncPublisher;
+
+    protected AbstractScanner(final AutomationCompositionProvider acProvider,
+            final ParticipantSyncPublisher participantSyncPublisher,
+            final AcRuntimeParameterGroup acRuntimeParameterGroup) {
+        this.acProvider = acProvider;
+        this.participantSyncPublisher = participantSyncPublisher;
+        this.maxStatusWaitMs = acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs();
+    }
+
+    protected void complete(final AutomationComposition automationComposition, UpdateSync updateSync) {
+        LOGGER.debug("automation composition scan: transition state {} {} {} completed",
+                automationComposition.getDeployState(), automationComposition.getLockState(),
+                automationComposition.getSubState());
+
+        var deployState = automationComposition.getDeployState();
+        if (DeployState.MIGRATING.equals(automationComposition.getDeployState())) {
+            // migration scenario
+            automationComposition.setCompositionId(automationComposition.getCompositionTargetId());
+            automationComposition.setCompositionTargetId(null);
+        }
+        automationComposition.setDeployState(AcmUtils.deployCompleted(deployState));
+        automationComposition.setLockState(AcmUtils.lockCompleted(deployState, automationComposition.getLockState()));
+        automationComposition.setPhase(null);
+        automationComposition.setSubState(SubState.NONE);
+        automationComposition.setPrecheck(null);
+        if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
+            automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
+        }
+        if (DeployState.DELETED.equals(automationComposition.getDeployState())) {
+            updateSync.setToBeDelete(true);
+            updateSync.setUpdated(false);
+        } else {
+            updateSync.setUpdated(true);
+        }
+        updateSync.setToBeSync(true);
+        saveAndSync(automationComposition, updateSync);
+    }
+
+    protected void savePhase(AutomationComposition automationComposition, int startPhase) {
+        automationComposition.setLastMsg(TimestampHelper.now());
+        automationComposition.setPhase(startPhase);
+    }
+
+    protected void handleTimeout(AutomationComposition automationComposition, UpdateSync updateSync) {
+        LOGGER.debug("automation composition scan: transition from state {} to {} {} not completed",
+                automationComposition.getDeployState(), automationComposition.getLockState(),
+                automationComposition.getSubState());
+
+        if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
+            LOGGER.debug("The ac instance is in timeout {}", automationComposition.getInstanceId());
+            saveAndSync(automationComposition, updateSync);
+            return;
+        }
+        var now = TimestampHelper.nowEpochMilli();
+        var lastMsg = TimestampHelper.toEpochMilli(automationComposition.getLastMsg());
+        if ((now - lastMsg) > maxStatusWaitMs) {
+            LOGGER.debug("Report timeout for the ac instance {}", automationComposition.getInstanceId());
+            automationComposition.setStateChangeResult(StateChangeResult.TIMEOUT);
+            updateSync.setUpdated(true);
+            updateSync.setToBeSync(true);
+        }
+        saveAndSync(automationComposition, updateSync);
+    }
+
+    /**
+     * Save AutomationComposition and Sync.
+     *
+     * @param automationComposition the AutomationComposition
+     * @param updateSync the update/sync information
+     */
+    public void saveAndSync(AutomationComposition automationComposition, UpdateSync updateSync) {
+        if (updateSync.isUpdated()) {
+            acProvider.updateAutomationComposition(automationComposition);
+        }
+        if (updateSync.isToBeDelete()) {
+            acProvider.deleteAutomationComposition(automationComposition.getInstanceId());
+        }
+        if (updateSync.isToBeSync()) {
+            participantSyncPublisher.sendSync(automationComposition);
+        }
+    }
+}
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AcDefinitionScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AcDefinitionScanner.java
new file mode 100644 (file)
index 0000000..d3f9726
--- /dev/null
@@ -0,0 +1,166 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
+import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.document.concepts.DocMessage;
+import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AcDefinitionScanner {
+    private static final Logger LOGGER = LoggerFactory.getLogger(AcDefinitionScanner.class);
+
+    private final long maxStatusWaitMs;
+
+    private final AcDefinitionProvider acDefinitionProvider;
+    private final ParticipantSyncPublisher participantSyncPublisher;
+
+    /**
+     * Constructor for instantiating AcDefinitionScanner.
+     *
+     * @param acDefinitionProvider the Policy Models Provider
+     * @param participantSyncPublisher the Participant Sync Publisher
+     * @param acRuntimeParameterGroup the parameters for the automation composition runtime
+     */
+    public AcDefinitionScanner(final AcDefinitionProvider acDefinitionProvider,
+            final ParticipantSyncPublisher participantSyncPublisher,
+            final AcRuntimeParameterGroup acRuntimeParameterGroup) {
+        this.acDefinitionProvider = acDefinitionProvider;
+        this.participantSyncPublisher = participantSyncPublisher;
+        this.maxStatusWaitMs = acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs();
+    }
+
+    private UpdateSync handlePrimeAckElement(DocMessage message, AutomationCompositionDefinition acDefinition) {
+        var result = new UpdateSync();
+        if (StateChangeResult.FAILED.equals(message.getStateChangeResult())) {
+            acDefinition.setStateChangeResult(StateChangeResult.FAILED);
+            result.setUpdated(true);
+            result.setToBeSync(true);
+        }
+        for (var element : acDefinition.getElementStateMap().values()) {
+            if (message.getParticipantId().equals(element.getParticipantId())) {
+                element.setMessage(message.getMessage());
+                element.setState(message.getCompositionState());
+                result.setUpdated(true);
+            }
+        }
+        return result;
+    }
+
+    private UpdateSync handleOutProperties(DocMessage message, AutomationCompositionDefinition acDefinition) {
+        var elementOpt = acDefinition.getElementStateMap().values().stream()
+                .filter(element -> element.getNodeTemplateId().equals(message.getAcElementDefinitionId())).findFirst();
+
+        var result = new UpdateSync();
+        if (elementOpt.isPresent()) {
+            elementOpt.get().setOutProperties(message.getOutProperties());
+            result.setUpdated(true);
+            result.setToBeSync(true);
+        }
+        return result;
+    }
+
+    /**
+     * Scan Message.
+     *
+     * @param acDefinition the AutomationComposition Definition
+     * @param message the message
+     */
+    public UpdateSync scanMessage(AutomationCompositionDefinition acDefinition, DocMessage message) {
+        return switch (message.getMessageType()) {
+            case PARTICIPANT_STATUS -> handleOutProperties(message, acDefinition);
+            case PARTICIPANT_PRIME_ACK -> handlePrimeAckElement(message, acDefinition);
+            default -> {
+                LOGGER.debug("Not valid MessageType {}", message.getMessageType());
+                yield new UpdateSync();
+            }
+        };
+    }
+
+    /**
+     * Scan an AutomationComposition Definition.
+     *
+     * @param acDefinition the AutomationComposition Definition
+     * @param updateSync defines if true if the composition has to be saved or sync
+     */
+    public void scanAutomationCompositionDefinition(AutomationCompositionDefinition acDefinition,
+            UpdateSync updateSync) {
+        if (StateChangeResult.FAILED.equals(acDefinition.getStateChangeResult())) {
+            LOGGER.debug("automation definition {} scanned, OK", acDefinition.getCompositionId());
+            updateAcDefinitionState(acDefinition, updateSync);
+            return;
+        }
+
+        boolean completed = true;
+        var finalState = AcTypeState.PRIMING.equals(acDefinition.getState())
+                || AcTypeState.PRIMED.equals(acDefinition.getState()) ? AcTypeState.PRIMED : AcTypeState.COMMISSIONED;
+        for (var element : acDefinition.getElementStateMap().values()) {
+            if (!finalState.equals(element.getState())) {
+                completed = false;
+                break;
+            }
+        }
+        if (completed) {
+            acDefinition.setState(finalState);
+            acDefinition.setStateChangeResult(StateChangeResult.NO_ERROR);
+            updateSync.setUpdated(true);
+            updateSync.setToBeSync(true);
+        } else {
+            updateSync.or(handleTimeout(acDefinition));
+        }
+        updateAcDefinitionState(acDefinition, updateSync);
+    }
+
+    private UpdateSync handleTimeout(AutomationCompositionDefinition acDefinition) {
+        var result = new UpdateSync();
+        if (StateChangeResult.TIMEOUT.equals(acDefinition.getStateChangeResult())) {
+            LOGGER.debug("The ac definition is in timeout {}", acDefinition.getCompositionId());
+            return result;
+        }
+        var now = TimestampHelper.nowEpochMilli();
+        var lastMsg = TimestampHelper.toEpochMilli(acDefinition.getLastMsg());
+        if ((now - lastMsg) > maxStatusWaitMs) {
+            LOGGER.debug("Report timeout for the ac definition {}", acDefinition.getCompositionId());
+            acDefinition.setStateChangeResult(StateChangeResult.TIMEOUT);
+            result.setUpdated(true);
+            result.setToBeSync(true);
+        }
+        return result;
+    }
+
+    private void updateAcDefinitionState(AutomationCompositionDefinition acDefinition,
+            UpdateSync updateSync) {
+        if (updateSync.isUpdated()) {
+            acDefinitionProvider.updateAcDefinitionState(acDefinition);
+        }
+        if (updateSync.isToBeSync()) {
+            participantSyncPublisher.sendSync(acDefinition, null);
+        }
+    }
+}
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/PhaseScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/PhaseScanner.java
new file mode 100644 (file)
index 0000000..de56040
--- /dev/null
@@ -0,0 +1,124 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionDeployPublisher;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher;
+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.DeployState;
+import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.AcmUtils;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PhaseScanner extends AbstractScanner {
+
+    private final AutomationCompositionStateChangePublisher acStateChangePublisher;
+    private final AutomationCompositionDeployPublisher acDeployPublisher;
+
+
+    /**
+     * Constructor for instantiating PhaseScanner.
+     *
+     * @param acProvider the provider to use to read automation compositions from the database
+     * @param participantSyncPublisher the Participant Sync Publisher
+     * @param acStateChangePublisher the automation composition StateChange Publisher
+     * @param acDeployPublisher the automation composition Deploy Publisher
+     * @param acRuntimeParameterGroup the parameters for the automation composition runtime
+     */
+    public PhaseScanner(final AutomationCompositionProvider acProvider,
+            final ParticipantSyncPublisher participantSyncPublisher,
+            final AutomationCompositionStateChangePublisher acStateChangePublisher,
+            final AutomationCompositionDeployPublisher acDeployPublisher,
+            final AcRuntimeParameterGroup acRuntimeParameterGroup) {
+        super(acProvider, participantSyncPublisher, acRuntimeParameterGroup);
+        this.acStateChangePublisher = acStateChangePublisher;
+        this.acDeployPublisher = acDeployPublisher;
+    }
+
+    /**
+     * Scan with startPhase: DEPLOY, UNDEPLOY, LOCK and UNLOCK.
+     *
+     * @param automationComposition the AutomationComposition
+     * @param serviceTemplate the ToscaServiceTemplate
+     * @param updateSync the update/sync information
+     */
+    public void scanWithPhase(final AutomationComposition automationComposition,
+            ToscaServiceTemplate serviceTemplate, UpdateSync updateSync) {
+        var completed = true;
+        var minSpNotCompleted = 1000; // min startPhase not completed
+        var maxSpNotCompleted = 0; // max startPhase not completed
+        var defaultMin = 1000; // min startPhase
+        var defaultMax = 0; // max startPhase
+        for (var element : automationComposition.getElements().values()) {
+            var toscaNodeTemplate = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()
+                    .get(element.getDefinition().getName());
+            int startPhase = toscaNodeTemplate != null
+                    && element.getDefinition().getVersion().equals(toscaNodeTemplate.getVersion())
+                    ? ParticipantUtils.findStartPhase(toscaNodeTemplate.getProperties()) : 0;
+            defaultMin = Math.min(defaultMin, startPhase);
+            defaultMax = Math.max(defaultMax, startPhase);
+            if (AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
+                    element.getSubState())) {
+                completed = false;
+                minSpNotCompleted = Math.min(minSpNotCompleted, startPhase);
+                maxSpNotCompleted = Math.max(maxSpNotCompleted, startPhase);
+            }
+        }
+
+        if (completed) {
+            complete(automationComposition, updateSync);
+        } else {
+            LOGGER.debug("automation composition scan: transition state {} {} not completed",
+                    automationComposition.getDeployState(), automationComposition.getLockState());
+
+            var isForward =
+                    AcmUtils.isForward(automationComposition.getDeployState(), automationComposition.getLockState());
+
+            var nextSpNotCompleted = isForward ? minSpNotCompleted : maxSpNotCompleted;
+
+            if (nextSpNotCompleted != automationComposition.getPhase()) {
+                sendAutomationCompositionMsg(automationComposition, nextSpNotCompleted, updateSync);
+            } else {
+                handleTimeout(automationComposition, updateSync);
+            }
+        }
+    }
+
+    private void sendAutomationCompositionMsg(AutomationComposition automationComposition, int startPhase,
+            UpdateSync updateSync) {
+        savePhase(automationComposition, startPhase);
+        updateSync.setUpdated(true);
+        saveAndSync(automationComposition, updateSync);
+
+        if (DeployState.DEPLOYING.equals(automationComposition.getDeployState())) {
+            LOGGER.debug("retry message AutomationCompositionDeploy");
+            acDeployPublisher.send(automationComposition, startPhase, false);
+        } else {
+            LOGGER.debug("retry message AutomationCompositionStateChange");
+            acStateChangePublisher.send(automationComposition, startPhase, false);
+        }
+    }
+}
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/SimpleScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/SimpleScanner.java
new file mode 100644 (file)
index 0000000..e35d5f0
--- /dev/null
@@ -0,0 +1,123 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
+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.StateChangeResult;
+import org.onap.policy.clamp.models.acm.concepts.SubState;
+import org.onap.policy.clamp.models.acm.document.concepts.DocMessage;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.AcmUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SimpleScanner extends AbstractScanner {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleScanner.class);
+
+    /**
+     * Constructor for instantiating SimpleScanner.
+     *
+     * @param acProvider the provider to use to read automation compositions from the database
+     * @param participantSyncPublisher the Participant Sync Publisher
+     * @param acRuntimeParameterGroup the parameters for the automation composition runtime
+     */
+    public SimpleScanner(final AutomationCompositionProvider acProvider,
+            final ParticipantSyncPublisher participantSyncPublisher,
+            final AcRuntimeParameterGroup acRuntimeParameterGroup) {
+        super(acProvider, participantSyncPublisher, acRuntimeParameterGroup);
+    }
+
+    /**
+     * Scan Message.
+     *
+     * @param automationComposition the AutomationComposition
+     * @param message the message
+     * @return the update/sync information
+     */
+    public UpdateSync scanMessage(AutomationComposition automationComposition, DocMessage message) {
+        return switch (message.getMessageType()) {
+            case PARTICIPANT_STATUS -> handleOutProperties(automationComposition, message);
+            case AUTOMATION_COMPOSITION_DEPLOY_ACK, AUTOMATION_COMPOSITION_STATECHANGE_ACK
+                    -> handleAcStateChange(automationComposition, message);
+            default -> {
+                LOGGER.debug("Not valid MessageType {}", message.getMessageType());
+                yield new UpdateSync();
+            }
+        };
+    }
+
+    private UpdateSync handleAcStateChange(AutomationComposition automationComposition, DocMessage message) {
+        var result = new UpdateSync();
+        if (StateChangeResult.FAILED.equals(message.getStateChangeResult())) {
+            automationComposition.setStateChangeResult(StateChangeResult.FAILED);
+            result.setUpdated(true);
+            result.setToBeSync(true);
+        }
+        var element = automationComposition.getElements().get(message.getInstanceElementId());
+        if (element != null) {
+            element.setDeployState(message.getDeployState());
+            element.setLockState(message.getLockState());
+            if (element.getStage() == null) {
+                element.setSubState(SubState.NONE);
+            }
+            element.setStage(element.getStage());
+            element.setMessage(message.getMessage());
+            result.setUpdated(true);
+        }
+        return result;
+    }
+
+    private UpdateSync handleOutProperties(AutomationComposition automationComposition, DocMessage message) {
+        var element = automationComposition.getElements().get(message.getInstanceElementId());
+        var result = new UpdateSync();
+        if (element != null) {
+            element.setOutProperties(message.getOutProperties());
+            element.setOperationalState(message.getOperationalState());
+            element.setUseState(message.getUseState());
+            result.setUpdated(true);
+            result.setToBeSync(true);
+        }
+        return result;
+    }
+
+    /**
+     * Simple scan: UPDATE, PREPARE, REVIEW, MIGRATE_PRECHECKING.
+     *
+     * @param automationComposition the AutomationComposition
+     * @param updateSync the update/sync information
+     */
+    public void simpleScan(final AutomationComposition automationComposition, UpdateSync updateSync) {
+        var completed = automationComposition.getElements().values().stream()
+                .filter(element -> AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
+                        element.getSubState())).findFirst().isEmpty();
+
+        if (completed) {
+            complete(automationComposition, updateSync);
+        } else {
+            handleTimeout(automationComposition, updateSync);
+        }
+    }
+}
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScanner.java
new file mode 100644 (file)
index 0000000..18717cc
--- /dev/null
@@ -0,0 +1,96 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import java.util.Comparator;
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionMigrationPublisher;
+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.ParticipantUtils;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.AcmUtils;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class StageScanner extends AbstractScanner {
+
+    private final AutomationCompositionMigrationPublisher acMigrationPublisher;
+
+    /**
+     * Constructor for instantiating StageScanner.
+     *
+     * @param acProvider the provider to use to read automation compositions from the database
+     * @param participantSyncPublisher the Participant Sync Publisher
+     * @param acMigrationPublisher the AutomationComposition Migration Publisher
+     * @param acRuntimeParameterGroup the parameters for the automation composition runtime
+     */
+    public StageScanner(final AutomationCompositionProvider acProvider,
+            final ParticipantSyncPublisher participantSyncPublisher,
+            final AutomationCompositionMigrationPublisher acMigrationPublisher,
+            final AcRuntimeParameterGroup acRuntimeParameterGroup) {
+        super(acProvider, participantSyncPublisher, acRuntimeParameterGroup);
+        this.acMigrationPublisher = acMigrationPublisher;
+    }
+
+    /**
+     * Scan with stage: MIGRATE.
+     *
+     * @param automationComposition the AutomationComposition
+     * @param serviceTemplate the ToscaServiceTemplate
+     * @param updateSync the update/sync information
+     */
+    public void scanStage(final AutomationComposition automationComposition, ToscaServiceTemplate serviceTemplate,
+            UpdateSync updateSync) {
+        var completed = true;
+        var minStageNotCompleted = 1000; // min stage not completed
+        for (var element : automationComposition.getElements().values()) {
+            if (AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState(),
+                    element.getSubState())) {
+                var toscaNodeTemplate = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()
+                        .get(element.getDefinition().getName());
+                var stageSet = ParticipantUtils.findStageSet(toscaNodeTemplate.getProperties());
+                var minStage = stageSet.stream().min(Comparator.comparing(Integer::valueOf)).orElse(0);
+                int stage = element.getStage() != null ? element.getStage() : minStage;
+                minStageNotCompleted = Math.min(minStageNotCompleted, stage);
+                completed = false;
+            }
+        }
+
+        if (completed) {
+            complete(automationComposition, updateSync);
+        } else {
+            LOGGER.debug("automation composition scan: transition from state {} to {} not completed",
+                    automationComposition.getDeployState(), automationComposition.getLockState());
+
+            if (minStageNotCompleted != automationComposition.getPhase()) {
+                savePhase(automationComposition, minStageNotCompleted);
+                updateSync.setUpdated(true);
+                saveAndSync(automationComposition, updateSync);
+                LOGGER.debug("retry message AutomationCompositionMigration");
+                acMigrationPublisher.send(automationComposition, minStageNotCompleted);
+            } else {
+                handleTimeout(automationComposition, updateSync);
+            }
+        }
+    }
+}
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/UpdateSync.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/UpdateSync.java
new file mode 100644 (file)
index 0000000..e6daa0c
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import lombok.Data;
+
+@Data
+public class UpdateSync {
+    private boolean toBeDelete = false;
+    private boolean updated = false;
+    private boolean toBeSync = false;
+
+    /**
+     * Or operator with other update/sync information.
+     *
+     * @param updateSync the update/sync information
+     */
+    public void or(UpdateSync updateSync) {
+        this.updated |= updateSync.updated;
+        this.toBeSync |= updateSync.toBeSync;
+        this.toBeDelete |= updateSync.toBeDelete;
+    }
+}
index ddb8a33..a555d82 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation.
+ *  Copyright (C) 2021-2025 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.policy.clamp.acm.runtime.supervision;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -34,18 +31,18 @@ import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_SERVIC
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.UUID;
-import java.util.function.Consumer;
 import org.junit.jupiter.api.Test;
 import org.onap.policy.clamp.acm.runtime.instantiation.InstantiationUtils;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionDeployPublisher;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher;
-import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
-import org.onap.policy.clamp.acm.runtime.util.CommonTestData;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.AcDefinitionScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.PhaseScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.SimpleScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.StageScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.UpdateSync;
 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
 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.LockState;
 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
@@ -58,8 +55,6 @@ import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
 class SupervisionScannerTest {
 
     private static final String AC_JSON = "src/test/resources/rest/acm/AutomationCompositionSmoke.json";
-    private static final String ELEMENT_NAME =
-            "org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement";
 
     private static final UUID compositionId = UUID.randomUUID();
 
@@ -71,7 +66,7 @@ class SupervisionScannerTest {
         acDefinition.setStateChangeResult(stateChangeResult);
         acDefinition.setCompositionId(compositionId);
         acDefinition.setLastMsg(TimestampHelper.now());
-        acDefinition.setServiceTemplate(serviceTemplate);
+        acDefinition.setServiceTemplate(Objects.requireNonNull(serviceTemplate));
         var node = new NodeTemplateState();
         node.setState(AcTypeState.PRIMING);
         node.setNodeTemplateStateId(UUID.randomUUID());
@@ -83,8 +78,11 @@ class SupervisionScannerTest {
         var acDefinitionProvider = mock(AcDefinitionProvider.class);
         var acTypeState = acDefinition.getState();
         if (AcTypeState.PRIMING.equals(acTypeState) || AcTypeState.DEPRIMING.equals(acTypeState)) {
-            when(acDefinitionProvider.getAllAcDefinitionsInTransition())
-                .thenReturn(List.of(Objects.requireNonNull(acDefinition)));
+            when(acDefinitionProvider.getAllAcDefinitionsInTransition()).thenReturn(List.of(acDefinition));
+            when(acDefinitionProvider.getAcDefinition(acDefinition.getCompositionId()))
+                    .thenReturn(Objects.requireNonNull(acDefinition));
+            when(acDefinitionProvider.findAcDefinition(acDefinition.getCompositionId()))
+                    .thenReturn(Optional.of(Objects.requireNonNull(acDefinition)));
         }
         when(acDefinitionProvider.getAcDefinition(compositionId)).thenReturn(acDefinition);
         return acDefinitionProvider;
@@ -100,276 +98,65 @@ class SupervisionScannerTest {
     }
 
     @Test
-    void testAcDefinitionPrimeFailed() {
-        var acDefinitionProvider = createAcDefinitionProvider(AcTypeState.PRIMING, StateChangeResult.FAILED);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+    void testAcDefinition() {
+        var acDefinitionProvider = createAcDefinitionProvider(AcTypeState.PRIMING, StateChangeResult.NO_ERROR);
+        var acDefinitionScanner = mock(AcDefinitionScanner.class);
         var supervisionScanner = new SupervisionScanner(mock(AutomationCompositionProvider.class), acDefinitionProvider,
-                mock(AutomationCompositionStateChangePublisher.class), mock(AutomationCompositionDeployPublisher.class),
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
+                acDefinitionScanner, mock(StageScanner.class), mock(SimpleScanner.class), mock(PhaseScanner.class));
         supervisionScanner.run();
-        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any(), any(), any());
-    }
-
-    @Test
-    void testAcDefinitionPrimeTimeout() {
-        var acDefinition = createAutomationCompositionDefinition(AcTypeState.PRIMING, StateChangeResult.NO_ERROR);
-        var acDefinitionProvider = createAcDefinitionProvider(acDefinition);
-        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-        var supervisionScanner = new SupervisionScanner(mock(AutomationCompositionProvider.class), acDefinitionProvider,
-                mock(AutomationCompositionStateChangePublisher.class), mock(AutomationCompositionDeployPublisher.class),
-                participantSyncPublisher, null, acRuntimeParameterGroup);
-        supervisionScanner.run();
-        // Ac Definition in Priming state
-        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any(), any(), any());
-
-        acRuntimeParameterGroup.getParticipantParameters().setMaxStatusWaitMs(-1);
-        supervisionScanner = new SupervisionScanner(mock(AutomationCompositionProvider.class), acDefinitionProvider,
-                mock(AutomationCompositionStateChangePublisher.class), mock(AutomationCompositionDeployPublisher.class),
-                participantSyncPublisher, null, acRuntimeParameterGroup);
-        supervisionScanner.run();
-        // set Timeout
-        verify(acDefinitionProvider).updateAcDefinitionState(acDefinition.getCompositionId(), acDefinition.getState(),
-            StateChangeResult.TIMEOUT);
-        verify(participantSyncPublisher).sendSync(any(AutomationCompositionDefinition.class), any());
-
-        clearInvocations(acDefinitionProvider);
-        clearInvocations(participantSyncPublisher);
-        acDefinition.setStateChangeResult(StateChangeResult.TIMEOUT);
-        supervisionScanner.run();
-        // already in Timeout
-        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any(), any(), any());
-        verify(participantSyncPublisher, times(0)).sendSync(acDefinition, null);
-
-        clearInvocations(acDefinitionProvider);
-        clearInvocations(participantSyncPublisher);
-        // retry by the user
-        acDefinition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        supervisionScanner.run();
-        // set Timeout
-        verify(acDefinitionProvider).updateAcDefinitionState(acDefinition.getCompositionId(), acDefinition.getState(),
-            StateChangeResult.TIMEOUT);
-        verify(participantSyncPublisher).sendSync(any(AutomationCompositionDefinition.class), any());
-
-        clearInvocations(acDefinitionProvider);
-        for (var element : acDefinition.getElementStateMap().values()) {
-            element.setState(AcTypeState.PRIMED);
-        }
-        supervisionScanner.run();
-        // completed
-        verify(acDefinitionProvider).updateAcDefinitionState(acDefinition.getCompositionId(), AcTypeState.PRIMED,
-            StateChangeResult.NO_ERROR);
+        verify(acDefinitionScanner).scanAutomationCompositionDefinition(any(), any());
     }
 
     @Test
     void testAcNotInTransitionOrFailed() {
         var automationCompositionProvider = mock(AutomationCompositionProvider.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
 
         var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setCompositionId(compositionId);
+        automationComposition.setCompositionId(Objects.requireNonNull(compositionId));
         when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
 
+        var stageScanner = mock(StageScanner.class);
+        var simpleScanner = mock(SimpleScanner.class);
+        var phaseScanner = mock(PhaseScanner.class);
         var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
+                mock(AcDefinitionScanner.class), stageScanner, simpleScanner, phaseScanner);
 
         // not in transition
         supervisionScanner.run();
-        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
+        verify(stageScanner, times(0)).scanStage(any(), any(), any());
+        verify(simpleScanner, times(0)).simpleScan(any(), any());
+        verify(phaseScanner, times(0)).scanWithPhase(any(), any(), any());
 
         automationComposition.setDeployState(DeployState.DEPLOYING);
         automationComposition.setStateChangeResult(StateChangeResult.FAILED);
         supervisionScanner.run();
         // failed
-        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
-    }
-
-    @Test
-    void testAcUndeployCompleted() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setDeployState(DeployState.UNDEPLOYING);
-        automationComposition.setLockState(LockState.NONE);
-        automationComposition.setCompositionId(compositionId);
-        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
-        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-        when(automationCompositionProvider.updateAcState(any())).thenReturn(automationComposition);
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-
-        var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
-        supervisionScanner.run();
-
-        verify(automationCompositionProvider).updateAcState(any(AutomationComposition.class));
-    }
-
-    @Test
-    void testAcDeleted() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setDeployState(DeployState.DELETING);
-        automationComposition.setLockState(LockState.NONE);
-        automationComposition.setCompositionId(compositionId);
-        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
-        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-
-        var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
-        supervisionScanner.run();
-
-        verify(automationCompositionProvider).deleteAutomationComposition(automationComposition.getInstanceId());
+        verify(stageScanner, times(0)).scanStage(any(), any(), any());
+        verify(simpleScanner, times(0)).simpleScan(any(), any());
+        verify(phaseScanner, times(0)).scanWithPhase(any(), any(), any());
     }
 
     @Test
     void testScanner() {
-        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
         var automationComposition = new AutomationComposition();
         automationComposition.setCompositionId(compositionId);
-        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-
-        var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
-
-        supervisionScanner.run();
-        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
-    }
-
-    @Test
-    void testScannerForTimeout() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
         automationComposition.setDeployState(DeployState.DEPLOYING);
-        automationComposition.setLockState(LockState.NONE);
-        automationComposition.setPhase(0);
-        automationComposition.setCompositionId(compositionId);
-        for (var entry : automationComposition.getElements().entrySet()) {
-            entry.getValue().setDeployState(DeployState.DEPLOYING);
-        }
-        // the first element is already completed
-        automationComposition.getElements().entrySet().iterator().next().getValue()
-                .setDeployState(DeployState.DEPLOYED);
-
         var automationCompositionProvider = mock(AutomationCompositionProvider.class);
         when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-        when(automationCompositionProvider.updateAcState(any())).thenReturn(automationComposition);
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-        acRuntimeParameterGroup.getParticipantParameters().setMaxStatusWaitMs(-1);
+        when(automationCompositionProvider.findAutomationComposition(automationComposition.getInstanceId()))
+                .thenReturn(Optional.of(automationComposition));
 
-        // verify timeout scenario
-        var scannerObj2 = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                participantSyncPublisher, null, acRuntimeParameterGroup);
-
-        automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        automationComposition.setLastMsg(TimestampHelper.now());
-        scannerObj2.run();
-        verify(automationCompositionProvider).updateAcState(any(AutomationComposition.class));
-        verify(participantSyncPublisher).sendSync(any(AutomationComposition.class));
-        assertEquals(StateChangeResult.TIMEOUT, automationComposition.getStateChangeResult());
-
-        //already in TIMEOUT
-        clearInvocations(automationCompositionProvider);
-        clearInvocations(participantSyncPublisher);
-        scannerObj2.run();
-        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
-        verify(participantSyncPublisher, times(0))
-                .sendSync(any(AutomationComposition.class));
-
-        clearInvocations(automationCompositionProvider);
-        clearInvocations(participantSyncPublisher);
-        for (Map.Entry<UUID, AutomationCompositionElement> entry : automationComposition.getElements().entrySet()) {
-            entry.getValue().setDeployState(DeployState.DEPLOYED);
-        }
-        scannerObj2.run();
-        verify(automationCompositionProvider).updateAcState(any(AutomationComposition.class));
-        verify(participantSyncPublisher).sendSync(any(AutomationComposition.class));
-        assertEquals(StateChangeResult.NO_ERROR, automationComposition.getStateChangeResult());
-    }
-
-    @Test
-    void testSendAutomationCompositionMsgStartPhase() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setDeployState(DeployState.DEPLOYING);
-        automationComposition.setLockState(LockState.NONE);
-        automationComposition.setPhase(0);
-        automationComposition.setCompositionId(compositionId);
-        for (var element : automationComposition.getElements().values()) {
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setDeployState(DeployState.DEPLOYING);
-                element.setLockState(LockState.NONE);
-            } else {
-                element.setDeployState(DeployState.DEPLOYED);
-                element.setLockState(LockState.LOCKED);
-            }
-        }
-
-        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
-        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var stageScanner = mock(StageScanner.class);
+        var simpleScanner = mock(SimpleScanner.class);
+        var phaseScanner = mock(PhaseScanner.class);
 
         var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
+                mock(AcDefinitionScanner.class), stageScanner, simpleScanner, phaseScanner);
 
         supervisionScanner.run();
-
-        verify(automationCompositionDeployPublisher).send(any(AutomationComposition.class), anyInt(), anyBoolean());
-    }
-
-    @Test
-    void testStartPhaseWithNull() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setDeployState(DeployState.DEPLOYING);
-        automationComposition.setLockState(LockState.NONE);
-        automationComposition.setPhase(0);
-        automationComposition.setLastMsg(TimestampHelper.now());
-        automationComposition.setCompositionId(compositionId);
-        for (var element : automationComposition.getElements().values()) {
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setDeployState(DeployState.DEPLOYING);
-                element.getDefinition().setName("NotExistElement");
-                element.setLockState(LockState.NONE);
-            } else {
-                element.setDeployState(DeployState.DEPLOYING);
-                element.getDefinition().setVersion("0.0.0");
-                element.setLockState(LockState.NONE);
-            }
-        }
-
-        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
-        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-
-        var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                mock(AutomationCompositionStateChangePublisher.class), automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
-
-        supervisionScanner.run();
-
-        verify(automationCompositionDeployPublisher, times(0))
-                .send(any(AutomationComposition.class), anyInt(), anyBoolean());
+        verify(stageScanner, times(0)).scanStage(any(), any(), any());
+        verify(simpleScanner, times(0)).simpleScan(any(), any());
+        verify(phaseScanner).scanWithPhase(any(), any(), any());
     }
 
     @Test
@@ -386,159 +173,62 @@ class SupervisionScannerTest {
             element.setDeployState(DeployState.DEPLOYED);
             element.setLockState(LockState.LOCKED);
         }
-        // first element is not migrated yet
-        automationComposition.getElements().entrySet().iterator().next().getValue()
-                .setDeployState(DeployState.MIGRATING);
 
         var automationCompositionProvider = mock(AutomationCompositionProvider.class);
         when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-        when(automationCompositionProvider.updateAcState(any())).thenReturn(automationComposition);
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        when(automationCompositionProvider.findAutomationComposition(automationComposition.getInstanceId()))
+                .thenReturn(Optional.of(automationComposition));
 
         var definitionTarget = createAutomationCompositionDefinition(AcTypeState.PRIMED, StateChangeResult.NO_ERROR);
         definitionTarget.setCompositionId(compositionTargetId);
         var acDefinitionProvider = createAcDefinitionProvider();
         when(acDefinitionProvider.getAcDefinition(compositionTargetId)).thenReturn(definitionTarget);
+        var stageScanner = mock(StageScanner.class);
 
         var supervisionScanner = new SupervisionScanner(automationCompositionProvider, acDefinitionProvider,
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
+                mock(AcDefinitionScanner.class), stageScanner, mock(SimpleScanner.class), mock(PhaseScanner.class));
 
         supervisionScanner.run();
-        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
-        assertEquals(DeployState.MIGRATING, automationComposition.getDeployState());
-
-        // first element is migrated
-        automationComposition.getElements().entrySet().iterator().next().getValue()
-                .setDeployState(DeployState.DEPLOYED);
-        supervisionScanner.run();
-        verify(automationCompositionProvider, times(1)).updateAcState(any(AutomationComposition.class));
-
-        assertEquals(DeployState.DEPLOYED, automationComposition.getDeployState());
-        assertEquals(compositionTargetId, automationComposition.getCompositionId());
+        verify(stageScanner).scanStage(automationComposition, definitionTarget.getServiceTemplate(),
+                new UpdateSync());
     }
 
     @Test
-    void testSendAutomationCompositionUpdate() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setLockState(LockState.LOCKED);
-        automationComposition.setDeployState(DeployState.UPDATING);
-        for (var element : automationComposition.getElements().values()) {
-            element.setSubState(SubState.NONE);
-            element.setLockState(LockState.LOCKED);
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setDeployState(DeployState.UPDATING);
-            } else {
-                element.setDeployState(DeployState.DEPLOYED);
-            }
-        }
-        testSimpleScan(automationComposition, element -> element.setDeployState(DeployState.DEPLOYED));
-    }
-
-    @Test
-    void testSendAutomationCompositionMigratingPrecheck() {
+    void testSendAutomationCompositionSimpleScan() {
         var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
         automationComposition.setLockState(LockState.LOCKED);
         automationComposition.setDeployState(DeployState.DEPLOYED);
         automationComposition.setSubState(SubState.MIGRATION_PRECHECKING);
-        for (var element : automationComposition.getElements().values()) {
-            element.setDeployState(DeployState.DEPLOYED);
-            element.setSubState(SubState.NONE);
-            element.setLockState(LockState.LOCKED);
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setSubState(SubState.MIGRATION_PRECHECKING);
-            }
-        }
-        testSimpleScan(automationComposition, element -> element.setSubState(SubState.NONE));
-    }
-
-    @Test
-    void testSendAutomationCompositionPrepare() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setLockState(LockState.NONE);
-        automationComposition.setDeployState(DeployState.UNDEPLOYED);
-        automationComposition.setSubState(SubState.PREPARING);
-        for (var element : automationComposition.getElements().values()) {
-            element.setDeployState(DeployState.UNDEPLOYED);
-            element.setSubState(SubState.NONE);
-            element.setLockState(LockState.NONE);
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setSubState(SubState.PREPARING);
-            }
-        }
-        testSimpleScan(automationComposition, element -> element.setSubState(SubState.NONE));
-    }
-
-    @Test
-    void testSendAutomationCompositionReview() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
-        automationComposition.setLockState(LockState.LOCKED);
-        automationComposition.setDeployState(DeployState.DEPLOYED);
-        automationComposition.setSubState(SubState.REVIEWING);
-        for (var element : automationComposition.getElements().values()) {
-            element.setDeployState(DeployState.DEPLOYED);
-            element.setSubState(SubState.NONE);
-            element.setLockState(LockState.LOCKED);
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setSubState(SubState.REVIEWING);
-            }
-        }
-        testSimpleScan(automationComposition, element -> element.setSubState(SubState.NONE));
-    }
-
-    private void testSimpleScan(AutomationComposition automationComposition, Consumer<AutomationCompositionElement> c) {
         automationComposition.setLockState(LockState.NONE);
         automationComposition.setCompositionId(compositionId);
         automationComposition.setLastMsg(TimestampHelper.now());
         var automationCompositionProvider = mock(AutomationCompositionProvider.class);
         when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
+        when(automationCompositionProvider.findAutomationComposition(automationComposition.getInstanceId()))
+                .thenReturn(Optional.of(automationComposition));
 
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var simpleScanner = mock(SimpleScanner.class);
         var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                null, null,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
+                mock(AcDefinitionScanner.class), mock(StageScanner.class), simpleScanner, mock(PhaseScanner.class));
         supervisionScanner.run();
-        verify(automationCompositionProvider, times(0)).updateAcState(any());
+        verify(simpleScanner).simpleScan(automationComposition, new UpdateSync());
 
-        automationComposition.getElements().values().forEach(c);
+        clearInvocations(simpleScanner);
+        automationComposition.setDeployState(DeployState.UNDEPLOYED);
+        automationComposition.setSubState(SubState.PREPARING);
         supervisionScanner.run();
-        verify(automationCompositionProvider).updateAcState(any());
-    }
+        verify(simpleScanner).simpleScan(automationComposition, new UpdateSync());
 
-    @Test
-    void testSendAutomationCompositionMsgUnlocking() {
-        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        clearInvocations(simpleScanner);
         automationComposition.setDeployState(DeployState.DEPLOYED);
-        automationComposition.setLockState(LockState.UNLOCKING);
-        automationComposition.setCompositionId(compositionId);
-        automationComposition.setPhase(0);
-        for (var element : automationComposition.getElements().values()) {
-            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
-                element.setDeployState(DeployState.DEPLOYED);
-                element.setLockState(LockState.UNLOCKING);
-            } else {
-                element.setDeployState(DeployState.DEPLOYED);
-                element.setLockState(LockState.UNLOCKED);
-            }
-        }
-
-        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
-        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
-
-        var automationCompositionDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
-        var automationCompositionStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
-        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
-
-        var supervisionScanner = new SupervisionScanner(automationCompositionProvider, createAcDefinitionProvider(),
-                automationCompositionStateChangePublisher, automationCompositionDeployPublisher,
-                mock(ParticipantSyncPublisher.class), null, acRuntimeParameterGroup);
-
+        automationComposition.setSubState(SubState.REVIEWING);
         supervisionScanner.run();
+        verify(simpleScanner).simpleScan(automationComposition, new UpdateSync());
 
-        verify(automationCompositionStateChangePublisher).send(any(AutomationComposition.class), anyInt(),
-                anyBoolean());
+        clearInvocations(simpleScanner);
+        automationComposition.setDeployState(DeployState.UPDATING);
+        automationComposition.setSubState(SubState.NONE);
+        supervisionScanner.run();
+        verify(simpleScanner).simpleScan(automationComposition, new UpdateSync());
     }
 }
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AcDefinitionScannerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/AcDefinitionScannerTest.java
new file mode 100644 (file)
index 0000000..2e6d282
--- /dev/null
@@ -0,0 +1,242 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_SERVICE_TEMPLATE_YAML;
+
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.instantiation.InstantiationUtils;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
+import org.onap.policy.clamp.acm.runtime.util.CommonTestData;
+import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
+import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.document.concepts.DocMessage;
+import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMessageType;
+import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+
+class AcDefinitionScannerTest {
+
+    private static final UUID COMPOSITION_ID = UUID.randomUUID();
+    private static final Map<String, Object> OUT_PROPERTIES = Map.of("key", "value");
+
+    @Test
+    void testFailScenario() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider,
+                participantSyncPublisher, acRuntimeParameterGroup);
+        var acDefinition = createAutomationCompositionDefinition(AcTypeState.PRIMING, StateChangeResult.NO_ERROR);
+        var element = acDefinition.getElementStateMap().values().iterator().next();
+        var docMessage = new DocMessage();
+        docMessage.setCompositionId(COMPOSITION_ID);
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_PRIME_ACK);
+        docMessage.setStateChangeResult(StateChangeResult.FAILED);
+        docMessage.setCompositionState(AcTypeState.COMMISSIONED);
+        docMessage.setParticipantId(element.getParticipantId());
+        var result = acDefinitionScanner.scanMessage(acDefinition, docMessage);
+        assertTrue(result.isUpdated());
+        assertTrue(result.isToBeSync());
+        assertEquals(docMessage.getCompositionState(),
+                acDefinition.getElementStateMap().get(element.getNodeTemplateStateId().toString()).getState());
+        assertEquals(docMessage.getStateChangeResult(), acDefinition.getStateChangeResult());
+
+    }
+
+    @Test
+    void testWithWrongData() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider,
+                participantSyncPublisher, acRuntimeParameterGroup);
+        var acDefinition = createAutomationCompositionDefinition(AcTypeState.DEPRIMING, StateChangeResult.NO_ERROR);
+        var element = acDefinition.getElementStateMap().values().iterator().next();
+        var docMessage = new DocMessage();
+        docMessage.setCompositionId(COMPOSITION_ID);
+        docMessage.setStateChangeResult(StateChangeResult.NO_ERROR);
+        docMessage.setCompositionState(AcTypeState.COMMISSIONED);
+        docMessage.setParticipantId(element.getParticipantId());
+
+        // wrong MessageType
+        docMessage.setMessageType(ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK);
+        var result = acDefinitionScanner.scanMessage(acDefinition, docMessage);
+        assertFalse(result.isUpdated());
+        assertFalse(result.isToBeSync());
+
+        // wrong elementId in outProperties
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_STATUS);
+        docMessage.setOutProperties(OUT_PROPERTIES);
+        docMessage.setAcElementDefinitionId(new ToscaConceptIdentifier("wrong", "1.0.1"));
+        result = acDefinitionScanner.scanMessage(acDefinition, docMessage);
+        assertFalse(result.isUpdated());
+        assertFalse(result.isToBeSync());
+
+        // wrong participantId in StateChange
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_PRIME_ACK);
+        docMessage.setParticipantId(UUID.randomUUID());
+        result = acDefinitionScanner.scanMessage(acDefinition, docMessage);
+        assertFalse(result.isUpdated());
+        assertFalse(result.isToBeSync());
+    }
+
+    @Test
+    void testScanMessageStateChange() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider,
+                participantSyncPublisher, acRuntimeParameterGroup);
+        var acDefinition = createAutomationCompositionDefinition(AcTypeState.DEPRIMING, StateChangeResult.NO_ERROR);
+        var element = acDefinition.getElementStateMap().values().iterator().next();
+        var docMessage = new DocMessage();
+        docMessage.setCompositionId(COMPOSITION_ID);
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_PRIME_ACK);
+        docMessage.setStateChangeResult(StateChangeResult.NO_ERROR);
+        docMessage.setCompositionState(AcTypeState.COMMISSIONED);
+        docMessage.setParticipantId(element.getParticipantId());
+        var result = acDefinitionScanner.scanMessage(acDefinition, docMessage);
+        assertTrue(result.isUpdated());
+        assertFalse(result.isToBeSync());
+        assertEquals(docMessage.getCompositionState(),
+                acDefinition.getElementStateMap().get(element.getNodeTemplateStateId().toString()).getState());
+    }
+
+    @Test
+    void testScanMessageOutProperties() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider,
+                participantSyncPublisher, acRuntimeParameterGroup);
+        var acDefinition = createAutomationCompositionDefinition(AcTypeState.DEPRIMING, StateChangeResult.NO_ERROR);
+        var element = acDefinition.getElementStateMap().values().iterator().next();
+        var docMessage = new DocMessage();
+        docMessage.setCompositionId(COMPOSITION_ID);
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_STATUS);
+        docMessage.setOutProperties(OUT_PROPERTIES);
+        docMessage.setAcElementDefinitionId(element.getNodeTemplateId());
+        var result = acDefinitionScanner.scanMessage(acDefinition, docMessage);
+        assertTrue(result.isUpdated());
+        assertTrue(result.isToBeSync());
+        assertEquals(docMessage.getOutProperties(),
+                acDefinition.getElementStateMap().get(element.getNodeTemplateStateId().toString()).getOutProperties());
+    }
+
+    private AutomationCompositionDefinition createAutomationCompositionDefinition(AcTypeState acTypeState,
+            StateChangeResult stateChangeResult) {
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        var acDefinition = new AutomationCompositionDefinition();
+        acDefinition.setState(acTypeState);
+        acDefinition.setStateChangeResult(stateChangeResult);
+        acDefinition.setCompositionId(COMPOSITION_ID);
+        acDefinition.setLastMsg(TimestampHelper.now());
+        acDefinition.setServiceTemplate(serviceTemplate);
+        var node = new NodeTemplateState();
+        node.setState(acTypeState);
+        node.setNodeTemplateStateId(UUID.randomUUID());
+        node.setParticipantId(UUID.randomUUID());
+        node.setNodeTemplateId(new ToscaConceptIdentifier("name", "1.0.0"));
+        acDefinition.setElementStateMap(Map.of(node.getNodeTemplateStateId().toString(), node));
+        return acDefinition;
+    }
+
+    @Test
+    void testAcDefinitionPrimeFailed() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider,
+                participantSyncPublisher, acRuntimeParameterGroup);
+        var acDefinition = createAutomationCompositionDefinition(AcTypeState.PRIMING, StateChangeResult.FAILED);
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any());
+        verify(participantSyncPublisher, times(0)).sendSync(any(), any());
+    }
+
+    @Test
+    void testAcDefinitionPrimeTimeout() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider, participantSyncPublisher,
+                acRuntimeParameterGroup);
+        var acDefinition = createAutomationCompositionDefinition(AcTypeState.DEPRIMING, StateChangeResult.NO_ERROR);
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        // Ac Definition in Depriming state
+        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any());
+        verify(participantSyncPublisher, times(0)).sendSync(any(), any());
+
+        acDefinition.setState(AcTypeState.PRIMING);
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        // Ac Definition in Priming state
+        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any());
+        verify(participantSyncPublisher, times(0)).sendSync(any(), any());
+
+        acRuntimeParameterGroup.getParticipantParameters().setMaxStatusWaitMs(-1);
+        acDefinitionScanner = new AcDefinitionScanner(acDefinitionProvider, participantSyncPublisher,
+                acRuntimeParameterGroup);
+        acDefinition = createAutomationCompositionDefinition(AcTypeState.PRIMING, StateChangeResult.NO_ERROR);
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        // set Timeout
+        verify(acDefinitionProvider).updateAcDefinitionState(acDefinition);
+        verify(participantSyncPublisher).sendSync(any(AutomationCompositionDefinition.class), any());
+
+        clearInvocations(acDefinitionProvider);
+        clearInvocations(participantSyncPublisher);
+        acDefinition = createAutomationCompositionDefinition(AcTypeState.PRIMING, StateChangeResult.TIMEOUT);
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        // already in Timeout
+        verify(acDefinitionProvider, times(0)).updateAcDefinitionState(any());
+        verify(participantSyncPublisher, times(0)).sendSync(any(), any());
+
+        clearInvocations(acDefinitionProvider);
+        clearInvocations(participantSyncPublisher);
+        // retry by the user
+        acDefinition.setStateChangeResult(StateChangeResult.NO_ERROR);
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        // set Timeout
+        verify(acDefinitionProvider).updateAcDefinitionState(acDefinition);
+        verify(participantSyncPublisher).sendSync(any(AutomationCompositionDefinition.class), any());
+
+        clearInvocations(acDefinitionProvider);
+        for (var element : acDefinition.getElementStateMap().values()) {
+            element.setState(AcTypeState.PRIMED);
+        }
+        acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, new UpdateSync());
+        // completed
+        verify(acDefinitionProvider).updateAcDefinitionState(acDefinition);
+    }
+}
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/PhaseScannerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/PhaseScannerTest.java
new file mode 100644 (file)
index 0000000..6958fe4
--- /dev/null
@@ -0,0 +1,254 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_SERVICE_TEMPLATE_YAML;
+
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.instantiation.InstantiationUtils;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionDeployPublisher;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
+import org.onap.policy.clamp.acm.runtime.util.CommonTestData;
+import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
+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.LockState;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+
+class PhaseScannerTest {
+
+    private static final String AC_JSON = "src/test/resources/rest/acm/AutomationCompositionSmoke.json";
+    private static final UUID COMPOSITION_ID = UUID.randomUUID();
+    private static final UUID INSTANCE_ID = UUID.randomUUID();
+    private static final String ELEMENT_NAME =
+            "org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement";
+
+    @Test
+    void testAcUndeployCompleted() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.UNDEPLOYING);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
+        when(automationCompositionProvider.updateAutomationComposition(any())).thenReturn(automationComposition);
+
+        var acDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
+        var acStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+
+        var phaseScanner = new PhaseScanner(automationCompositionProvider, mock(ParticipantSyncPublisher.class),
+                acStateChangePublisher, acDeployPublisher, acRuntimeParameterGroup);
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+
+        verify(automationCompositionProvider).updateAutomationComposition(any(AutomationComposition.class));
+    }
+
+    @Test
+    void testAcDeleted() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.DELETING);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
+        var acDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
+        var acStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+
+        var phaseScanner = new PhaseScanner(automationCompositionProvider, mock(ParticipantSyncPublisher.class),
+                acStateChangePublisher, acDeployPublisher, acRuntimeParameterGroup);
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+
+        verify(automationCompositionProvider).deleteAutomationComposition(automationComposition.getInstanceId());
+    }
+
+    @Test
+    void testScannerForTimeout() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.DEPLOYING);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setPhase(0);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        for (var entry : automationComposition.getElements().entrySet()) {
+            entry.getValue().setDeployState(DeployState.DEPLOYING);
+        }
+        // the first element is already completed
+        automationComposition.getElements().entrySet().iterator().next().getValue()
+                .setDeployState(DeployState.DEPLOYED);
+
+        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
+        when(automationCompositionProvider.updateAutomationComposition(any())).thenReturn(automationComposition);
+        var acDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
+        var acStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
+        var participantSyncPublisher = mock(ParticipantSyncPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        acRuntimeParameterGroup.getParticipantParameters().setMaxStatusWaitMs(-1);
+
+        // verify timeout scenario
+        var phaseScanner = new PhaseScanner(automationCompositionProvider, participantSyncPublisher,
+                acStateChangePublisher, acDeployPublisher, acRuntimeParameterGroup);
+
+        automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
+        automationComposition.setLastMsg(TimestampHelper.now());
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+        verify(automationCompositionProvider).updateAutomationComposition(any(AutomationComposition.class));
+        verify(participantSyncPublisher).sendSync(any(AutomationComposition.class));
+        assertEquals(StateChangeResult.TIMEOUT, automationComposition.getStateChangeResult());
+
+        //already in TIMEOUT
+        clearInvocations(automationCompositionProvider);
+        clearInvocations(participantSyncPublisher);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
+        verify(participantSyncPublisher, times(0))
+                .sendSync(any(AutomationComposition.class));
+
+        clearInvocations(automationCompositionProvider);
+        clearInvocations(participantSyncPublisher);
+        for (Map.Entry<UUID, AutomationCompositionElement> entry : automationComposition.getElements().entrySet()) {
+            entry.getValue().setDeployState(DeployState.DEPLOYED);
+        }
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+        verify(automationCompositionProvider).updateAutomationComposition(any(AutomationComposition.class));
+        verify(participantSyncPublisher).sendSync(any(AutomationComposition.class));
+        assertEquals(StateChangeResult.NO_ERROR, automationComposition.getStateChangeResult());
+    }
+
+    @Test
+    void testSendAutomationCompositionMsgStartPhase() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.DEPLOYING);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setPhase(0);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        for (var element : automationComposition.getElements().values()) {
+            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
+                element.setDeployState(DeployState.DEPLOYING);
+                element.setLockState(LockState.NONE);
+            } else {
+                element.setDeployState(DeployState.DEPLOYED);
+                element.setLockState(LockState.LOCKED);
+            }
+        }
+
+        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
+        var acDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
+        var acStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+
+        var phaseScanner = new PhaseScanner(automationCompositionProvider, mock(ParticipantSyncPublisher.class),
+                acStateChangePublisher, acDeployPublisher, acRuntimeParameterGroup);
+
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+
+        verify(acDeployPublisher).send(any(AutomationComposition.class), anyInt(), anyBoolean());
+    }
+
+    @Test
+    void testStartPhaseWithNull() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.DEPLOYING);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setPhase(0);
+        automationComposition.setLastMsg(TimestampHelper.now());
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        for (var element : automationComposition.getElements().values()) {
+            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
+                element.setDeployState(DeployState.DEPLOYING);
+                element.getDefinition().setName("NotExistElement");
+                element.setLockState(LockState.NONE);
+            } else {
+                element.setDeployState(DeployState.DEPLOYING);
+                element.getDefinition().setVersion("0.0.0");
+                element.setLockState(LockState.NONE);
+            }
+        }
+
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var acDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+
+        var phaseScanner = new PhaseScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                mock(AutomationCompositionStateChangePublisher.class), acDeployPublisher,
+                acRuntimeParameterGroup);
+
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+
+        verify(acDeployPublisher, times(0))
+                .send(any(AutomationComposition.class), anyInt(), anyBoolean());
+    }
+
+    @Test
+    void testSendAutomationCompositionMsgUnlocking() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.DEPLOYED);
+        automationComposition.setLockState(LockState.UNLOCKING);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        automationComposition.setPhase(0);
+        for (var element : automationComposition.getElements().values()) {
+            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
+                element.setDeployState(DeployState.DEPLOYED);
+                element.setLockState(LockState.UNLOCKING);
+            } else {
+                element.setDeployState(DeployState.DEPLOYED);
+                element.setLockState(LockState.UNLOCKED);
+            }
+        }
+
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var acDeployPublisher = mock(AutomationCompositionDeployPublisher.class);
+        var acStateChangePublisher = mock(AutomationCompositionStateChangePublisher.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+
+        var phaseScanner = new PhaseScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                acStateChangePublisher, acDeployPublisher, acRuntimeParameterGroup);
+
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        phaseScanner.scanWithPhase(automationComposition, serviceTemplate, new UpdateSync());
+
+        verify(acStateChangePublisher).send(any(AutomationComposition.class), anyInt(),
+                anyBoolean());
+    }
+}
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/SimpleScannerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/SimpleScannerTest.java
new file mode 100644 (file)
index 0000000..b422f13
--- /dev/null
@@ -0,0 +1,246 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.instantiation.InstantiationUtils;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
+import org.onap.policy.clamp.acm.runtime.util.CommonTestData;
+import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
+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.LockState;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.concepts.SubState;
+import org.onap.policy.clamp.models.acm.document.concepts.DocMessage;
+import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMessageType;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+
+class SimpleScannerTest {
+
+    private static final String AC_JSON = "src/test/resources/rest/acm/AutomationCompositionSmoke.json";
+    private static final String ELEMENT_NAME =
+            "org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement";
+
+    private static final UUID COMPOSITION_ID = UUID.randomUUID();
+    private static final UUID INSTANCE_ID = UUID.randomUUID();
+    private static final Map<String, Object> OUT_PROPERTIES = Map.of("key", "value");
+
+    @Test
+    void testFailScenario() {
+        var automationComposition = createDeploying();
+        var elementId = automationComposition.getElements().values().iterator().next().getId();
+        var docMessage = new DocMessage();
+        docMessage.setMessageType(ParticipantMessageType.AUTOMATION_COMPOSITION_DEPLOY_ACK);
+        docMessage.setInstanceId(INSTANCE_ID);
+        docMessage.setInstanceElementId(elementId);
+        docMessage.setDeployState(DeployState.UNDEPLOYED);
+        docMessage.setLockState(LockState.NONE);
+        docMessage.setStateChangeResult(StateChangeResult.FAILED);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var simpleScanner = new SimpleScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                acRuntimeParameterGroup);
+        var result = simpleScanner.scanMessage(automationComposition, docMessage);
+        assertTrue(result.isUpdated());
+        assertTrue(result.isToBeSync());
+        assertEquals(docMessage.getDeployState(),
+                automationComposition.getElements().get(elementId).getDeployState());
+        assertEquals(docMessage.getLockState(),
+                automationComposition.getElements().get(elementId).getLockState());
+        assertEquals(docMessage.getStateChangeResult(), automationComposition.getStateChangeResult());
+    }
+
+    @Test
+    void testWithWrongData() {
+        var automationComposition = createDeploying();
+        var elementId = automationComposition.getElements().values().iterator().next().getId();
+        var docMessage = new DocMessage();
+        docMessage.setInstanceId(INSTANCE_ID);
+        docMessage.setInstanceElementId(elementId);
+        docMessage.setStateChangeResult(StateChangeResult.NO_ERROR);
+        docMessage.setDeployState(DeployState.DEPLOYED);
+        docMessage.setLockState(LockState.LOCKED);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var simpleScanner = new SimpleScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                acRuntimeParameterGroup);
+
+        // wrong MessageType
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_PRIME_ACK);
+        var result = simpleScanner.scanMessage(automationComposition, docMessage);
+        assertFalse(result.isUpdated());
+        assertFalse(result.isToBeSync());
+
+        // wrong elementId in outProperties
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_STATUS);
+        docMessage.setInstanceElementId(UUID.randomUUID());
+        docMessage.setOutProperties(OUT_PROPERTIES);
+        result = simpleScanner.scanMessage(automationComposition, docMessage);
+        assertFalse(result.isUpdated());
+        assertFalse(result.isToBeSync());
+
+        // wrong elementId in StateChange
+        docMessage.setMessageType(ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK);
+        result = simpleScanner.scanMessage(automationComposition, docMessage);
+        assertFalse(result.isUpdated());
+        assertFalse(result.isToBeSync());
+    }
+
+    @Test
+    void testScanMessageOutProperties() {
+        var automationComposition = createDeploying();
+        var elementId = automationComposition.getElements().values().iterator().next().getId();
+        var docMessage = new DocMessage();
+        docMessage.setMessageType(ParticipantMessageType.PARTICIPANT_STATUS);
+        docMessage.setInstanceId(INSTANCE_ID);
+        docMessage.setInstanceElementId(elementId);
+        docMessage.setOutProperties(Map.of("key", "value"));
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var simpleScanner = new SimpleScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                acRuntimeParameterGroup);
+        var result = simpleScanner.scanMessage(automationComposition, docMessage);
+        assertTrue(result.isUpdated());
+        assertTrue(result.isToBeSync());
+        assertEquals(docMessage.getOutProperties(),
+                automationComposition.getElements().get(elementId).getOutProperties());
+    }
+
+    @Test
+    void testScanMessageStateChange() {
+        var automationComposition = createDeploying();
+        var elementId = automationComposition.getElements().values().iterator().next().getId();
+        var docMessage = new DocMessage();
+        docMessage.setMessageType(ParticipantMessageType.AUTOMATION_COMPOSITION_DEPLOY_ACK);
+        docMessage.setStateChangeResult(StateChangeResult.NO_ERROR);
+        docMessage.setInstanceId(INSTANCE_ID);
+        docMessage.setInstanceElementId(elementId);
+        docMessage.setDeployState(DeployState.DEPLOYED);
+        docMessage.setLockState(LockState.LOCKED);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var simpleScanner = new SimpleScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                acRuntimeParameterGroup);
+        var result = simpleScanner.scanMessage(automationComposition, docMessage);
+        assertTrue(result.isUpdated());
+        assertFalse(result.isToBeSync());
+        assertEquals(docMessage.getDeployState(),
+                automationComposition.getElements().get(elementId).getDeployState());
+        assertEquals(docMessage.getLockState(),
+                automationComposition.getElements().get(elementId).getLockState());
+    }
+
+    private AutomationComposition createDeploying() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setDeployState(DeployState.DEPLOYING);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setPhase(0);
+        automationComposition.setLastMsg(TimestampHelper.now());
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        for (var element : automationComposition.getElements().values()) {
+            element.setDeployState(DeployState.DEPLOYING);
+            element.setLockState(LockState.NONE);
+        }
+        return automationComposition;
+    }
+
+    @Test
+    void testSendAutomationCompositionMigratingPrecheck() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setLockState(LockState.LOCKED);
+        automationComposition.setDeployState(DeployState.DEPLOYED);
+        automationComposition.setSubState(SubState.MIGRATION_PRECHECKING);
+        for (var element : automationComposition.getElements().values()) {
+            element.setDeployState(DeployState.DEPLOYED);
+            element.setSubState(SubState.NONE);
+            element.setLockState(LockState.LOCKED);
+            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
+                element.setSubState(SubState.MIGRATION_PRECHECKING);
+            }
+        }
+        testSimpleScan(automationComposition, element -> element.setSubState(SubState.NONE));
+    }
+
+    @Test
+    void testSendAutomationCompositionPrepare() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setDeployState(DeployState.UNDEPLOYED);
+        automationComposition.setSubState(SubState.PREPARING);
+        for (var element : automationComposition.getElements().values()) {
+            element.setDeployState(DeployState.UNDEPLOYED);
+            element.setSubState(SubState.NONE);
+            element.setLockState(LockState.NONE);
+            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
+                element.setSubState(SubState.PREPARING);
+            }
+        }
+        testSimpleScan(automationComposition, element -> element.setSubState(SubState.NONE));
+    }
+
+    @Test
+    void testSendAutomationCompositionUpdate() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setLockState(LockState.LOCKED);
+        automationComposition.setDeployState(DeployState.UPDATING);
+        for (var element : automationComposition.getElements().values()) {
+            element.setSubState(SubState.NONE);
+            element.setLockState(LockState.LOCKED);
+            if (ELEMENT_NAME.equals(element.getDefinition().getName())) {
+                element.setDeployState(DeployState.UPDATING);
+            } else {
+                element.setDeployState(DeployState.DEPLOYED);
+            }
+        }
+        testSimpleScan(automationComposition, element -> element.setDeployState(DeployState.DEPLOYED));
+    }
+
+    private void testSimpleScan(AutomationComposition automationComposition, Consumer<AutomationCompositionElement> c) {
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setLockState(LockState.NONE);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        automationComposition.setLastMsg(TimestampHelper.now());
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var simpleScanner = new SimpleScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                acRuntimeParameterGroup);
+        simpleScanner.simpleScan(automationComposition, new UpdateSync());
+        verify(acProvider, times(0)).updateAutomationComposition(any());
+
+        automationComposition.getElements().values().forEach(c);
+        simpleScanner.simpleScan(automationComposition, new UpdateSync());
+        verify(acProvider).updateAutomationComposition(any());
+    }
+}
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScannerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/StageScannerTest.java
new file mode 100644 (file)
index 0000000..1cff25f
--- /dev/null
@@ -0,0 +1,100 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.policy.clamp.acm.runtime.supervision.scanner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_SERVICE_TEMPLATE_YAML;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.instantiation.InstantiationUtils;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionMigrationPublisher;
+import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantSyncPublisher;
+import org.onap.policy.clamp.acm.runtime.util.CommonTestData;
+import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
+import org.onap.policy.clamp.models.acm.concepts.DeployState;
+import org.onap.policy.clamp.models.acm.concepts.LockState;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+
+class StageScannerTest {
+    private static final String AC_JSON = "src/test/resources/rest/acm/AutomationCompositionSmoke.json";
+    private static final UUID COMPOSITION_ID = UUID.randomUUID();
+
+    @Test
+    void testSendAutomationCompositionMigrate() {
+        var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
+        automationComposition.setInstanceId(UUID.randomUUID());
+        automationComposition.setDeployState(DeployState.MIGRATING);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        var compositionTargetId = UUID.randomUUID();
+        automationComposition.setCompositionTargetId(compositionTargetId);
+        automationComposition.setLockState(LockState.LOCKED);
+        automationComposition.setLastMsg(TimestampHelper.now());
+        automationComposition.setPhase(0);
+        for (var element : automationComposition.getElements().values()) {
+            element.setDeployState(DeployState.DEPLOYED);
+            element.setLockState(LockState.LOCKED);
+        }
+        // first element is not migrated yet
+        var element = automationComposition.getElements().entrySet().iterator().next().getValue();
+        element.setDeployState(DeployState.MIGRATING);
+
+        var acProvider = mock(AutomationCompositionProvider.class);
+        when(acProvider.updateAutomationComposition(any())).thenReturn(automationComposition);
+
+        var acRuntimeParameterGroup = CommonTestData.geParameterGroup("dbScanner");
+        var supervisionScanner = new StageScanner(acProvider, mock(ParticipantSyncPublisher.class),
+                mock(AutomationCompositionMigrationPublisher.class), acRuntimeParameterGroup);
+
+        var serviceTemplate = InstantiationUtils.getToscaServiceTemplate(TOSCA_SERVICE_TEMPLATE_YAML);
+        supervisionScanner.scanStage(automationComposition, serviceTemplate, new UpdateSync());
+        verify(acProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
+        assertEquals(DeployState.MIGRATING, automationComposition.getDeployState());
+
+        // send message for next stage
+        clearInvocations(acProvider);
+        var toscaNodeTemplate = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()
+                .get(element.getDefinition().getName());
+        toscaNodeTemplate.setProperties(Map.of("stage", List.of(1)));
+
+        supervisionScanner.scanStage(automationComposition, serviceTemplate, new UpdateSync());
+        verify(acProvider).updateAutomationComposition(any(AutomationComposition.class));
+        assertEquals(DeployState.MIGRATING, automationComposition.getDeployState());
+
+        // first element is migrated
+        clearInvocations(acProvider);
+        element.setDeployState(DeployState.DEPLOYED);
+        supervisionScanner.scanStage(automationComposition, serviceTemplate, new UpdateSync());
+        verify(acProvider).updateAutomationComposition(any(AutomationComposition.class));
+
+        assertEquals(DeployState.DEPLOYED, automationComposition.getDeployState());
+        assertEquals(compositionTargetId, automationComposition.getCompositionId());
+    }
+}