Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollout retry #1454

Merged
merged 7 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -278,6 +293,23 @@ Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(@NotNull
@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
Expand All @@ -296,6 +328,20 @@ Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(@NotNull
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ public static String getGroupTargetFilter(final String baseFilter, final Rollout
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
return baseFilter;
}
if (isRolloutRetried(baseFilter)) {
return baseFilter;
}
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
}

Expand All @@ -253,4 +256,12 @@ public static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout
+ rollout.getStatus().name().toLowerCase());
}
}

public static boolean isRolloutRetried(final String targetFilter) {
return targetFilter.contains("failedrollout");
}

public static String getIdFromRetriedTargetFilter(final String targetFilter) {
avgustinmm marked this conversation as resolved.
Show resolved Hide resolved
return targetFilter.substring("failedrollout==".length());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -525,10 +525,18 @@ private RolloutGroup fillRolloutGroupWithTargets(final JpaRollout rollout, final
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,
Expand Down Expand Up @@ -572,8 +580,14 @@ private Long assignTargetsToGroupInNewTransaction(final JpaRollout rollout, fina
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,20 @@ public Rollout create(final RolloutCreate rollout, final List<RolloutGroupCreate

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);
Expand Down Expand Up @@ -618,10 +628,19 @@ public void cancelRolloutsForDistributionSet(final DistributionSet set) {
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)));
} else {
targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(),
groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId)));
groupTargetFilter -> targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(baseFilter), dsTypeId)));
}

long unusedTargetsCount = 0;

Expand Down Expand Up @@ -675,8 +694,11 @@ private long countOverlappingTargetsWithPreviousGroups(final String baseFilter,

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");
}
Expand All @@ -691,9 +713,9 @@ private long calculateRemainingTargets(final List<RolloutGroup> groups, final St
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");
Expand Down Expand Up @@ -730,4 +752,21 @@ public void triggerNextGroup(final long rolloutId) {
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) {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,17 @@ public Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(f
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)) {
Expand All @@ -715,6 +726,15 @@ public long countByRsqlAndNotInRolloutGroupsAndCompatible(final Collection<Long>
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);
Expand Down Expand Up @@ -796,6 +816,14 @@ public long countByRsqlAndCompatible(final String targetFilterQuery, final Long
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
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;
Expand Down Expand Up @@ -611,4 +613,15 @@ public static Specification<JpaTarget> orderedByLinkedDistributionSet(final long
};
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ ResponseEntity<Void> deny(@PathVariable("rolloutId") Long rolloutId,
* @param representationModeParam
* the representation mode parameter specifying whether a compact
* or a full representation shall be returned
*
*
* @return a list of all rollout groups referred to a rollout for a defined
* or default page request with status OK. The response is always
* paged. In any failure the JsonResponseExceptionHandler is
Expand Down Expand Up @@ -404,4 +404,28 @@ ResponseEntity<PagedList<MgmtTarget>> getRolloutGroupTargets(@PathVariable("roll
@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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ static RolloutCreate fromRequest(final EntityFactory entityFactory, final MgmtRo
.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())
Expand Down