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