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:
committed by
GitHub
parent
22ce1c27a4
commit
44e7a72be3
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user