Add rollback support 49/141349/10
authorwaynedunican <wayne.dunican@est.tech>
Mon, 16 Jun 2025 17:35:24 +0000 (18:35 +0100)
committerAdheli Tavares <adheli.tavares@est.tech>
Tue, 24 Jun 2025 08:46:19 +0000 (08:46 +0000)
- Add MIGRATION_ROLLBAKING enum in DeployState
- Add new endpoint for rollback
- Update migration messages for rollback
- Add validation in resolver class
- Add business logic

Issue-ID: POLICY-5362
Change-Id: I936d6dd6b98fab13ad4548d2f994cc8313374d3f
Signed-off-by: waynedunican <wayne.dunican@est.tech>
13 files changed:
models/src/main/java/org/onap/policy/clamp/models/acm/concepts/DeployState.java
models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcInstanceStateResolver.java
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcInstanceStateResolverTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/InstantiationController.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/stub/InstantiationControllerStub.java
runtime-acm/src/main/resources/openapi/openapi.yaml
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/contract/InstantiationControllerStubTest.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java

index 57669fe..6b59535 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation.
+ *  Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,5 +28,6 @@ public enum DeployState {
     DELETING,
     DELETED,
     UPDATING,
-    MIGRATING
+    MIGRATING,
+    MIGRATION_REVERTING
 }
index 2d7608a..ee05a95 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2023-2024 Nordix Foundation.
+ * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ public class AutomationCompositionMigration extends ParticipantMessage {
     private List<ParticipantDeploy> participantUpdatesList = new ArrayList<>();
 
     private Boolean precheck = false;
+    private Boolean rollback = false;
     private Integer stage = 0;
 
     public AutomationCompositionMigration() {
index 27c0d40..defa804 100644 (file)
@@ -42,6 +42,7 @@ public class AcInstanceStateResolver {
     private static final String DELETING = DeployState.DELETING.name();
     private static final String MIGRATING = DeployState.MIGRATING.name();
     private static final String MIGRATION_PRECHECKING = SubState.MIGRATION_PRECHECKING.name();
+    private static final String MIGRATION_REVERTING = DeployState.MIGRATION_REVERTING.name();
     private static final String SUB_STATE_NONE = SubState.NONE.name();
     private static final String PREPARING = SubState.PREPARING.name();
     private static final String REVIEWING = SubState.REVIEWING.name();
@@ -126,6 +127,10 @@ public class AcInstanceStateResolver {
 
         // review order in a failed or timeout scenario
         addSubOrderWithFail(REVIEW, DEPLOYED, LOCKED, REVIEWING);
+
+        // rollback
+        addDeployOrderWithFail(MIGRATION_REVERTING, MIGRATING, LOCKED, SUB_STATE_NONE);
+        addDeployOrderWithFail(UNDEPLOY, MIGRATION_REVERTING, LOCKED, SUB_STATE_NONE);
     }
 
     private void addDeployOrder(String deployOrder, String deployState, String lockState) {
@@ -177,7 +182,7 @@ public class AcInstanceStateResolver {
      * @param acDeployOrder the Deploy Ordered
      * @param acLockOrder the Lock Ordered
      * @param acSubOrder the Sub Ordered
-     * @param acDeployState then current Deploy State
+     * @param acDeployState the current Deploy State
      * @param acLockState the current Lock State
      * @param acSubState the current Sub State
      * @param acStateChangeResult the current Result of the State Change
index 5d2c4f2..b5f5efd 100644 (file)
@@ -266,4 +266,18 @@ public class AutomationCompositionProvider {
         acRollbackRepository.save(jpaCopy);
         acRollbackRepository.flush();
     }
+
+    /**
+     * Get the copied automation composition from the RollbackRepository.
+     *
+     * @param instanceId the id of the ac instance
+     * @return the acRollback object
+     */
+    public JpaAutomationCompositionRollback getAutomationCompositionRollback(String instanceId) {
+        var result = acRollbackRepository.findById(instanceId);
+        if (result.isEmpty()) {
+            throw new PfModelRuntimeException(Status.NOT_FOUND, "Instance not found for rollback");
+        }
+        return result.get();
+    }
 }
index a807a11..c9c4f0d 100644 (file)
@@ -75,6 +75,16 @@ class AcInstanceStateResolverTest {
         result = acTypeStateResolver.resolve(DeployOrder.NONE, LockOrder.NONE, SubOrder.REVIEW,
             DeployState.DEPLOYED, LockState.LOCKED, SubState.NONE, StateChangeResult.NO_ERROR);
         assertThat(result).isEqualTo(AcInstanceStateResolver.REVIEW);
+
+        // rollback
+        result = acTypeStateResolver.resolve(DeployOrder.MIGRATE, LockOrder.NONE, SubOrder.REVIEW,
+            DeployState.MIGRATION_REVERTING, LockState.LOCKED, SubState.NONE, StateChangeResult.NO_ERROR);
+        assertThat(result).isEqualTo(AcInstanceStateResolver.NONE);
+
+        result = acTypeStateResolver.resolve(DeployOrder.UNDEPLOY, LockOrder.NONE, SubOrder.NONE,
+            DeployState.MIGRATION_REVERTING, LockState.LOCKED, SubState.NONE, StateChangeResult.FAILED);
+        assertThat(result).isEqualTo(AcInstanceStateResolver.UNDEPLOY);
+
     }
 
     @Test
index 7113476..dd24833 100644 (file)
@@ -23,8 +23,11 @@ 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyIterable;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -48,6 +51,7 @@ import org.onap.policy.clamp.models.acm.persistence.repository.AutomationComposi
 import org.onap.policy.common.utils.coder.Coder;
 import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.common.utils.resources.ResourceUtils;
+import org.onap.policy.models.base.PfModelRuntimeException;
 import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
@@ -264,5 +268,28 @@ class AutomationCompositionProviderTest {
         automationCompositionProvider.copyAcElementsBeforeUpdate(ac);
 
         verify(acRollbackRepository).save(any(JpaAutomationCompositionRollback.class));
+        assertThrows(PfModelRuntimeException.class, () -> automationCompositionProvider // NOSONAR
+            .getAutomationCompositionRollback(ac.getInstanceId().toString()));
+    }
+
+    @Test
+    void testGetRollbackSuccess() {
+        var ac = inputAutomationCompositions.getAutomationCompositionList().get(0);
+        var rollback = new JpaAutomationCompositionRollback();
+        rollback.setInstanceId(ac.getInstanceId().toString());
+        rollback.setCompositionId(ac.getCompositionId().toString());
+
+        when(acRollbackRepository.findById(anyString())).thenReturn(Optional.of(rollback));
+
+        var rbFromDb = automationCompositionProvider.getAutomationCompositionRollback(ac.getInstanceId()
+            .toString());
+        assertNotNull(rbFromDb);
+    }
+
+    @Test
+    void testGetRollbackEmpty() {
+        when(acRollbackRepository.findById(anyString())).thenReturn(Optional.empty());
+        assertThrows(PfModelRuntimeException.class, () -> automationCompositionProvider
+            .getAutomationCompositionRollback("empty"));
     }
 }
index b301ef1..b607372 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation.
+ *  Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -225,6 +225,8 @@ class AutomationCompositionHandlerTest {
         migrationMsg.setAutomationCompositionId(UUID.randomUUID());
         migrationMsg.setCompositionTargetId(UUID.randomUUID());
         assertDoesNotThrow(() -> ach.handleAutomationCompositionMigration(migrationMsg));
+        migrationMsg.setRollback(true);
+        assertDoesNotThrow(() -> ach.handleAutomationCompositionMigration(migrationMsg));
     }
 
     @Test
@@ -347,6 +349,7 @@ class AutomationCompositionHandlerTest {
         migrationMsg.setCompositionId(acMigrate.getCompositionId());
         migrationMsg.setAutomationCompositionId(acMigrate.getInstanceId());
         migrationMsg.setCompositionTargetId(acMigrate.getCompositionTargetId());
+        migrationMsg.setRollback(false);
         var participantMigrate = CommonTestData.createparticipantDeploy(cacheProvider.getParticipantId(), acMigrate);
         migrationMsg.setParticipantUpdatesList(List.of(participantMigrate));
         var listener = mock(ThreadHandler.class);
index 59373e8..14660a8 100644 (file)
@@ -23,7 +23,9 @@ package org.onap.policy.clamp.acm.runtime.instantiation;
 
 import jakarta.validation.Valid;
 import jakarta.ws.rs.core.Response.Status;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import lombok.NonNull;
@@ -34,6 +36,7 @@ import org.onap.policy.clamp.acm.runtime.supervision.SupervisionAcHandler;
 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.AutomationCompositions;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.LockState;
@@ -456,6 +459,34 @@ public class AutomationCompositionInstantiationProvider {
         }
     }
 
+    /**
+     * Rollback AC Instance.
+     *
+     * @param instanceId the instanceId
+     * @return the instantiation response
+     */
+    public InstantiationResponse rollback(UUID instanceId) {
+        var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
+        var automationCompositionToRollback =
+            automationCompositionProvider.getAutomationCompositionRollback(instanceId.toString());
+
+        if (DeployState.DEPLOYED.equals(automationComposition.getDeployState())
+              && SubState.NONE.equals(automationComposition.getSubState())
+              && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
+            automationComposition.setCompositionId(UUID.fromString(automationCompositionToRollback.getCompositionId()));
+            Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
+            automationCompositionToRollback.getElements().forEach((String uuid, Object acElement) ->
+                elements.put(UUID.fromString(uuid), (AutomationCompositionElement) acElement));
+            automationComposition.setElements(elements);
+            automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
+            AcmUtils.setCascadedState(automationComposition, DeployState.MIGRATION_REVERTING, LockState.LOCKED);
+            automationCompositionProvider.updateAutomationComposition(automationComposition);
+            return createInstantiationResponse(automationComposition);
+        } else {
+            throw new PfModelRuntimeException(Status.BAD_REQUEST, "Invalid state for rollback");
+        }
+    }
+
     private List<UUID> updateElementsProperties(AutomationComposition automationComposition,
                                                 AutomationComposition acToBeUpdated) {
         for (var element : automationComposition.getElements().entrySet()) {
index 386241d..f20f3f9 100644 (file)
@@ -36,7 +36,7 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
 /**
- * Class to provide REST end points for creating, deleting, query and commanding a automation composition definition.
+ * Class to provide REST end points for creating, deleting, query and commanding an automation composition definition.
  */
 @RestController
 @RequiredArgsConstructor
@@ -119,4 +119,10 @@ public class InstantiationController extends AbstractRestController implements A
         provider.compositionInstanceState(compositionId, instanceId, body);
         return ResponseEntity.accepted().build();
     }
+
+    @Override
+    public ResponseEntity<Void> rollbackCompositionInstance(UUID instanceId) {
+        provider.rollback(instanceId);
+        return ResponseEntity.accepted().build();
+    }
 }
index bbcfddc..d22fbfe 100644 (file)
@@ -90,4 +90,9 @@ public class InstantiationControllerStub extends AbstractRestController implemen
             @Valid AcInstanceStateUpdate body, UUID requestId) {
         return new ResponseEntity<>(HttpStatus.ACCEPTED);
     }
+
+    @Override
+    public ResponseEntity<Void> rollbackCompositionInstance(UUID instanceId) {
+        return new ResponseEntity<>(HttpStatus.ACCEPTED);
+    }
 }
index 2bbfe3a..7b60a89 100644 (file)
@@ -1615,7 +1615,126 @@ paths:
       x-interface info:
         api-version: 8.2.1
         last-mod-release: Paris
-
+  /compositions/{compositionId}/instances/{instanceId}/rollback:
+    post:
+      tags:
+        - Automation Composition Instance
+      summary: Rollback an automation composition instance to a previous state
+      description: Triggers a rollback of the specified instance.
+      operationId: rollbackCompositionInstance
+      parameters:
+        - name: instanceId
+          in: path
+          description: The UUID of the automation composition definition on which to rollback
+          required: true
+          schema:
+            type: string
+            format: uuid
+      requestBody:
+        description: Parameters for the rollback (e.g., target version)
+        required: true
+        content:
+          application/json:
+            schema:
+          application/yaml:
+            schema:
+      responses:
+        202:
+          description: Rollback initiated
+          headers:
+            X-LatestVersion:
+              $ref: '#/components/headers/X-LatestVersion'
+            X-PatchVersion:
+              $ref: '#/components/headers/X-PatchVersion'
+            X-MinorVersion:
+              $ref: '#/components/headers/X-MinorVersion'
+            X-onap-RequestId:
+              $ref: '#/components/headers/X-onap-RequestId'
+        400:
+          description: Bad request
+          headers:
+            X-LatestVersion:
+              $ref: '#/components/headers/X-LatestVersion'
+            X-PatchVersion:
+              $ref: '#/components/headers/X-PatchVersion'
+            X-MinorVersion:
+              $ref: '#/components/headers/X-MinorVersion'
+            X-onap-RequestId:
+              $ref: '#/components/headers/X-onap-RequestId'
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SimpleResponse'
+        401:
+          description: Authentication Error, returns an instance of
+            [SimpleResponse](https://github.com/onap/policy-clamp/blob/master/models/src/main/java/org/onap/policy/clamp/models/acm/messages/rest/SimpleResponse.java)
+          headers:
+            X-LatestVersion:
+              $ref: '#/components/headers/X-LatestVersion'
+            X-PatchVersion:
+              $ref: '#/components/headers/X-PatchVersion'
+            X-MinorVersion:
+              $ref: '#/components/headers/X-MinorVersion'
+            X-onap-RequestId:
+              $ref: '#/components/headers/X-onap-RequestId'
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/SimpleResponse'
+          404:
+            description: The specified automation composition definition was not found, returns an instance of
+              [SimpleResponse](https://github.com/onap/policy-clamp/blob/master/models/src/main/java/org/onap/policy/clamp/models/acm/messages/rest/SimpleResponse.java)
+            headers:
+              X-LatestVersion:
+                $ref: '#/components/headers/X-LatestVersion'
+              X-PatchVersion:
+                $ref: '#/components/headers/X-PatchVersion'
+              X-MinorVersion:
+                $ref: '#/components/headers/X-MinorVersion'
+              X-onap-RequestId:
+                $ref: '#/components/headers/X-onap-RequestId'
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/SimpleResponse'
+          400:
+            description: Bad Request, returns an instance of
+              [SimpleResponse](https://github.com/onap/policy-clamp/blob/master/models/src/main/java/org/onap/policy/clamp/models/acm/messages/rest/SimpleResponse.java)
+            headers:
+              X-LatestVersion:
+                $ref: '#/components/headers/X-LatestVersion'
+              X-PatchVersion:
+                $ref: '#/components/headers/X-PatchVersion'
+              X-MinorVersion:
+                $ref: '#/components/headers/X-MinorVersion'
+              X-onap-RequestId:
+                $ref: '#/components/headers/X-onap-RequestId'
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/SimpleResponse'
+          500:
+            description: Internal Server Error, returns an instance of
+              [SimpleResponse](https://github.com/onap/policy-clamp/blob/master/models/src/main/java/org/onap/policy/clamp/models/acm/messages/rest/SimpleResponse.java)
+            headers:
+              X-LatestVersion:
+                $ref: '#/components/headers/X-LatestVersion'
+              X-PatchVersion:
+                $ref: '#/components/headers/X-PatchVersion'
+              X-MinorVersion:
+                $ref: '#/components/headers/X-MinorVersion'
+              X-onap-RequestId:
+                $ref: '#/components/headers/X-onap-RequestId'
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/SimpleResponse'
+          security:
+          - basicAuth: [ ]
+          x-interface info:
+            api-version: 8.2.1
+            last-mod-release: Paris
+          x-codegen-request-body-name: body
 components:
   securitySchemes:
     basicAuth:
index 52dbcf8..f64cbba 100644 (file)
@@ -45,6 +45,7 @@ class InstantiationControllerStubTest extends CommonRestController {
     private static final String INSTANTIATION_ENDPOINT = "instances";
     private static final String COMPOSITION_ID = "1aeed185-a98b-45b6-af22-8d5d20485ea3";
     private static final String INSTANCE_ID = "709c62b3-8918-41b9-a747-d21eb79c6c23";
+    private static final String ROLLBACK = "rollback";
 
     @LocalServerPort
     private int randomServerPort;
@@ -101,4 +102,15 @@ class InstantiationControllerStubTest extends CommonRestController {
         var respPost = invocationBuilder.delete();
         assertThat(Response.Status.OK.getStatusCode()).isEqualTo(respPost.getStatus());
     }
+
+    @Test
+    void testRollback() {
+        var invocationBuilder = super.sendRequest(COMMISSIONING_ENDPOINT
+            + "/" + COMPOSITION_ID
+            + "/" + INSTANTIATION_ENDPOINT
+            + "/" + INSTANCE_ID
+            + "/" + ROLLBACK);
+        var respPost = invocationBuilder.post(Entity.json(new AcInstanceStateUpdate()));
+        assertThat(Response.Status.ACCEPTED.getStatusCode()).isEqualTo(respPost.getStatus());
+    }
 }
index bfaa132..d9ada4b 100644 (file)
@@ -24,9 +24,13 @@ package org.onap.policy.clamp.acm.runtime.instantiation;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 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;
@@ -54,6 +58,7 @@ import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceSt
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
+import org.onap.policy.clamp.models.acm.persistence.concepts.JpaAutomationCompositionRollback;
 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
@@ -61,6 +66,7 @@ import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider
 import org.onap.policy.clamp.models.acm.persistence.provider.ProviderUtils;
 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
 import org.onap.policy.models.base.PfConceptKey;
+import org.onap.policy.models.base.PfModelRuntimeException;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
@@ -562,6 +568,96 @@ class AutomationCompositionInstantiationProviderTest {
         assertThatDeleteThrownBy(automationComposition, DeployState.DELETING, LockState.NONE);
     }
 
+    @Test
+    void testRollbackFailure() {
+        var acDefinition = CommonTestData.createAcDefinition(serviceTemplate, AcTypeState.PRIMED);
+        var compositionId = acDefinition.getCompositionId();
+        var automationComposition =
+            InstantiationUtils.getAutomationCompositionFromResource(AC_INSTANTIATION_CREATE_JSON, "Rollback");
+        automationComposition.setCompositionId(compositionId);
+        automationComposition.setInstanceId(UUID.randomUUID());
+        automationComposition.setDeployState(DeployState.MIGRATION_REVERTING);
+        automationComposition.setCompositionTargetId(UUID.randomUUID());
+
+        var acProvider = mock(AutomationCompositionProvider.class);
+        when(acProvider.getAutomationComposition(automationComposition.getInstanceId()))
+            .thenReturn(automationComposition);
+        var rollbackRecord = mock(JpaAutomationCompositionRollback.class);
+        when(acProvider.getAutomationCompositionRollback(anyString())).thenReturn(rollbackRecord);
+
+        final var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        final var supervisionAcHandler = mock(SupervisionAcHandler.class);
+        final var participantProvider = mock(ParticipantProvider.class);
+        final var encryptionUtils = new EncryptionUtils(CommonTestData.getTestParamaterGroup());
+        var instantiationProvider = new AutomationCompositionInstantiationProvider(acProvider, acDefinitionProvider,
+            new AcInstanceStateResolver(), supervisionAcHandler, participantProvider, new AcRuntimeParameterGroup(),
+            encryptionUtils);
+
+        assertThrows(PfModelRuntimeException.class, () -> instantiationProvider
+            .rollback(automationComposition.getInstanceId()));
+
+        // DeployState != MIGRATION_REVERTING
+        when(acProvider.getAutomationComposition(automationComposition.getInstanceId()))
+            .thenReturn(automationComposition);
+        when(acProvider.getAutomationCompositionRollback(anyString())).thenReturn(rollbackRecord);
+
+        automationComposition.setDeployState(DeployState.DELETING);
+        assertThrows(PfModelRuntimeException.class, () -> instantiationProvider
+            .rollback(automationComposition.getInstanceId()));
+
+        // SubState != NONE
+        automationComposition.setDeployState(DeployState.DEPLOYED);
+        automationComposition.setSubState(SubState.PREPARING);
+        assertThrows(PfModelRuntimeException.class, () -> instantiationProvider
+            .rollback(automationComposition.getInstanceId()));
+
+        // StateChangeResult != NO_ERROR
+        automationComposition.setSubState(SubState.NONE);
+        automationComposition.setStateChangeResult(StateChangeResult.FAILED);
+        assertThrows(PfModelRuntimeException.class, () -> instantiationProvider
+            .rollback(automationComposition.getInstanceId()));
+
+        verify(acProvider, never()).updateAutomationComposition(any());
+    }
+
+    @Test
+    void testRollbackSuccess() {
+        final var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        final var acDefinition = CommonTestData.createAcDefinition(serviceTemplate, AcTypeState.PRIMED);
+        final var compositionId = acDefinition.getCompositionId();
+
+        var automationComposition =
+            InstantiationUtils.getAutomationCompositionFromResource(AC_INSTANTIATION_CREATE_JSON, "Rollback");
+        automationComposition.setInstanceId(UUID.randomUUID());
+        automationComposition.setCompositionId(compositionId);
+        automationComposition.setDeployState(DeployState.DEPLOYED);
+        automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
+        automationComposition.setSubState(SubState.NONE);
+        automationComposition.setCompositionTargetId(UUID.randomUUID());
+
+        var acProvider = mock(AutomationCompositionProvider.class);
+        when(acProvider.getAutomationComposition(automationComposition.getInstanceId()))
+            .thenReturn(automationComposition);
+        final var supervisionAcHandler = mock(SupervisionAcHandler.class);
+        final var participantProvider = mock(ParticipantProvider.class);
+        final var encryptionUtils = new EncryptionUtils(CommonTestData.getTestParamaterGroup());
+        final var instantiationProvider = new AutomationCompositionInstantiationProvider(acProvider,
+            acDefinitionProvider, new AcInstanceStateResolver(), supervisionAcHandler,
+            participantProvider, new AcRuntimeParameterGroup(), encryptionUtils);
+
+        var rollbackRecord = new JpaAutomationCompositionRollback();
+        rollbackRecord.setCompositionId(automationComposition.getCompositionId().toString());
+
+        when(acProvider.getAutomationComposition(automationComposition.getInstanceId()))
+            .thenReturn(automationComposition);
+        when(acProvider.getAutomationCompositionRollback(anyString())).thenReturn(rollbackRecord);
+
+        instantiationProvider.rollback(automationComposition.getInstanceId());
+
+        verify(acProvider).updateAutomationComposition(automationComposition);
+        assertEquals(DeployState.MIGRATION_REVERTING, automationComposition.getDeployState());
+    }
+
     private void assertThatDeleteThrownBy(AutomationComposition automationComposition, DeployState deployState,
             LockState lockState) {
         automationComposition.setDeployState(deployState);