Fix dynamic rollout override behavior (#2447)

Since static assigments of dynamic rollouts always override the oldest rollouts
it seems more consistent if this is the case also with dynamic assigments. I.e.
all older are overriden and if there are actions of newer rollouts - won't be assigned.

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-06-11 15:24:48 +03:00
committed by GitHub
parent 58b969db38
commit 4cfd90b745
6 changed files with 92 additions and 118 deletions

View File

@@ -91,13 +91,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
/**
* Action statuses that result in a terminated action
*/
private static final List<Status> DEFAULT_ACTION_TERMINATION_STATUSES = List.of(
Status.ERROR, Status.FINISHED, Status.CANCELED);
private static final List<Status> DEFAULT_ACTION_TERMINATION_STATUSES = List.of(Status.ERROR, Status.FINISHED, Status.CANCELED);
/**
* In case of DOWNLOAD_ONLY, actions can be finished with DOWNLOADED status.
*/
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = List.of(
Status.ERROR, Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES =
List.of(Status.ERROR, Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
private static final String TRANSACTION_ASSIGNING_TARGETS_TO_ROLLOUT_GROUP_FAILED = "Transaction assigning Targets to RolloutGroup failed";
@@ -647,7 +646,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
rollout.getRolloutGroups(), RolloutGroupStatus.READY, group);
final Slice<Target> targets;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutAndCompatibleAndUpdatable(
targets = targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
readyGroups, targetFilter, rollout.getDistributionSet().getType(), pageRequest);
} else {
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(
@@ -688,8 +687,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(
// don't use RolloutHelper.getTargetFilterQuery(rollout)
// since it contains condition for device to be created
// before the rollout
// since it contains condition for device to be created before the rollout
rollout.getTargetFilterQuery(), group);
long newActions = 0;
do {
@@ -723,8 +721,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return false;
}
private void createDynamicGroup(final JpaRollout rollout, final JpaRolloutGroup lastGroup, final int groupCount,
final RolloutGroupStatus status) {
private void createDynamicGroup(
final JpaRollout rollout, final JpaRolloutGroup lastGroup, final int groupCount, final RolloutGroupStatus status) {
try {
RolloutHelper.verifyRolloutGroupAmount(groupCount + 1, quotaManagement);
} catch (final AssignmentQuotaExceededException e) {
@@ -738,8 +736,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final JpaRolloutGroup group = new JpaRolloutGroup();
final String lastGroupWithoutSuffix = "group-" + groupCount;
final String suffix = lastGroup.getName().startsWith(lastGroupWithoutSuffix) ? lastGroup.getName()
.substring(lastGroupWithoutSuffix.length()) : "";
final String suffix = lastGroup.getName().startsWith(lastGroupWithoutSuffix)
? lastGroup.getName().substring(lastGroupWithoutSuffix.length())
: "";
final String nameAndDesc = "group-" + (groupCount + 1) + suffix;
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
@@ -767,15 +766,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
((JpaRolloutManagement) rolloutManagement).publishRolloutGroupCreatedEventAfterCommit(savedGroup, rollout);
}
private int createActionsForDynamicGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
final String targetFilter, final long limit) {
private int createActionsForDynamicGroupInNewTransaction(
final JpaRollout rollout, final RolloutGroup group, final String targetFilter, final long limit) {
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForRolloutDynamicGroup", status -> {
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
final Slice<Target> targets = targetManagement.findByTargetFilterQueryAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
rollout.getId(), rollout.getWeight().orElse(1000), rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout).get(0).getId(),
targetFilter, rollout.getDistributionSet().getType(), pageRequest
// Dynamic rollouts shall always have weight!
);
// Dynamic rollouts shall always have weight!
final Slice<Target> targets = targetManagement.findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
rollout.getId(), targetFilter, rollout.getDistributionSet().getType(), PageRequest.of(0, Math.toIntExact(limit)));
if (targets.getNumberOfElements() == 0) {
return 0;

View File

@@ -277,46 +277,18 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
public Slice<Target> findByTargetFilterQueryAndNotInRolloutAndCompatibleAndUpdatable(
final Collection<Long> groups, final String targetFilterQuery, final DistributionSetType dsType, final Pageable pageable) {
public Slice<Target> findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final Collection<Long> groups, final String rsql, final DistributionSetType dsType, final Pageable pageable) {
return targetRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database),
RSQLUtility.buildRsqlSpecification(rsql, TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))),
pageable)
.map(Target.class::cast);
}
@Override
public Slice<Target> findByTargetFilterQueryAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
final long rolloutId, final int weight, final long firstGroupId, final String targetFilterQuery,
final DistributionSetType distributionSetType, final Pageable pageable) {
return targetRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.hasNoOverridingActionsAndNotInRollout(weight, rolloutId),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetType.getId()))),
pageable)
.map(Target.class::cast);
}
@Override
public long countByActionsInRolloutGroup(final long rolloutGroupId) {
return targetRepository.count(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId));
}
@Override
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(String rolloutId, Collection<Long> groups, Pageable pageable) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, specList, pageable);
}
@Override
public long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final String rsql, final Collection<Long> groups, final DistributionSetType dsType) {
@@ -327,6 +299,14 @@ public class JpaTargetManagement implements TargetManagement {
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))));
}
@Override
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(String rolloutId, Collection<Long> groups, Pageable pageable) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, specList, pageable);
}
@Override
public long countByFailedRolloutAndNotInRolloutGroups(String rolloutId, Collection<Long> groups) {
final List<Specification<JpaTarget>> specList = List.of(
@@ -335,6 +315,24 @@ public class JpaTargetManagement implements TargetManagement {
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public Slice<Target> findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
final long rolloutId, final String rsql, final DistributionSetType distributionSetType, final Pageable pageable) {
return targetRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(rsql, TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.hasNoOverridingActionsAndNotInRollout(rolloutId),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetType.getId()))),
pageable)
.map(Target.class::cast);
}
@Override
public long countByActionsInRolloutGroup(final long rolloutGroupId) {
return targetRepository.count(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId));
}
@Override
public Slice<Target> findByInRolloutGroupWithoutAction(final long group, final Pageable pageable) {
if (!rolloutGroupRepository.existsById(group)) {

View File

@@ -173,8 +173,8 @@ public final class TargetSpecifications {
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> isOverdue(final long overdueTimestamp) {
return (targetRoot, query, cb) -> cb.lessThanOrEqualTo(targetRoot.get(JpaTarget_.lastTargetQuery),
overdueTimestamp);
return (targetRoot, query, cb) ->
cb.lessThanOrEqualTo(targetRoot.get(JpaTarget_.lastTargetQuery), overdueTimestamp);
}
/**
@@ -186,7 +186,8 @@ public final class TargetSpecifications {
public static Specification<JpaTarget> likeControllerIdOrName(final String searchText) {
return (targetRoot, query, cb) -> {
final String searchTextToLower = searchText.toLowerCase();
return cb.or(cb.like(cb.lower(targetRoot.get(JpaTarget_.controllerId)), searchTextToLower),
return cb.or(
cb.like(cb.lower(targetRoot.get(JpaTarget_.controllerId)), searchTextToLower),
cb.like(cb.lower(targetRoot.get(AbstractJpaNamedEntity_.name)), searchTextToLower));
};
}
@@ -251,8 +252,8 @@ public final class TargetSpecifications {
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasAssignedDistributionSet(final Long distributionSetId) {
return (targetRoot, query, cb) -> cb.equal(
targetRoot.get(JpaTarget_.assignedDistributionSet).get(AbstractJpaBaseEntity_.id), distributionSetId);
return (targetRoot, query, cb) ->
cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet).get(AbstractJpaBaseEntity_.id), distributionSetId);
}
/**
@@ -358,10 +359,8 @@ public final class TargetSpecifications {
*/
public static Specification<JpaTarget> hasNoActionInRolloutGroup(final Long group) {
return (targetRoot, query, cb) -> {
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot
.join(JpaTarget_.rolloutTargetGroup, JoinType.INNER);
rolloutTargetJoin.on(
cb.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup).get(AbstractJpaBaseEntity_.id), group));
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot.join(JpaTarget_.rolloutTargetGroup, JoinType.INNER);
rolloutTargetJoin.on(cb.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup).get(AbstractJpaBaseEntity_.id), group));
final ListJoin<JpaTarget, JpaAction> actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT);
actionsJoin.on(cb.equal(actionsJoin.get(JpaAction_.rolloutGroup).get(AbstractJpaBaseEntity_.id), group));
@@ -388,7 +387,6 @@ public final class TargetSpecifications {
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasTag(final Long tagId) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaTarget, JpaTargetTag> tags = targetRoot.join(JpaTarget_.tags, JoinType.LEFT);
return cb.equal(tags.get(AbstractJpaBaseEntity_.id), tagId);
@@ -435,31 +433,14 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s that have:
* <ul>
* <li>no active (non-finished) actions with greater weight in the older rollouts</li>
* <li>or have actions in the current rollout</li>
* <li>or have actions with great or equal weight in the newer rollouts</li>
* </ul>
* {@link Specification} for retrieving {@link Target}s that have no overriding actions - i.e. no actions from newer rollouts
*
* @param weight the referent weight
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasNoOverridingActionsAndNotInRollout(final int weight, final long rolloutId) {
public static Specification<JpaTarget> hasNoOverridingActionsAndNotInRollout(final long rolloutId) {
return (targetRoot, query, cb) -> {
final ListJoin<JpaTarget, JpaAction> actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT);
actionsJoin.on(
cb.or(
cb.and(
cb.and(
cb.lt(actionsJoin.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rolloutId),
cb.gt(actionsJoin.get(JpaAction_.weight), weight)),
cb.or(
cb.equal(actionsJoin.get(JpaAction_.active), true),
cb.equal(actionsJoin.get(JpaAction_.status), Action.Status.SCHEDULED))),
cb.and(
cb.ge(actionsJoin.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rolloutId),
cb.ge(actionsJoin.get(JpaAction_.weight), weight))));
actionsJoin.on(cb.ge(actionsJoin.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rolloutId));
return cb.isNull(actionsJoin.get(AbstractJpaBaseEntity_.id));
};
}
@@ -497,4 +478,4 @@ public final class TargetSpecifications {
final Join<JpaTarget, JpaTargetType> targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT);
return targetTypeJoin.join(JpaTargetType_.distributionSetTypes, JoinType.LEFT).get(AbstractJpaBaseEntity_.id);
}
}
}