Introduce Stop of a rollout (#2595)

* Stop of a rollout feature

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* remove some test comments

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* make stop transactional

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* attempt to fix hibernate failed tests

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fix some sonar issues

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* changes after review

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fix build

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fixes after review

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* finish all rollout groups on deletion of rollout

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* refactor finishing groups

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fix RolloutManagementTest

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

---------

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
Stanislav Trailov
2025-08-12 17:13:50 +03:00
committed by GitHub
parent 4566702030
commit 45cd012532
25 changed files with 581 additions and 140 deletions

View File

@@ -360,8 +360,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
distributionSetInvalidationManagement
.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSetId),
MgmtRestModelMapper.convertCancelationType(invalidateRequestBody.getActionCancelationType()),
invalidateRequestBody.isCancelRollouts()));
MgmtRestModelMapper.convertCancelationType(invalidateRequestBody.getActionCancelationType())));
return ResponseEntity.ok().build();
}
}

View File

@@ -185,6 +185,13 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
return ResponseEntity.ok().build();
}
@Override
@AuditLog(entity = "Rollout", type = AuditLog.Type.UPDATE, description = "Stop Rollout")
public ResponseEntity<Void> stop(Long rolloutId) {
this.rolloutManagement.stop(rolloutId);
return ResponseEntity.ok().build();
}
@Override
@AuditLog(entity = "Rollout", type = AuditLog.Type.DELETE, description = "Delete Rollout")
public ResponseEntity<Void> delete(final Long rolloutId) {

View File

@@ -17,7 +17,7 @@ import org.eclipse.hawkbit.mgmt.json.model.MgmtTypeEntity;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtCancelationType;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.Type;
@@ -67,20 +67,20 @@ public final class MgmtRestModelMapper {
}
/**
* Converts the given repository {@link CancelationType} into a corresponding {@link MgmtCancelationType}.
* Converts the given repository {@link ActionCancellationType} into a corresponding {@link MgmtCancelationType}.
*
* @param cancelationType the repository representation of the cancellation type
* @return <null> or the REST cancellation type
*/
public static CancelationType convertCancelationType(final MgmtCancelationType cancelationType) {
public static ActionCancellationType convertCancelationType(final MgmtCancelationType cancelationType) {
if (cancelationType == null) {
return null;
}
return switch (cancelationType) {
case SOFT -> CancelationType.SOFT;
case FORCE -> CancelationType.FORCE;
case NONE -> CancelationType.NONE;
case SOFT -> ActionCancellationType.SOFT;
case FORCE -> ActionCancellationType.FORCE;
case NONE -> ActionCancellationType.NONE;
};
}

View File

@@ -1685,7 +1685,7 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
* Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and cancels assignments
*/
@Test
void invalidateDistributionSet() throws Exception {
void softInvalidateDistributionSet() throws Exception {
DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(5, "invalidateDistributionSet");
// the distribution set is locked and the old instance become stale
@@ -1697,7 +1697,6 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
final JSONObject jsonObject = new JSONObject();
jsonObject.put("actionCancelationType", "soft");
jsonObject.put("cancelRollouts", true);
mvc.perform(post("/rest/v1/distributionsets/{ds}/invalidate", distributionSet.getId())
.content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON))
@@ -1706,7 +1705,11 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
assertThat(targetFilterQueryManagement.get(targetFilterQuery.getId()).get().getAutoAssignDistributionSet())
.isNull();
assertThat(rolloutManagement.get(rollout.getId()).get().getStatus()).isIn(RolloutStatus.STOPPING,
RolloutStatus.FINISHED);
RolloutStatus.STOPPED);
//then enforce executor to stop the rollout and check
rolloutHandler.handleAll();
assertThat(rolloutManagement.get(rollout.getId()).get().getStatus()).isIn(RolloutStatus.STOPPED);
for (final Target target : targets) {
assertThat(targetManagement.get(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
@@ -1717,6 +1720,69 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
}
}
@Test
void forceInvalidateDistributionSet() throws Exception {
DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(5, "invalidateDistributionSet");
distributionSet = assignDistributionSet(distributionSet, targets).getDistributionSet();
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(
Create.builder().name("invalidateDistributionSet").query("name==*").autoAssignDistributionSet(distributionSet).build());
final Rollout rollout = testdataFactory.createRolloutByVariables("invalidateDistributionSet", "desc", 2,
"name==*", distributionSet, "50", "80");
final JSONObject jsonObject = new JSONObject();
jsonObject.put("actionCancelationType", "force");
mvc.perform(post("/rest/v1/distributionsets/{ds}/invalidate", distributionSet.getId())
.content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
assertThat(targetFilterQueryManagement.get(targetFilterQuery.getId()).get().getAutoAssignDistributionSet())
.isNull();
assertThat(rolloutManagement.get(rollout.getId()).get().getStatus()).isIn(RolloutStatus.DELETING,
RolloutStatus.DELETED);
//then enforce executor to stop the rollout and check
rolloutHandler.handleAll();
// assert rollout is deleted
assertThat(rolloutManagement.get(rollout.getId())).isEmpty();
for (final Target target : targets) {
assertThat(targetManagement.get(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
.getNumberOfElements()).isEqualTo(1);
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
.getContent().get(0).getStatus()).isEqualTo(Status.CANCELED);
}
}
@Test
void invalidateDistributionSetWithNoneCancellation() throws Exception {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(5, "invalidateDistributionSet");
Rollout rollout = testdataFactory.createRolloutByVariables("invalidateDistributionSet", "desc", 1,
"name==*", distributionSet, "50", "80");
rollout = testdataFactory.startRollout(rollout);
final JSONObject jsonObject = new JSONObject();
jsonObject.put("actionCancelationType", "none");
mvc.perform(post("/rest/v1/distributionsets/{ds}/invalidate", distributionSet.getId())
.content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
assertThat(rolloutManagement.get(rollout.getId()).get().getStatus()).isIn(RolloutStatus.RUNNING);
for (final Target target : targets) {
assertThat(targetManagement.get(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
.getNumberOfElements()).isEqualTo(1);
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
.getContent().get(0).getStatus()).isEqualTo(Status.RUNNING);
}
}
/**
* Tests the lock. It is verified that the distribution set can be marked as locked through update operation.
*/

View File

@@ -31,6 +31,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.awaitility.Awaitility;
@@ -1461,6 +1462,55 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(jsonPath("$.deleted", equalTo(true)));
assertStatusIs(rollout, RolloutStatus.DELETED);
List<Action> rolloutActions =
deploymentManagement.findActions("rollout.id==" + rollout.getId(), PAGE).getContent();
for (Action action : rolloutActions) {
Assertions.assertEquals(Status.CANCELED, action.getStatus());
}
// ensure groups are in final state
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(rollout.getId(), PAGE).getContent();
for (RolloutGroup rolloutGroup : groups) {
Assertions.assertEquals(RolloutGroupStatus.FINISHED, rolloutGroup.getStatus());
}
}
@Test
void stopRunningRollout() throws Exception {
final Rollout rollout = testdataFactory.createAndStartRollout();
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/stop", rollout.getId()))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(get("/rest/v1/rollouts/{rolloutid}", rollout.getId()))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", equalTo("stopping")));
// force executor to retrigger
rolloutHandler.handleAll();
List<Action> rolloutActions =
deploymentManagement.findActions("rollout.id==" + rollout.getId(), PAGE).getContent();
for (Action action : rolloutActions) {
Awaitility.await()
.atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> Assertions.assertEquals(Status.CANCELING, action.getStatus()));
}
// assume that the targets have agreed to cancel the actions
rolloutActions.forEach(action -> controllerManagement.addCancelActionStatus(
Action.ActionStatusCreate.builder().actionId(action.getId()).status(Status.CANCELED).build()
));
// force executor to retrigger
rolloutHandler.handleAll();
// rollout should be in stopped state after all actions are cancelled
mvc.perform(get("/rest/v1/rollouts/{rolloutid}", rollout.getId()))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", equalTo("stopped")));
}
/**