/*-
* ============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.
DELETING,
DELETED,
UPDATING,
- MIGRATING
+ MIGRATING,
+ MIGRATION_REVERTING
}
/*-
* ============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.
private List<ParticipantDeploy> participantUpdatesList = new ArrayList<>();
private Boolean precheck = false;
+ private Boolean rollback = false;
private Integer stage = 0;
public AutomationCompositionMigration() {
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();
// 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) {
* @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
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();
+ }
}
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
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;
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;
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"));
}
}
/*-
* ============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.
migrationMsg.setAutomationCompositionId(UUID.randomUUID());
migrationMsg.setCompositionTargetId(UUID.randomUUID());
assertDoesNotThrow(() -> ach.handleAutomationCompositionMigration(migrationMsg));
+ migrationMsg.setRollback(true);
+ assertDoesNotThrow(() -> ach.handleAutomationCompositionMigration(migrationMsg));
}
@Test
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);
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;
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;
}
}
+ /**
+ * 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()) {
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
provider.compositionInstanceState(compositionId, instanceId, body);
return ResponseEntity.accepted().build();
}
+
+ @Override
+ public ResponseEntity<Void> rollbackCompositionInstance(UUID instanceId) {
+ provider.rollback(instanceId);
+ return ResponseEntity.accepted().build();
+ }
}
@Valid AcInstanceStateUpdate body, UUID requestId) {
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}
+
+ @Override
+ public ResponseEntity<Void> rollbackCompositionInstance(UUID instanceId) {
+ return new ResponseEntity<>(HttpStatus.ACCEPTED);
+ }
}
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:
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;
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());
+ }
}
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;
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;
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;
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);