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,7 +25,5 @@ public class MgmtInvalidateDistributionSetRequestBody {
@NotNull
@Schema(description = "Type of cancelation for actions referring to the given distribution set")
private MgmtCancelationType actionCancelationType;
@Schema(description = "Defines if rollouts referring to this distribution set should be canceled", example = "true")
private boolean cancelRollouts;
}

View File

@@ -348,6 +348,39 @@ public interface MgmtRolloutRestApi {
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> pause(@PathVariable("rolloutId") Long rolloutId);
/**
* Handles the POST request for stopping a rollout.
*
* @param rolloutId the ID of the rollout to be paused.
* @return OK response (200) if rollout could be stopped. In case of any exception the corresponding errors occur.
*/
@Operation(summary = "Stop a Rollout", description = "Handles the POST request of stopping a running rollout. " +
"Required Permission: HANDLE_ROLLOUT")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
@ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403",
description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or " +
"data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Rollout not found.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
"and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
})
@PostMapping(value = MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}/stop",
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> stop(@PathVariable("rolloutId") Long rolloutId);
/**
* Handles the DELETE request for deleting a rollout.
*

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")));
}
/**

View File

@@ -31,12 +31,12 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
@@ -421,5 +421,5 @@ public interface DeploymentManagement extends PermissionSupport {
* @param set the distribution set for that the actions should be canceled
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void cancelActionsForDistributionSet(final CancelationType cancelationType, final DistributionSet set);
void cancelActionsForDistributionSet(final ActionCancellationType cancelationType, final DistributionSet set);
}

View File

@@ -37,6 +37,7 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -354,6 +355,14 @@ public interface RolloutManagement extends PermissionSupport {
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
Rollout update(@NotNull @Valid Update update);
/**
* Stop a rollout
* @param rolloutId of the rollout to be stopped
* @return stopped rollout
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
Rollout stop(long rolloutId);
/**
* Deletes a rollout. A rollout might be deleted asynchronously by
* indicating the rollout by {@link RolloutStatus#DELETING}
@@ -372,7 +381,7 @@ public interface RolloutManagement extends PermissionSupport {
* canceled
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void cancelRolloutsForDistributionSet(DistributionSet set);
void cancelRolloutsForDistributionSet(DistributionSet set, ActionCancellationType cancelationType);
/**
* Triggers next group of a rollout for processing even success threshold
@@ -385,6 +394,15 @@ public interface RolloutManagement extends PermissionSupport {
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void triggerNextGroup(long rolloutId);
/**
* Cancels all actions that refer to a given rollout.
*
* @param cancelationType - type of cancellation - FORCE or SOFT (NONE is ignored)
* @param rollout - the rollout which actions are about to be cancelled
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void cancelActiveActionsForRollouts(final Rollout rollout, final ActionCancellationType cancelationType);
@SuperBuilder
@Getter
@EqualsAndHashCode(callSuper = true)

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.model;
/**
* Defines if and how actions should be canceled when :
* - invalidating a distribution set
* - stopping a rollout
* - deleting a rollout
*/
public enum ActionCancellationType {
/**
* will perform a FORCE action cancellation - will put them in CANCELED state.
*/
FORCE,
/**
* will perform a SOFT action cancellation - will put them in CANCELING state.
*/
SOFT,
/**
* Used in distribution set invalidation - will ONLY invalidate the DS, will not change action status
*/
NONE
}

View File

@@ -20,28 +20,16 @@ import lombok.Data;
public class DistributionSetInvalidation {
private Collection<Long> distributionSetIds;
private CancelationType cancelationType;
private boolean cancelRollouts;
private ActionCancellationType actionCancellationType;
/**
* Parametric constructor
*
* @param distributionSetIds defines which distribution sets should be canceled
* @param cancelationType defines if actions should be canceled
* @param cancelRollouts defines if rollouts should be canceled
* @param actionCancellationType defines if actions should be canceled
*/
public DistributionSetInvalidation(final Collection<Long> distributionSetIds, final CancelationType cancelationType,
final boolean cancelRollouts) {
public DistributionSetInvalidation(final Collection<Long> distributionSetIds, final ActionCancellationType actionCancellationType) {
this.distributionSetIds = distributionSetIds;
this.cancelationType = cancelationType;
this.cancelRollouts = cancelRollouts;
}
/**
* Defines if and how actions should be canceled when invalidating a
* distribution set
*/
public enum CancelationType {
FORCE, SOFT, NONE
this.actionCancellationType = actionCancellationType;
}
}

View File

@@ -54,7 +54,9 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
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.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -288,10 +290,19 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return;
}
// set soft delete
rollout.setStatus(RolloutStatus.DELETED);
rollout.setDeleted(true);
rolloutRepository.save(rollout);
finishRolloutGroups(rollout);
rolloutManagement.cancelActiveActionsForRollouts(rollout, ActionCancellationType.FORCE);
entityManager.flush();
boolean hasActiveActionsLeft = actionRepository.countByRolloutIdAndActive(rollout.getId(), true) > 0;
log.trace("rollout {} has active actions left : {} ", rollout.getId(), hasActiveActionsLeft);
if (!hasActiveActionsLeft) {
// set soft delete
rollout.setStatus(RolloutStatus.DELETED);
rollout.setDeleted(true);
rolloutRepository.save(rollout);
}
}
private void handleStopRollout(final JpaRollout rollout) {
@@ -309,18 +320,20 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return;
}
rolloutGroupRepository.findByRolloutAndStatusNotIn(rollout, List.of(RolloutGroupStatus.FINISHED, RolloutGroupStatus.ERROR))
.forEach(rolloutGroup -> {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup);
});
finishRolloutGroups(rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
// Soft cancel all active rollouts actions
rolloutManagement.cancelActiveActionsForRollouts(rollout, ActionCancellationType.SOFT);
// check if all actions are non-active and then finish or finish once all are processed.
boolean hasActiveActions = actionRepository.countByRolloutIdAndActiveAndStatusNot(rollout.getId(), true, Status.CANCELING) > 0;
if (!hasActiveActions) {
rollout.setStatus(RolloutStatus.STOPPED);
rolloutRepository.save(rollout);
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId).toList();
afterCommit.afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutStoppedEvent(
tenantAware.getCurrentTenant(), rollout.getId(), groupIds)));
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId).toList();
afterCommit.afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutStoppedEvent(
tenantAware.getCurrentTenant(), rollout.getId(), groupIds)));
}
}
private void handleReadyRollout(final Rollout rollout) {
@@ -392,6 +405,14 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}
}
private void finishRolloutGroups(final JpaRollout rollout) {
rolloutGroupRepository.findByRolloutAndStatusNotIn(rollout, List.of(RolloutGroupStatus.FINISHED, RolloutGroupStatus.ERROR))
.forEach(rolloutGroup -> {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup);
});
}
private Slice<JpaAction> findScheduledActionsByRollout(final JpaRollout rollout) {
return actionRepository.findByRolloutIdAndStatus(PageRequest.of(0, TRANSACTION_ACTIONS), rollout.getId(),
Status.SCHEDULED);

View File

@@ -74,11 +74,11 @@ import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
@@ -239,7 +239,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action);
onlineDsAssignmentStrategy.cancelAssignment(action);
onlineDsAssignmentStrategy.sendCancellationMessage(action);
return saveAction;
} else {
@@ -516,7 +516,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional
public void cancelActionsForDistributionSet(final CancelationType cancelationType, final DistributionSet distributionSet) {
public void cancelActionsForDistributionSet(final ActionCancellationType cancelationType, final DistributionSet distributionSet) {
actionRepository.findAll(ActionSpecifications.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING))
.forEach(action -> {
try {
@@ -529,7 +529,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
log.trace("Could not cancel action {} due to entity not found exception.", action.getId(), e);
}
});
if (cancelationType == CancelationType.FORCE) {
if (cancelationType == ActionCancellationType.FORCE) {
actionRepository.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId())).forEach(action -> {
try {
assertTargetUpdateAllowed(action);
@@ -544,10 +544,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
}
protected ActionRepository getActionRepository() {
return actionRepository;
}
protected boolean isActionsAutocloseEnabled() {
return getConfigValue(REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class);
}
@@ -1001,4 +997,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
throw new EntityNotFoundException(Action.class, actionId);
}
}
private Page<JpaAction> findActiveActionsForRollout(long rolloutId, Pageable pageable) {
return actionRepository
.findAll(ActionSpecifications.byRolloutIdAndActive(rolloutId), pageable);
}
}

View File

@@ -25,9 +25,9 @@ import org.eclipse.hawkbit.repository.exception.StopRolloutException;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
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.TargetFilterQuery;
import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -80,7 +80,7 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) {
log.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds());
final String tenant = tenantAware.getCurrentTenant();
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(), distributionSetInvalidation.isCancelRollouts())) {
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getActionCancellationType())) {
final String handlerId = JpaRolloutManagement.createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId);
try {
@@ -108,30 +108,30 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
final DistributionSetInvalidation distributionSetInvalidation) {
return systemSecurityContext.runAsSystem(() -> {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getActionCancellationType()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds,
distributionSetInvalidation.getCancelationType());
distributionSetInvalidation.getActionCancellationType());
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
});
}
private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
final boolean cancelRollouts) {
return cancelationType != CancelationType.NONE || cancelRollouts;
private static boolean shouldRolloutsBeCanceled(final ActionCancellationType cancelationType) {
return cancelationType != ActionCancellationType.NONE;
}
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation, final String tenant) {
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
final String tenant) {
DeploymentHelper.runInNewTransaction(txManager, tenant + "-invalidateDS", status -> {
distributionSetInvalidation.getDistributionSetIds().forEach(setId -> invalidateDistributionSet(
setId, distributionSetInvalidation.getCancelationType(), distributionSetInvalidation.isCancelRollouts()));
distributionSetInvalidation.getDistributionSetIds().forEach(setId -> invalidateDistributionSet(setId,
distributionSetInvalidation.getActionCancellationType()));
return 0;
});
}
private void invalidateDistributionSet(final long setId, final CancelationType cancelationType, final boolean cancelRollouts) {
private void invalidateDistributionSet(final long setId, final ActionCancellationType cancelationType) {
final DistributionSet distributionSet = distributionSetManagement.getOrElseThrowException(setId);
if (!distributionSet.isComplete()) {
throw new IncompleteDistributionSetException(
@@ -141,18 +141,18 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
log.debug("Distribution set {} marked as invalid.", setId);
// rollout cancellation should only be permitted with UPDATE_ROLLOUT permission
if (shouldRolloutsBeCanceled(cancelationType, cancelRollouts)) {
if (shouldRolloutsBeCanceled(cancelationType)) {
log.debug("Cancel rollouts after ds invalidation. ID: {}", setId);
rolloutManagement.cancelRolloutsForDistributionSet(distributionSet);
rolloutManagement.cancelRolloutsForDistributionSet(distributionSet, cancelationType);
}
if (cancelationType != ActionCancellationType.NONE) {
log.debug("Cancel actions after ds invalidation. ID: {}", setId);
deploymentManagement.cancelActionsForDistributionSet(cancelationType, distributionSet);
}
// Do run as system to ensure all actions (even invisible) are canceled due to invalidation.
systemSecurityContext.runAsSystem(() -> {
if (cancelationType != CancelationType.NONE) {
log.debug("Cancel actions after ds invalidation. ID: {}", setId);
deploymentManagement.cancelActionsForDistributionSet(cancelationType, distributionSet);
}
log.debug("Cancel auto assignments after ds invalidation. ID: {}", setId);
targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId);
return null;
@@ -167,11 +167,11 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
return setIds.stream().mapToLong(targetFilterQueryManagement::countByAutoAssignDistributionSetId).sum();
}
private long countActionsForInvalidation(final Collection<Long> setIds, final CancelationType cancelationType) {
private long countActionsForInvalidation(final Collection<Long> setIds, final ActionCancellationType cancelationType) {
long affectedActionsByDSInvalidation = 0;
if (cancelationType == CancelationType.FORCE) {
if (cancelationType == ActionCancellationType.FORCE) {
affectedActionsByDSInvalidation = countActionsForForcedInvalidation(setIds);
} else if (cancelationType == CancelationType.SOFT) {
} else if (cancelationType == ActionCancellationType.SOFT) {
affectedActionsByDSInvalidation = countActionsForSoftInvalidation(setIds);
}
return affectedActionsByDSInvalidation;

View File

@@ -19,6 +19,8 @@ import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
@@ -30,6 +32,7 @@ import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.RolloutFields;
@@ -46,22 +49,30 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -75,6 +86,9 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.ObjectCopyUtil;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
@@ -106,6 +120,10 @@ public class JpaRolloutManagement implements RolloutManagement {
RolloutStatus.CREATING, RolloutStatus.READY, RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.STARTING, RolloutStatus.RUNNING,
RolloutStatus.PAUSED, RolloutStatus.APPROVAL_DENIED);
@Value("${org.eclipse.hawkbit.repository.jpa.management.rollout.max.actions.per.transaction:5000}")
private int MAX_ACTIONS;
private final EntityManager entityManager;
private final RolloutRepository rolloutRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final RolloutApprovalStrategy rolloutApprovalStrategy;
@@ -113,42 +131,55 @@ public class JpaRolloutManagement implements RolloutManagement {
private final RolloutStatusCache rolloutStatusCache;
private final ActionRepository actionRepository;
private final TargetManagement<? extends Target> targetManagement;
private final ActionStatusRepository actionStatusRepository;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final TenantAware tenantAware;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final QuotaManagement quotaManagement;
private final AfterTransactionCommitExecutor afterCommit;
private final SystemSecurityContext systemSecurityContext;
private final ContextAware contextAware;
private final RepositoryProperties repositoryProperties;
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
protected JpaRolloutManagement(
final EntityManager entityManager,
final RolloutRepository rolloutRepository,
final RolloutGroupRepository rolloutGroupRepository,
final RolloutApprovalStrategy rolloutApprovalStrategy,
final StartNextGroupRolloutGroupSuccessAction startNextRolloutGroupAction,
final RolloutStatusCache rolloutStatusCache,
final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository,
final TargetRepository targetRepository,
final TargetManagement<? extends Target> targetManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final TenantAware tenantAware,
final TenantConfigurationManagement tenantConfigurationManagement,
final QuotaManagement quotaManagement,
final AfterTransactionCommitExecutor afterCommit,
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware,
final RepositoryProperties repositoryProperties) {
this.entityManager = entityManager;
this.rolloutRepository = rolloutRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.startNextRolloutGroupAction = startNextRolloutGroupAction;
this.rolloutStatusCache = rolloutStatusCache;
this.actionRepository = actionRepository;
this.actionStatusRepository = actionStatusRepository;
this.targetManagement = targetManagement;
this.distributionSetManagement = distributionSetManagement;
this.tenantAware = tenantAware;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.quotaManagement = quotaManagement;
this.afterCommit = afterCommit;
this.systemSecurityContext = systemSecurityContext;
this.contextAware = contextAware;
this.repositoryProperties = repositoryProperties;
this.onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, actionRepository, actionStatusRepository,
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties);
}
public static String createRolloutLockKey(final String tenant) {
@@ -409,27 +440,49 @@ public class JpaRolloutManagement implements RolloutManagement {
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long rolloutId) {
public Rollout stop(long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
if (RolloutStatus.DELETING == jpaRollout.getStatus()) {
return;
if (!ROLLOUT_STATUS_STOPPABLE.contains(jpaRollout.getStatus())) {
log.debug("Failed to stop rollout {} because it is in {} status.", rolloutId, jpaRollout.getStatus());
throw new RolloutIllegalStateException("Rollout can only be stopped into the following statuses " + ROLLOUT_STATUS_STOPPABLE);
}
jpaRollout.setStatus(RolloutStatus.DELETING);
rolloutRepository.save(jpaRollout);
log.debug("Stopping Rollout {}", jpaRollout.getId());
jpaRollout.setStatus(RolloutStatus.STOPPING);
return rolloutRepository.save(jpaRollout);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set) {
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
this.delete0(jpaRollout);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set, final ActionCancellationType cancelationType) {
// stop all rollouts for this distribution set
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
jpaRollout.setStatus(RolloutStatus.STOPPING);
rolloutRepository.save(jpaRollout);
log.debug("Rollout {} stopped", jpaRollout.getId());
});
if (cancelationType.equals(ActionCancellationType.SOFT)) {
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
jpaRollout.setStatus(RolloutStatus.STOPPING);
rolloutRepository.save(jpaRollout);
log.debug("Rollout {} stopping", jpaRollout.getId());
});
} else if (cancelationType.equals(ActionCancellationType.FORCE)) {
// Use same status filter here like in the soft case ? Seems they make sense
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
this.delete0(jpaRollout);
log.debug("Rollout {} deleting", jpaRollout.getId());
});
}
}
@Override
@@ -459,6 +512,124 @@ public class JpaRolloutManagement implements RolloutManagement {
startNextRolloutGroupAction.exec(rollout, latestRunning);
}
@Override
@Transactional
public void cancelActiveActionsForRollouts(Rollout rollout, ActionCancellationType cancelationType) {
// check cancellation type
if (ActionCancellationType.FORCE.equals(cancelationType)) {
forceQuitActionsOfRollout(rollout);
} else if (ActionCancellationType.SOFT.equals(cancelationType)) {
softCancelActionsOfRollout(rollout);
}
}
private void softCancelActionsOfRollout(final Rollout rollout) {
final List<JpaAction> actions = actionRepository.findAll(
ActionSpecifications
.byRolloutIdAndActiveAndStatusIsNot(rollout.getId(),
List.of(Action.Status.CANCELING)), // avoid cancelling state here, because it is count as still active
Pageable.ofSize(MAX_ACTIONS))
.getContent();
log.info("Found {} active actions for rollout {}, performing soft cancel.", actions.size(), rollout.getId());
storeActionsAndStatuses(actions, Action.Status.CANCELING);
// send cancellation messages to event publisher
onlineDsAssignmentStrategy.sendCancellationMessages(actions, tenantAware.getCurrentTenant());
}
private void forceQuitActionsOfRollout(final Rollout rollout) {
final List<JpaAction> actions = findActiveActionsForRollout(rollout.getId(), Pageable.ofSize(MAX_ACTIONS))
.getContent();
log.info("Found {} active actions for rollout {}", actions.size(), rollout.getId());
storeActionsAndStatuses(actions, Action.Status.CANCELED);
// find next active actions - filter by targetId list and isActive
final List<Long> targetIds = actions.stream()
.map(action -> action.getTarget().getId())
.toList();
entityManager.flush();
int modifiedRows = updateTargetAssignedDsWithFirstActiveAction(targetIds);
log.debug("Updated {} targets with their previously active action", modifiedRows);
// if no active actions
// set assignedDs to previously installedDs and status to IN_SYNC
// otherwise set assigned ds to the active action ...
modifiedRows = updateTargetAssignedDsWithInstalledIfNoActiveActions(targetIds);
log.debug("Updated assignDs to previously installed to {} number of targets.", modifiedRows);
}
private void storeActionsAndStatuses(List<JpaAction> actions, Action.Status status) {
final List<JpaActionStatus> cancellingStatuses = new ArrayList<>(actions.size());
final long currentTimestamp = System.currentTimeMillis();
final boolean active = Action.Status.CANCELING.equals(status);
final String typeOfCancellation = active ? "cancellation" : "force quit";
actions.forEach(action -> {
action.setStatus(status);
action.setActive(active);
JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setStatus(status);
actionStatus.setOccurredAt(currentTimestamp);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "A " + typeOfCancellation + " has been performed by server.");
cancellingStatuses.add(actionStatus);
});
actionStatusRepository.saveAll(cancellingStatuses);
actionRepository.saveAll(actions);
}
private int updateTargetAssignedDsWithFirstActiveAction(List<Long> targetIds) {
final Query updateQuery = entityManager.createNativeQuery(
"UPDATE sp_target t " +
"SET t.assigned_distribution_set = ( " +
"SELECT a.distribution_set" +
" FROM sp_action a" +
" WHERE a.target = t.id AND a.active = 1" +
" ORDER BY a.id ASC" +
" LIMIT 1" +
") " +
"WHERE t.id IN (" + Jpa.formatNativeQueryInClause("tid", targetIds) + ")"
);
Jpa.setNativeQueryInParameter(updateQuery, "tid", targetIds);
final int updated = updateQuery.executeUpdate();
log.info("{} of target assigned distribution values updated for tenant {}",
updated, tenantAware.getCurrentTenant());
return updated;
}
private int updateTargetAssignedDsWithInstalledIfNoActiveActions(List<Long> targetIds) {
final Query updateQuery = entityManager.createNativeQuery(
"UPDATE sp_target t " +
"SET t.assigned_distribution_set = t.installed_distribution_set, t.update_status = 1 " +
"WHERE t.id IN (" + Jpa.formatNativeQueryInClause("tid", targetIds) + ") " +
" AND (SELECT count(*) FROM sp_action a " +
" WHERE a.target=t.id and a.active=1) = 0"
);
Jpa.setNativeQueryInParameter(updateQuery, "tid", targetIds);
final int updated = updateQuery.executeUpdate();
log.info("{} of target assigned distribution set to previously installed distribution value for tenant {}",
updated, tenantAware.getCurrentTenant());
return updated;
}
private Page<JpaAction> findActiveActionsForRollout(long rolloutId, Pageable pageable) {
return actionRepository
.findAll(ActionSpecifications.byRolloutIdAndActive(rolloutId), pageable);
}
private void delete0(final JpaRollout jpaRollout) {
if (RolloutStatus.DELETING == jpaRollout.getStatus()) {
return;
}
jpaRollout.setStatus(RolloutStatus.DELETING);
rolloutRepository.save(jpaRollout);
}
private Page<Rollout> appendStatusDetails(final Page<Rollout> rollouts) {
final List<Long> rolloutIds = rollouts.getContent().stream().map(Rollout::getId).toList();
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(rolloutIds);
@@ -851,5 +1022,13 @@ public class JpaRolloutManagement implements RolloutManagement {
return new TargetCount(totalTargets, baseFilter);
}
private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
}
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isConfirmationFlowEnabled();
}
private record TargetCount(long total, String filter) {}
}

View File

@@ -176,7 +176,7 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
}
void cancelAssignment(final JpaAction action) {
void sendCancellationMessage(final JpaAction action) {
if (isMultiAssignmentsEnabled()) {
sendMultiActionCancelEvent(action);
} else {
@@ -184,6 +184,14 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
}
void sendCancellationMessages(final List<JpaAction> actions, final String tenant) {
if(isMultiAssignmentsEnabled()) {
sendMultiActionCancelEvent(tenant, Collections.unmodifiableList(actions));
} else {
actions.forEach(this::cancelAssignDistributionSetEvent);
}
}
private static Stream<Action> filterCancellations(final List<Action> actions) {
return actions.stream().filter(action -> {
final Status actionStatus = action.getStatus();

View File

@@ -183,6 +183,25 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
*/
Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status);
/**
* Returns the number of active/non-active actions for a rollout
*
* @param rolloutId - the ID of the rollout the actions belong to
* @param active - wether the actions should be active or not
* @return number of actions which match the criteria
*/
Long countByRolloutIdAndActive(Long rolloutId, boolean active);
/**
* Returns the number of active/non-active actions for a rollout which status is not the provided one.
*
* @param rolloutId - the ID of the rollout the actions belong to
* @param active - wether the actions should be active or not
* @param status - the status that matched actions should not be.
* @return number of actions which match the criteria
*/
Long countByRolloutIdAndActiveAndStatusNot(Long rolloutId, boolean active, Action.Status status);
/**
* Returns {@code true} if actions for the given rollout exists, otherwise {@code false}
* <p/>

View File

@@ -28,6 +28,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
/**
* Utility class for {@link Action}s {@link Specification}s. The class provides Spring Data JPQL Specifications.
*/
@@ -84,6 +86,21 @@ public final class ActionSpecifications {
cb.equal(root.get(JpaAction_.active), true));
}
public static Specification<JpaAction> byRolloutIdAndActive(final Long rolloutId) {
return (root, query, cb) -> cb.and(
cb.equal(root.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rolloutId),
cb.equal(root.get(JpaAction_.active), true)
);
}
public static Specification<JpaAction> byRolloutIdAndActiveAndStatusIsNot(final Long rolloutId, final List<Action.Status> statuses) {
return (root, query, cb) -> cb.and(
cb.equal(root.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rolloutId),
cb.equal(root.get(JpaAction_.active), true),
cb.not(root.get(JpaAction_.status).in(statuses))
);
}
public static Specification<JpaAction> byDistributionSetIdAndActiveAndStatusIsNot(final Long distributionSetId, final Action.Status status) {
return (root, query, cb) -> cb.and(
cb.equal(root.get(JpaAction_.distributionSet).get(AbstractJpaBaseEntity_.id), distributionSetId),

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);
}

View File

@@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
@@ -59,7 +60,6 @@ import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
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.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
@@ -1082,6 +1082,19 @@ public class TestdataFactory {
createDistributionSet(prefix), "50", "5");
}
/**
* Create {@link Rollout} with a new {@link DistributionSet} and {@link Target}s.
*
* @return created {@link Rollout}
*/
public Rollout createAndStartRollout() {
return startAndReloadRollout(createRollout());
}
public Rollout startRollout(final Rollout rollout) {
return startAndReloadRollout(rollout);
}
/**
* Create the data for a simple rollout scenario
*
@@ -1212,7 +1225,7 @@ public class TestdataFactory {
public DistributionSet createAndInvalidateDistributionSet() {
final DistributionSet distributionSet = createDistributionSet();
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(List.of(distributionSet.getId()), CancelationType.NONE, false));
new DistributionSetInvalidation(List.of(distributionSet.getId()), ActionCancellationType.NONE));
return distributionSetManagement.get(distributionSet.getId()).orElseThrow();
}