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:
committed by
GitHub
parent
4566702030
commit
45cd012532
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user