Rollout retry (#1454)

* Rollout retry mechanism initial commit

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

* Remove test target fields for filter query

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

* minor refactoring

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

* Fixes after review

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

* more refactoring after review

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

* skip compatibility check of dstype for retried rollout

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

* remove dsType from javadoc

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

---------

Signed-off-by: Stanislav Trailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
Stanislav Trailov
2023-10-19 09:58:46 +03:00
committed by GitHub
parent 22ce1c27a4
commit 44e7a72be3
11 changed files with 331 additions and 16 deletions

View File

@@ -139,6 +139,21 @@ public interface TargetManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByRsqlAndCompatible(@NotEmpty String rsqlParam, @NotNull Long dsTypeId);
/**
* Count all targets with failed actions for specific Rollout
* and that are compatible with the passed {@link DistributionSetType}
* and created after given timestamp
*
* @param rolloutId
* rolloutId of the rollout to be retried.
* @param dsTypeId
* ID of the {@link DistributionSetType} the targets need to be
* compatible with
* @return the found number of{@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByFailedInRollout(@NotEmpty String rolloutId, @NotNull Long dsTypeId);
/**
* Count {@link TargetFilterQuery}s for given target filter query.
*
@@ -278,6 +293,23 @@ public interface TargetManagement {
@NotEmpty Collection<Long> groups, @NotNull String rsqlParam,
@NotNull DistributionSetType distributionSetType);
/**
* Finds all targets with failed actions for specific Rollout
* and that are not assigned to one of the retried {@link RolloutGroup}s and are
* compatible with the passed {@link DistributionSetType}.
*
* @param pageRequest
* the pageRequest to enhance the query for paging and sorting
* @param groups
* the list of {@link RolloutGroup}s
* @param rolloutId
* rolloutId of the rollout to be retried.
* @return a page of the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Slice<Target> findByFailedRolloutAndNotInRolloutGroups(@NotNull Pageable pageRequest,
@NotEmpty Collection<Long> groups, @NotNull String rolloutId);
/**
* Counts all targets for all the given parameter {@link TargetFilterQuery}
* and that are not assigned to one of the {@link RolloutGroup}s and are
@@ -296,6 +328,20 @@ public interface TargetManagement {
long countByRsqlAndNotInRolloutGroupsAndCompatible(@NotEmpty Collection<Long> groups, @NotNull String rsqlParam,
@NotNull DistributionSetType distributionSetType);
/**
* Counts all targets with failed actions for specific Rollout
* and that are not assigned to one of the {@link RolloutGroup}s and are
* compatible with the passed {@link DistributionSetType}.
*
* @param groups
* the list of {@link RolloutGroup}s
* @param rolloutId
* rolloutId of the rollout to be retried.
* @return count of the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByFailedRolloutAndNotInRolloutGroups(@NotEmpty Collection<Long> groups, @NotNull String rolloutId);
/**
* Finds all targets of the provided {@link RolloutGroup} that have no
* Action for the RolloutGroup.

View File

@@ -229,6 +229,9 @@ public final class RolloutHelper {
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
return baseFilter;
}
if (isRolloutRetried(baseFilter)) {
return baseFilter;
}
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
}
@@ -253,4 +256,12 @@ public final class RolloutHelper {
+ rollout.getStatus().name().toLowerCase());
}
}
public static boolean isRolloutRetried(final String targetFilter) {
return targetFilter.contains("failedrollout");
}
public static String getIdFromRetriedTargetFilter(final String targetFilter) {
return targetFilter.substring("failedrollout==".length());
}
}

View File

@@ -525,10 +525,18 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
RolloutGroupStatus.READY, group);
final long targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
long targetsInGroupFilter;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatible(readyGroups, groupTargetFilter,
rollout.getDistributionSet().getType()));
rollout.getDistributionSet().getType()));
} else {
targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
"countByFailedRolloutAndNotInRolloutGroupsAndCompatible",
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(readyGroups,
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery())));
}
final long expectedInGroup = Math
.round((double) (group.getTargetPercentage() / 100) * (double) targetsInGroupFilter);
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
@@ -572,8 +580,14 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
RolloutGroupStatus.READY, group);
final Slice<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(
Slice<Target> targets;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
} else {
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(
pageRequest, readyGroups, RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()));
}
createAssignmentOfTargetsToGroup(targets, group);

View File

@@ -196,10 +196,20 @@ public class JpaRolloutManagement implements RolloutManagement {
private JpaRollout createRollout(final JpaRollout rollout) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final Long totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(),
long totalTargets;
String errMsg;
if (RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
totalTargets = targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()),
rollout.getDistributionSet().getType().getId());
errMsg = "No failed targets in Rollout";
} else {
totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(),
rollout.getDistributionSet().getType().getId());
errMsg = "Rollout does not match any existing targets";
}
if (totalTargets == 0) {
throw new ValidationException("Rollout does not match any existing targets");
throw new ValidationException(errMsg);
}
rollout.setTotalTargets(totalTargets);
return rolloutRepository.save(rollout);
@@ -618,10 +628,19 @@ public class JpaRolloutManagement implements RolloutManagement {
private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets, final Long dsTypeId) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
final Map<String, Long> targetFilterCounts = groups.stream()
Map<String, Long> targetFilterCounts;
if (!RolloutHelper.isRolloutRetried(baseFilter)) {
targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(),
groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId)));
groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId)));
} else {
targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(),
groupTargetFilter -> targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(baseFilter), dsTypeId)));
}
long unusedTargetsCount = 0;
@@ -675,8 +694,11 @@ public class JpaRolloutManagement implements RolloutManagement {
private long calculateRemainingTargets(final List<RolloutGroup> groups, final String targetFilter,
final Long createdAt, final Long dsTypeId) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId);
final TargetCount targets = calculateTargets(targetFilter, createdAt, dsTypeId);
long totalTargets = targets.total();
final String baseFilter = targets.filter();
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
@@ -691,9 +713,9 @@ public class JpaRolloutManagement implements RolloutManagement {
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
final String targetFilter, final Long createdAt, final Long dsTypeId) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId);
final TargetCount targets = calculateTargets(targetFilter, createdAt, dsTypeId);
long totalTargets = targets.total();
final String baseFilter = targets.filter();
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
@@ -730,4 +752,21 @@ public class JpaRolloutManagement implements RolloutManagement {
startNextRolloutGroupAction.exec(rollout, latestRunning);
}
private TargetCount calculateTargets(final String targetFilter, final Long createdAt, final Long dsTypeId) {
String baseFilter;
long totalTargets;
if (!RolloutHelper.isRolloutRetried(targetFilter)) {
baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId);
} else {
totalTargets = targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(targetFilter), dsTypeId);
baseFilter = targetFilter;
}
return new TargetCount(totalTargets, baseFilter);
}
private record TargetCount(long total, String filter) {}
}

View File

@@ -693,6 +693,17 @@ public class JpaTargetManagement implements TargetManagement {
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageRequest, specList);
}
@Override
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups,
String rolloutId) {
final List<Specification<JpaTarget>> specList = Arrays.asList(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups)
);
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageRequest, specList);
}
@Override
public Slice<Target> findByInRolloutGroupWithoutAction(final Pageable pageRequest, final long group) {
if (!rolloutGroupRepository.existsById(group)) {
@@ -715,6 +726,15 @@ public class JpaTargetManagement implements TargetManagement {
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public long countByFailedRolloutAndNotInRolloutGroups(Collection<Long> groups, String rolloutId) {
final List<Specification<JpaTarget>> specList = Arrays.asList(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public long countByRsqlAndNonDSAndCompatible(final long distributionSetId, final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
@@ -796,6 +816,14 @@ public class JpaTargetManagement implements TargetManagement {
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public long countByFailedInRollout(final String rolloutId, final Long dsTypeId) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId));
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public Optional<Target> get(final long id) {
return targetRepository.findById(id).map(t -> t);

View File

@@ -42,8 +42,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
@@ -611,4 +613,15 @@ public final class TargetSpecifications {
};
}
public static Specification<JpaTarget> failedActionsForRollout(final String rolloutId) {
return (targetRoot, query, cb) -> {
Join<JpaTarget, Action> targetActions =
targetRoot.join("actions");
return cb.and(
cb.equal(targetActions.get("rollout").get("id"), rolloutId),
cb.equal(targetActions.get("status"), Action.Status.ERROR));
};
}
}

View File

@@ -404,4 +404,28 @@ public interface MgmtRolloutRestApi {
@PostMapping(value = MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}/triggerNextGroup", produces = {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> triggerNextGroup(@PathVariable("rolloutId") Long rolloutId);
/**
* Handles the POST request to retry a rollout
*
* @param rolloutId
* the ID of the rollout to be retried.
* @return OK response (200). In case of any exception the corresponding
* errors occur.
*/
@Operation(summary = "Retry a rollout", description = "Handles the POST request of retrying a rollout. Required Permission: CREATE_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}/retry", produces = {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE})
ResponseEntity<MgmtRolloutResponseBody> retryRollout(@PathVariable("rolloutId") final String rolloutId);
}

View File

@@ -132,6 +132,18 @@ final class MgmtRolloutMapper {
.weight(restRequest.getWeight());
}
static RolloutCreate fromRetriedRollout(final EntityFactory entityFactory, final Rollout rollout) {
return entityFactory.rollout().create()
.name(rollout.getName().concat("_retry"))
.description(rollout.getDescription())
.set(rollout.getDistributionSet())
.targetFilterQuery("failedrollout==".concat(String.valueOf(rollout.getId())))
.actionType(rollout.getActionType())
.forcedTime(rollout.getForcedTime())
.startAt(rollout.getStartAt())
.weight(null);
}
static RolloutGroupCreate fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) {
return entityFactory.rolloutGroup().create().name(restRequest.getName())

View File

@@ -31,6 +31,7 @@ import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
@@ -39,6 +40,7 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -47,7 +49,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -311,6 +312,28 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtRolloutResponseBody> retryRollout(final String rolloutId) {
final Rollout rolloutForRetry = this.rolloutManagement.get(Long.parseLong(rolloutId))
.orElseThrow(EntityNotFoundException::new);
if (rolloutForRetry.isDeleted()) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
if (!rolloutForRetry.getStatus().equals(Rollout.RolloutStatus.FINISHED)) {
throw new ValidationException("Rollout must be finished in order to be retried!");
}
final RolloutCreate create = MgmtRolloutMapper.fromRetriedRollout(entityFactory, rolloutForRetry);
final RolloutGroupConditions groupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
final Rollout retriedRollout = rolloutManagement.create(create, 1, false,
groupConditions);
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(retriedRollout, true));
}
private static MgmtRepresentationMode parseRepresentationMode(final String representationModeParam) {
return MgmtRepresentationMode.fromValue(representationModeParam).orElseGet(() -> {
// no need for a 400, just apply a safe fallback

View File

@@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
@@ -55,6 +56,7 @@ import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -1577,6 +1579,99 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
}
@Test
@Description("Retry rollout test scenario")
public void retryRolloutTest() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final List<Target> successTargets = testdataFactory.createTargets("retryRolloutTargetSuccess-", 6);
final List<Target> failedTargets = testdataFactory.createTargets("retryRolloutTargetFailed-", 4);
final List<Target> allTargets = new ArrayList<>(successTargets);
allTargets.addAll(failedTargets);
postRollout("rolloutToBeRetried", 1, dsA.getId(), "id==retryRolloutTarget*", 10, Action.ActionType.FORCED);
Rollout rollout = rolloutManagement.getByName("rolloutToBeRetried").orElseThrow();
// no scheduler so invoke here
rolloutHandler.handleAll();
rolloutManagement.start(rollout.getId());
// no scheduler so invoke here
rolloutHandler.handleAll();
testdataFactory.sendUpdateActionStatusToTargets(successTargets, Status.FINISHED, "Finished successfully!");
testdataFactory.sendUpdateActionStatusToTargets(failedTargets, Status.ERROR, "Finished error!");
rolloutHandler.handleAll();
for (Target target : allTargets) {
final List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
for (Action action : actions) {
if (action.getTarget().getControllerId().startsWith("retryRolloutTargetFailed")) {
Assertions.assertEquals(Status.ERROR, action.getStatus());
} else {
Assertions.assertEquals(Status.FINISHED, action.getStatus());
}
Assertions.assertEquals(rollout.getId(), action.getRollout().getId());
}
}
//retry rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/retry", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().is(201));
//search for _retried suffix
Rollout retriedRollout = rolloutManagement.getByName(rollout.getName() + "_retry").orElseThrow();
//assert 4 targets involved
rolloutHandler.handleAll();
rolloutManagement.start(retriedRollout.getId());
rolloutHandler.handleAll();
for (Target target : failedTargets) {
// for failed targets - check for 2 actions - one from old rollout and one from the retried
List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
Assertions.assertEquals(2, actions.size());
Assertions.assertEquals(Status.ERROR, actions.get(0).getStatus());
Assertions.assertEquals(rollout.getId(), actions.get(0).getRollout().getId());
Assertions.assertEquals(Status.RUNNING, actions.get(1).getStatus());
Assertions.assertEquals(retriedRollout.getId(), actions.get(1).getRollout().getId());
}
for (Target target : successTargets) {
//ensure no other actions from the success targets are created
List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
Assertions.assertEquals(1, actions.size());
Assertions.assertEquals(rollout.getId(), actions.get(0).getRollout().getId());
}
}
@Test
@Description("Retrying a running rollout should not be allowed.")
public void retryNotFinishedRolloutShouldNotBeAllowed() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
testdataFactory.createTargets("retryRolloutTarget-", 10);
postRollout("rolloutToBeRetried", 1, dsA.getId(), "id==retryRolloutTarget*", 10, Action.ActionType.FORCED);
Rollout rollout = rolloutManagement.getByName("rolloutToBeRetried").orElseThrow();
// no scheduler so invoke here
rolloutHandler.handleAll();
rolloutManagement.start(rollout.getId());
// no scheduler so invoke here
rolloutHandler.handleAll();
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/retry", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
}
@Test
@Description("Retrying a non-existing rollout should lead to NOT FOUND.")
public void retryNonExistingRolloutShouldLeadToNotFound() throws Exception {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/retry", 6782623)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
}
private void triggerNextGroupAndExpect(final Rollout rollout, final ResultMatcher expect) throws Exception {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/triggerNextGroup", rollout.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(expect);
@@ -1596,6 +1691,10 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
retrieveAndCompareRolloutsContent(dsA, urlTemplate, isFullRepresentation, false, null, null);
}
private Rollout getRollout(final long rolloutId) {
return rolloutManagement.get(rolloutId).orElseThrow(NoSuchElementException::new);
}
private void retrieveAndCompareRolloutsContent(final DistributionSet dsA, final String urlTemplate,
final boolean isFullRepresentation, final boolean isStartTypeScheduled, final Long startAt,
final Long forcetime) throws Exception {

View File

@@ -224,6 +224,10 @@ public class RolloutGrid extends AbstractGrid<ProxyRollout, String> {
return isDeletionAllowed(status) && status != RolloutStatus.CREATING;
}
private static boolean isRolloutRetried(final String targetFilter) {
return targetFilter.contains("failedrollout");
}
private static boolean isEditingAllowed(final RolloutStatus status) {
final List<RolloutStatus> statesThatAllowEditing = Arrays.asList(RolloutStatus.PAUSED, RolloutStatus.READY,
RolloutStatus.RUNNING, RolloutStatus.STARTING, RolloutStatus.STOPPED);
@@ -361,14 +365,16 @@ public class RolloutGrid extends AbstractGrid<ProxyRollout, String> {
clickEvent -> updateRollout(rollout), VaadinIcons.EDIT, UIMessageIdProvider.TOOLTIP_ROLLOUT_UPDATE,
SPUIStyleDefinitions.STATUS_ICON_NEUTRAL,
UIComponentIdProvider.ROLLOUT_UPDATE_BUTTON_ID + "." + rollout.getId(),
permissionChecker.hasRolloutUpdatePermission() && isEditingAllowed(rollout.getStatus()));
permissionChecker.hasRolloutUpdatePermission() && isEditingAllowed(rollout.getStatus())
&& !isRolloutRetried(rollout.getTargetFilterQuery()));
actionColumns.add(GridComponentBuilder.addIconColumn(this, updateButton, UPDATE_BUTTON_ID, null));
final ValueProvider<ProxyRollout, Button> copyButton = rollout -> GridComponentBuilder.buildActionButton(i18n,
clickEvent -> copyRollout(rollout), VaadinIcons.COPY, UIMessageIdProvider.TOOLTIP_ROLLOUT_COPY,
SPUIStyleDefinitions.STATUS_ICON_NEUTRAL,
UIComponentIdProvider.ROLLOUT_COPY_BUTTON_ID + "." + rollout.getId(),
permissionChecker.hasRolloutCreatePermission() && isCopyingAllowed(rollout.getStatus()));
permissionChecker.hasRolloutCreatePermission() && isCopyingAllowed(rollout.getStatus())
&& !isRolloutRetried(rollout.getTargetFilterQuery()));
actionColumns.add(GridComponentBuilder.addIconColumn(this, copyButton, COPY_BUTTON_ID, null));
actionColumns.add(GridComponentBuilder.addDeleteColumn(this, i18n, DELETE_BUTTON_ID, rolloutDeleteSupport,