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

@@ -25,9 +25,9 @@ import org.eclipse.hawkbit.repository.exception.StopRolloutException;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
@@ -78,7 +78,7 @@ class ConcurrentDistributionSetInvalidationTest extends AbstractJpaIntegrationTe
() -> rolloutGroupManagement.findByRollout(rollout.getId(), PAGE).getSize() > 0)));
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(distributionSet.getId()), CancelationType.SOFT, true);
Collections.singletonList(distributionSet.getId()), ActionCancellationType.SOFT);
assertThatExceptionOfType(StopRolloutException.class)
.as("Invalidation of distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation));

View File

@@ -17,8 +17,8 @@ import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Pageable;
@@ -294,7 +294,7 @@ class DeploymentManagementSecurityTest extends AbstractJpaIntegrationTest {
void cancelActionsForDistributionSetPermissionsCheck() {
assertPermissions(() -> {
deploymentManagement.cancelActionsForDistributionSet(
DistributionSetInvalidation.CancelationType.FORCE, new JpaDistributionSet());
ActionCancellationType.FORCE, new JpaDistributionSet());
return null;
}, List.of(SpPermission.UPDATE_TARGET));
}

View File

@@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
@@ -22,13 +23,12 @@ import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -43,6 +43,7 @@ import org.springframework.data.repository.query.Param;
* Feature: Component Tests - Repository<br/>
* Story: Distribution set invalidation management
*/
@Slf4j
class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTest {
/**
@@ -53,8 +54,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
final InvalidationTestData invalidationTestData = createInvalidationTestData("verifyInvalidateDistributionSetStopAutoAssignment");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false);
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.NONE);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 0);
@@ -76,19 +76,18 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
}
/**
* Verify invalidation of distribution sets that removes distribution sets from auto assignments and stops rollouts
* Verify invalidation of distribution sets that removes distribution sets from auto assignments but does not stop rollouts
*/
@Test
void verifyInvalidateDistributionSetStopRollouts() {
void verifyInvalidateDistributionSetDoesNotStopRollouts() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopRollouts");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
true);
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.NONE);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 1);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 0);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutHandler.handleAll();
@@ -96,9 +95,9 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isEqualTo(RolloutStatus.FINISHED);
.isEqualTo(RolloutStatus.READY);
assertNoScheduledActionsExist(invalidationTestData.getRollout());
assertRolloutGroupsAreFinished(invalidationTestData.getRollout());
for (final Target target : invalidationTestData.getTargets()) {
// if status is pending, the assignment has not been canceled
assertThat(
@@ -118,8 +117,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
"verifyInvalidateDistributionSetStopAllAndForceCancel");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.FORCE,
true);
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.FORCE);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 5, 1);
@@ -129,16 +127,10 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isEqualTo(RolloutStatus.FINISHED);
// rollout should be deleted when force invalidation
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId())).isEmpty();
assertNoScheduledActionsExist(invalidationTestData.getRollout());
assertRolloutGroupsAreFinished(invalidationTestData.getRollout());
for (final Target target : invalidationTestData.getTargets()) {
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(findActionsByTarget(target)).hasSize(1);
assertThat(findActionsByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELED);
}
assertAllRolloutActionsAreCancelled(invalidationTestData.getRollout());
}
/**
@@ -149,7 +141,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
final InvalidationTestData invalidationTestData = createInvalidationTestData("verifyInvalidateDistributionSetStopAll");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT,true);
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.SOFT);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 5, 1);
@@ -176,7 +168,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
List.of(distributionSet.getId()), CancelationType.SOFT, true);
List.of(distributionSet.getId()), ActionCancellationType.SOFT);
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation));
@@ -193,7 +185,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true));
ActionCancellationType.SOFT));
}
/**
@@ -206,8 +198,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
.runAsSystem(() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAuthority"));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false));
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.NONE));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
@@ -223,14 +214,13 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAndUpdateTargetAuthority"));
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
List.of(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT, true);
List.of(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.SOFT);
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Insufficient permission exception expected")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false));
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.NONE));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
@@ -246,8 +236,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAndUpdateTargetAuthority"));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT,
true));
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), ActionCancellationType.SOFT));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
@@ -259,10 +248,10 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
.isZero();
}
private void assertRolloutGroupsAreFinished(final Rollout rollout) {
assertThat(rolloutGroupRepository.findByRolloutId(rollout.getId(), PAGE))
.isNotEmpty()
.allMatch(rolloutGroup -> rolloutGroup.getStatus().equals(RolloutGroupStatus.FINISHED));
private void assertAllRolloutActionsAreCancelled(final Rollout rollout) {
assertThat(
actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Status.CANCELED).getTotalElements())
.isZero();
}
private InvalidationTestData createInvalidationTestData(final String testName) {

View File

@@ -50,11 +50,11 @@ import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThis
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
@@ -482,7 +482,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final Long softwareModuleId = testdataFactory.createSoftwareModuleOs().getId();
distributionSetManagement.assignSoftwareModules(distributionSetId, singletonList(softwareModuleId));
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(singletonList(distributionSetId), CancelationType.NONE, false));
new DistributionSetInvalidation(singletonList(distributionSetId), ActionCancellationType.NONE));
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
@@ -876,7 +876,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
distributionSetManagement.createMetadata(dsId, Map.of(knownKey1, knownValue));
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(singletonList(dsId), CancelationType.NONE, false));
new DistributionSetInvalidation(singletonList(dsId), ActionCancellationType.NONE));
// assert that no new metadata can be created
assertThatExceptionOfType(InvalidDistributionSetException.class)

View File

@@ -20,7 +20,9 @@ import org.eclipse.hawkbit.repository.RolloutManagement.Create;
import org.eclipse.hawkbit.repository.RolloutManagement.GroupCreate;
import org.eclipse.hawkbit.repository.RolloutManagement.Update;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
@@ -113,7 +115,7 @@ class RolloutManagementSecurityTest extends AbstractJpaIntegrationTest {
DistributionSetManagement.Create.builder().type(defaultDsType()).name("name").version("1.0.0").build();
final DistributionSet ds = distributionSetManagement.create(dsCreate);
assertPermissions(() -> {
rolloutManagement.cancelRolloutsForDistributionSet(ds);
rolloutManagement.cancelRolloutsForDistributionSet(ds, ActionCancellationType.SOFT);
return null;
}, List.of(SpPermission.UPDATE_ROLLOUT, SpPermission.READ_REPOSITORY, SpPermission.CREATE_REPOSITORY));
}

View File

@@ -1914,11 +1914,11 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 10),
@Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 4),
@Expect(type = RolloutCreatedEvent.class, count = 1),
@Expect(type = RolloutUpdatedEvent.class, count = 6),
@Expect(type = RolloutDeletedEvent.class, count = 1),
@Expect(type = RolloutGroupUpdatedEvent.class, count = 11),
@Expect(type = RolloutGroupUpdatedEvent.class, count = 16),
@Expect(type = RolloutGroupCreatedEvent.class, count = 5) })
void deleteRolloutWhichHasBeenStartedBeforeIsSoftDeleted() {
final int amountTargetsForRollout = 10;
@@ -1962,8 +1962,8 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
// verify that all scheduled actions are deleted
assertThat(actionRepository.findByRolloutIdAndStatus(PAGE, deletedRollout.getId(), Status.SCHEDULED)
.getNumberOfElements()).isZero();
// verify that all running actions keep running
assertThat(actionRepository.findByRolloutIdAndStatus(PAGE, deletedRollout.getId(), Status.RUNNING)
// verify that all running actions are force cancelled
assertThat(actionRepository.findByRolloutIdAndStatus(PAGE, deletedRollout.getId(), Status.CANCELED)
.getNumberOfElements()).isEqualTo(2);
}