Move rollout executor related target management methods in executor (#2812)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-11-18 08:44:35 +02:00
committed by GitHub
parent 068e7cef69
commit 20bb41c51c
17 changed files with 340 additions and 349 deletions

View File

@@ -473,11 +473,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final PageRequest pageRequest = PageRequest.of(0, ACTION_PAGE_LIMIT);
final Page<Action> groupScheduledActions;
if (rolloutGroupParentId == null) {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIsNullAndStatus(pageRequest, rolloutId,
Action.Status.SCHEDULED);
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIsNullAndStatus(
pageRequest, rolloutId, Action.Status.SCHEDULED);
} else {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIdAndStatus(pageRequest, rolloutId,
rolloutGroupParentId, Action.Status.SCHEDULED);
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIdAndStatus(
pageRequest, rolloutId, rolloutGroupParentId, Action.Status.SCHEDULED);
}
if (groupScheduledActions.getContent().isEmpty()) {
@@ -493,8 +493,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
public void startScheduledActions(final List<Action> rolloutGroupActions) {
// Close actions already assigned and collect pending assignments
final List<JpaAction> pendingTargetAssignments = rolloutGroupActions.stream().map(JpaAction.class::cast)
.map(this::closeActionIfSetWasAlreadyAssigned).filter(Objects::nonNull).toList();
final List<JpaAction> pendingTargetAssignments = rolloutGroupActions.stream()
.map(JpaAction.class::cast).map(this::closeActionIfSetWasAlreadyAssigned).filter(Objects::nonNull).toList();
if (pendingTargetAssignments.isEmpty()) {
return;
}
@@ -987,10 +987,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final JpaTarget target = action.getTarget();
if (target.getAssignedDistributionSet() != null && action.getDistributionSet().getId()
.equals(target.getAssignedDistributionSet().getId())) {
// the target has already the distribution set assigned, we don't
// need to start the scheduled action, just finish it.
log.debug("Target {} has distribution set {} assigned. Closing action...", target.getControllerId(),
action.getDistributionSet().getName());
// the target has already the distribution set assigned, we don't need to start the scheduled action, just finish it.
log.debug("Target {} has distribution set {} assigned. Closing action...",
target.getControllerId(), action.getDistributionSet().getName());
action.setStatus(Status.FINISHED);
action.setActive(false);
setSkipActionStatus(action);
@@ -1023,8 +1022,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private List<JpaAction> activateActionsOfRolloutGroup(final List<JpaAction> actions) {
actions.forEach(action -> {
action.setActive(true);
final boolean confirmationRequired = action.getRolloutGroup().isConfirmationRequired() && action.getTarget()
.getAutoConfirmationStatus() == null;
final boolean confirmationRequired = action.getRolloutGroup().isConfirmationRequired() &&
action.getTarget().getAutoConfirmationStatus() == null;
if (isConfirmationFlowEnabled() && confirmationRequired) {
action.setStatus(Status.WAIT_FOR_CONFIRMATION);
return;

View File

@@ -46,15 +46,12 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
@@ -84,7 +81,6 @@ public class JpaTargetManagement
private final JpaDistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement;
private final TargetTypeRepository targetTypeRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final TargetTagRepository targetTagRepository;
@SuppressWarnings("java:S107")
@@ -92,13 +88,11 @@ public class JpaTargetManagement
final TargetRepository jpaRepository, final EntityManager entityManager,
final JpaDistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetTypeRepository targetTypeRepository,
final RolloutGroupRepository rolloutGroupRepository,
final TargetTagRepository targetTagRepository) {
super(jpaRepository, entityManager);
this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
this.targetTypeRepository = targetTypeRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.targetTagRepository = targetTagRepository;
}
@@ -162,50 +156,6 @@ public class JpaTargetManagement
.map(Target.class::cast);
}
@Override
public Slice<Target> findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final Collection<Long> groups, final String rsql, final DistributionSetType dsType, final Pageable pageable) {
return jpaRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
QLSupport.getInstance().buildSpec(rsql, TargetFields.class),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))),
pageable)
.map(Target.class::cast);
}
@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(jpaRepository, specList, pageable);
}
@Override
public Slice<Target> findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
final long rolloutId, final String rsql, final DistributionSetType distributionSetType, final Pageable pageable) {
return jpaRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
QLSupport.getInstance().buildSpec(rsql, TargetFields.class),
TargetSpecifications.hasNoOverridingActionsAndNotInRollout(rolloutId),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetType.getId()))),
pageable)
.map(Target.class::cast);
}
@Override
public Slice<Target> findByInRolloutGroupWithoutAction(final long group, final Pageable pageable) {
if (!rolloutGroupRepository.existsById(group)) {
throw new EntityNotFoundException(RolloutGroup.class, group);
}
return JpaManagementHelper.findAllWithoutCountBySpec(
jpaRepository, List.of(TargetSpecifications.hasNoActionInRolloutGroup(group)), pageable);
}
@Override
public Page<Target> findByAssignedDistributionSet(final long distributionSetId, final Pageable pageable) {
final DistributionSet validDistSet = distributionSetManagement.get(distributionSetId);
@@ -289,29 +239,6 @@ public class JpaTargetManagement
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))));
}
@Override
public long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final String rsql, final Collection<Long> groups, final DistributionSetType dsType) {
return jpaRepository.count(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
QLSupport.getInstance().buildSpec(rsql, TargetFields.class),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))));
}
@Override
public long countByFailedRolloutAndNotInRolloutGroups(String rolloutId, Collection<Long> groups) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.countBySpec(jpaRepository, specList);
}
@Override
public long countByActionsInRolloutGroup(final long rolloutGroupId) {
return jpaRepository.count(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,

View File

@@ -41,7 +41,7 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
}
// Note - the exec could be called by JpaRolloutsExecutor and buy JpaRolloutsManagement#triggerNextGroup
// this means it cold be called by concurrently.
// this means it could be called by concurrently.
@Override
public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) {
systemSecurityContext.runAsSystem(() -> {

View File

@@ -9,6 +9,7 @@
*/
package org.eclipse.hawkbit.repository.jpa.scheduler;
import static org.eclipse.hawkbit.repository.jpa.JpaManagementHelper.combineWithAnd;
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
import java.lang.reflect.InvocationTargetException;
@@ -25,6 +26,7 @@ import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -33,23 +35,28 @@ import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.management.JpaRolloutManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.EvaluatorNotConfiguredException;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupEvaluationManager;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
@@ -57,6 +64,7 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
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.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -64,12 +72,16 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondit
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
@@ -106,7 +118,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private final RolloutGroupRepository rolloutGroupRepository;
private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
private final RolloutRepository rolloutRepository;
private final TargetManagement<? extends Target> targetManagement;
private final TargetRepository targetRepository;
private final DeploymentManagement deploymentManagement;
private final RolloutGroupManagement rolloutGroupManagement;
private final RolloutManagement rolloutManagement;
@@ -117,6 +129,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private final PlatformTransactionManager txManager;
private final TenantAware tenantAware;
private final ContextAware contextAware;
private final SystemSecurityContext systemSecurityContext;
private final RepositoryProperties repositoryProperties;
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
@@ -124,17 +137,18 @@ public class JpaRolloutExecutor implements RolloutExecutor {
public JpaRolloutExecutor(
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
final RolloutTargetGroupRepository rolloutTargetGroupRepository,
final RolloutRepository rolloutRepository, final TargetManagement<? extends Target> targetManagement,
final RolloutRepository rolloutRepository, final TargetRepository targetRepository,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final RolloutManagement rolloutManagement, final QuotaManagement quotaManagement,
final RolloutGroupEvaluationManager evaluationManager, final RolloutApprovalStrategy rolloutApprovalStrategy,
final EntityManager entityManager, final PlatformTransactionManager txManager,
final TenantAware tenantAware, final ContextAware contextAware, final RepositoryProperties repositoryProperties) {
final TenantAware tenantAware, final ContextAware contextAware, final SystemSecurityContext systemSecurityContext,
final RepositoryProperties repositoryProperties) {
this.actionRepository = actionRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.rolloutTargetGroupRepository = rolloutTargetGroupRepository;
this.rolloutRepository = rolloutRepository;
this.targetManagement = targetManagement;
this.targetRepository = targetRepository;
this.deploymentManagement = deploymentManagement;
this.rolloutGroupManagement = rolloutGroupManagement;
this.rolloutManagement = rolloutManagement;
@@ -145,6 +159,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
this.txManager = txManager;
this.tenantAware = tenantAware;
this.contextAware = contextAware;
this.systemSecurityContext = systemSecurityContext;
this.repositoryProperties = repositoryProperties;
}
@@ -492,7 +507,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
if (rolloutGroup.isDynamic()) {
return targetManagement.countByActionsInRolloutGroup(rolloutGroup.getId());
return countByActionsInRolloutGroup(rolloutGroup.getId());
} else {
return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId());
}
@@ -589,13 +604,13 @@ public class JpaRolloutExecutor implements RolloutExecutor {
targetsInGroupFilter = DeploymentHelper.runInNewTransaction(
txManager,
"countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable",
count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
count -> countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
groupTargetFilter, readyGroups, rollout.getDistributionSet().getType()));
} else { // if it is a rollout retry
targetsInGroupFilter = DeploymentHelper.runInNewTransaction(
txManager,
"countByFailedRolloutAndNotInRolloutGroupsAndCompatible",
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(
count -> countByFailedRolloutAndNotInRolloutGroups(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()), readyGroups));
}
@@ -640,10 +655,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
rollout.getRolloutGroups(), RolloutGroupStatus.READY, group);
final Slice<Target> targets;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targets = targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
targets = findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
readyGroups, targetFilter, rollout.getDistributionSet().getType(), pageRequest);
} else {
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(
targets = findByFailedRolloutAndNotInRolloutGroups(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()), readyGroups, pageRequest);
}
@@ -681,7 +696,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
// since it contains condition for device to be created before the rollout
rollout.getTargetFilterQuery(), group);
final Slice<Target> targets = targetManagement.findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
final Slice<Target> targets = findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
rollout.getId(), groupTargetFilter, rollout.getDistributionSet().getType(),
PageRequest.of(0, Math.toIntExact(Math.min(TRANSACTION_TARGETS, targetsLeftToAdd))));
if (targets.getNumberOfElements() > 0) {
@@ -798,7 +813,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private Long createActionsForTargetsInNewTransaction(final Rollout rollout, final RolloutGroup group) {
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
final Slice<Target> targets = targetManagement.findByInRolloutGroupWithoutAction(
final Slice<Target> targets = findByInRolloutGroupWithoutAction(
group.getId(), PageRequest.of(0, JpaRolloutExecutor.TRANSACTION_TARGETS));
if (targets.getNumberOfElements() > 0) {
@@ -858,7 +873,80 @@ public class JpaRolloutExecutor implements RolloutExecutor {
try {
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, actionRepository::countByTargetId);
} catch (final AssignmentQuotaExceededException ex) {
deploymentManagement.handleMaxAssignmentsExceeded(target.getId(), requested, ex);
systemSecurityContext.runAsSystem(() -> deploymentManagement.handleMaxAssignmentsExceeded(target.getId(), requested, ex));
}
}
// target repository access
// package-private just for testing
Slice<Target> findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
final long rolloutId, final String rsql, final DistributionSetType distributionSetType, final Pageable pageable) {
return targetRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
QLSupport.getInstance().buildSpec(rsql, TargetFields.class),
TargetSpecifications.hasNoOverridingActionsAndNotInRollout(rolloutId),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetType.getId()))),
pageable)
.map(Target.class::cast);
}
// Finds all targets for all the given parameter {@link TargetFilterQuery} and that are not assigned to one of the {@link RolloutGroup}s
// and are compatible with the passed {@link DistributionSetType}
private 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(
QLSupport.getInstance().buildSpec(rsql, TargetFields.class),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))),
pageable)
.map(Target.class::cast);
}
// 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}.
private Slice<Target> findByFailedRolloutAndNotInRolloutGroups(
final String rolloutId, final Collection<Long> groups, final Pageable pageable) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, specList, pageable);
}
// Finds all targets of the provided {@link RolloutGroup} that have no Action for the RolloutGroup
private Slice<Target> findByInRolloutGroupWithoutAction(final long group, final Pageable pageable) {
if (!rolloutGroupRepository.existsById(group)) {
throw new EntityNotFoundException(RolloutGroup.class, group);
}
return JpaManagementHelper.findAllWithoutCountBySpec(
targetRepository, List.of(TargetSpecifications.hasNoActionInRolloutGroup(group)), pageable);
}
private long countByActionsInRolloutGroup(final long rolloutGroupId) {
return targetRepository.count(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId));
}
// Counts all targets for all the given parameter {@link TargetFilterQuery} and that are not assigned to one of the {@link RolloutGroup}s
// and are compatible with the passed {@link DistributionSetType}
private long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final String rsql, final Collection<Long> groups, final DistributionSetType dsType) {
return targetRepository.count(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
QLSupport.getInstance().buildSpec(rsql, TargetFields.class),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))));
}
// 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}
private long countByFailedRolloutAndNotInRolloutGroups(final String rolloutId, final Collection<Long> groups) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
}

View File

@@ -160,35 +160,18 @@ class TargetManagementTest extends AbstractAccessControllerManagementTest {
READ_TARGET, UPDATE_TARGET + "/type.id==" + targetType1.getId(),
READ_DISTRIBUTION_SET,
CREATE_ROLLOUT, READ_ROLLOUT, HANDLE_ROLLOUT), () -> {
assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
List.of(1L), "id==*", ds2Type2.getType(), UNPAGED).stream().toList())
.containsExactly(target1Type1);
assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", List.of(1L), ds2Type2.getType()))
.isEqualTo(1);
final Rollout rollout = testdataFactory.createRolloutByVariables("testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5");
final List<Long> groups = rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream().
map(Identifiable::getId).toList();
assertThat(groups.stream().flatMap(
group -> targetManagement.findByInRolloutGroupWithoutAction(group, UNPAGED).get()).toList())
.containsExactly(target1Type1);
assertThat(groups.stream().flatMap(
group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group, UNPAGED).get()).toList())
.containsExactly(target1Type1);
assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(groups, "id==*", ds2Type2.getType(), UNPAGED))
.isEmpty();
assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", groups, ds2Type2.getType()))
.isZero();
// as system in context - doesn't apply scopes
final Rollout runAsSystem = systemSecurityContext.runAsSystem(
() -> testdataFactory.createRolloutByVariables(
"testRolloutAsSystem", "testDescriptionAsSystem", 3, "id==*", ds2Type2, "50", "5"));
assertThat(rolloutGroupManagement.findByRollout(runAsSystem.getId(), UNPAGED).getContent().stream()
.flatMap(
group -> targetManagement.findByInRolloutGroupWithoutAction(group.getId(), UNPAGED).getContent().stream()).toList())
.hasSize(3);
});
}

View File

@@ -672,8 +672,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34);
enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(deploymentRequests);
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests);
assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size());
final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).toList();

View File

@@ -38,6 +38,7 @@ import io.github.classgraph.ClassInfo;
import io.github.classgraph.ScanResult;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.PermissionSupport;
import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
@@ -257,7 +258,8 @@ class ManagementSecurityTest extends AbstractJpaIntegrationTest {
assertThat(spelNode.getChild(0) instanceof VariableReference varRef && varRef.toStringAST().equals("#root")).isTrue();
assertThat(spelNode.getChild(1)).isInstanceOf(StringLiteral.class);
final StringLiteral literal = (StringLiteral) spelNode.getChild(1);
preAuthorizedPermissions.add(literal.getLiteralValue().getValue() + "_" + permissionGroup);
preAuthorizedPermissions.add(String.valueOf(literal.getLiteralValue().getValue())
.replace(SpringEvalExpressions.PERMISSION_GROUP_PLACEHOLDER, permissionGroup));
}
default -> throw new IllegalStateException("Unexpected MethodReference: " + method);
}

View File

@@ -10,10 +10,13 @@
package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.callAs;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.SneakyThrows;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
@@ -24,8 +27,11 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.context.TestPropertySource;
@@ -39,6 +45,15 @@ import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.repository.dynamicRolloutsMinInvolvePeriodMS=-1" })
class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
@Configuration
static class Config {
@Bean
SecurityContextSerializer securityContextSerializer() {
return SecurityContextSerializer.JSON_SERIALIZATION;
}
}
@BeforeEach
void reset() {
this.approvalStrategy.setApprovalNeeded(false);
@@ -47,19 +62,21 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/**
* Verifies a simple rollout flow
*/
@SneakyThrows
@Test
void rolloutFlow() {
final String rolloutName = "rollout-std";
final int amountGroups = 5; // static only
final String targetPrefix = "controller-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
final DistributionSet distributionSet = testdataFactory.createDistributionSetLocked("dsFor" + rolloutName);
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, false);
final Rollout rollout = callAs(
withUser("rolloutFlowUser", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, false));
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
// add 2 targets not to be included
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2);
@@ -89,22 +106,24 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/**
* Verifies a simple dynamic rollout flow
*/
@SneakyThrows
@Test
void dynamicRolloutFlow() {
final String rolloutName = "dynamic-rollout-std";
final int amountGroups = 2; // static only
final String targetPrefix = "controller-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
final DistributionSet distributionSet = testdataFactory.createDistributionSetLocked("dsFor" + rolloutName);
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, true);
final Rollout rollout = callAs(
withUser("dynamicRolloutFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, true));
// rollout is READY
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
final RolloutGroup dynamic1 = groups.get(amountGroups);
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic
for (int i = 0; i < amountGroups; i++) {
@@ -139,8 +158,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
final RolloutGroup dynamic2 = groups.get(amountGroups + 1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
@@ -205,25 +223,27 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/**
* Verifies a simple dynamic rollout flow with a dynamic group template
*/
@SneakyThrows
@Test
void dynamicRolloutTemplateFlow() {
final String rolloutName = "dynamic-template-rollout-std";
final int amountGroups = 3; // static only
final String targetPrefix = "controller-template-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
final DistributionSet distributionSet = testdataFactory.createDistributionSetLocked("dsFor" + rolloutName);
// create rollout with amountGroups static groups * 3 targets and dynamic group template with 6 targets
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
Action.ActionType.FORCED, 1000, false, true,
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build());
final Rollout rollout = callAs(
withUser("dynamicRolloutTemplateFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
Action.ActionType.FORCED, 1000, false, true,
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build()));
// rollout is READY, amountGroups + 1 (dynamic) rollout groups and amountGroups * 3 targets in static groups
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
final RolloutGroup dynamic1 = groups.get(amountGroups);
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic
for (int i = 0; i < amountGroups; i++) {
@@ -258,8 +278,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
final RolloutGroup dynamic2 = groups.get(amountGroups + 1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
@@ -305,16 +324,19 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/**
* Verifies a simple pure (no static groups) dynamic rollout flow with a dynamic group template
*/
@SneakyThrows
@Test
void dynamicRolloutPureFlow() {
final String rolloutName = "pure-dynamic-rollout-std";
final String targetPrefix = "controller-pure-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
final DistributionSet distributionSet = testdataFactory.createDistributionSetLocked("dsFor" + rolloutName);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, 0,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
Action.ActionType.FORCED, 1000, false, true,
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build());
final Rollout rollout = callAs(
withUser("dynamicRolloutPureFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, 0,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
Action.ActionType.FORCED, 1000, false, true,
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build()));
// rollout is READY, amountGroups + 1 (dynamic) rollout groups and amountGroups * 3 targets in static groups
assertRollout(rollout, true, RolloutStatus.READY, 1, 0);
@@ -347,8 +369,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, 10, Sort.by(Direction.ASC, "id"))).getContent();
final RolloutGroup dynamic2 = groups.get(1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
@@ -388,19 +409,21 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/**
* Verifies a simple rollout flow
*/
@SneakyThrows
@Test
void rollout0ThresholdFlow() {
final String rolloutName = "rollout-std-0threshold";
final int amountGroups = 5; // static only
final String targetPrefix = "controller-rollout-std-0threshold-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
final DistributionSet distributionSet = testdataFactory.createDistributionSetLocked("dsFor" + rolloutName);
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "0", "25", false, false);
final Rollout rollout = callAs(
withUser("rollout0ThresholdFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "0", "25", false, false));
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))
).getContent();
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
// start rollout
rolloutManagement.start(rollout.getId());

View File

@@ -57,17 +57,14 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaNamedEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
@@ -79,7 +76,6 @@ import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
/**
@@ -729,60 +725,6 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<
"notExisting", ds.getId(), "name==*")).isFalse();
}
/**
* Tests action based aspects of the dynamic group assignment filters.
* Target matches filter no active action with ge weight.
*/
@Test
void findByTargetFilterQueryAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable() {
final String targetPrefix = "dyn_action_filter_";
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(targetPrefix, 10);
final Rollout rolloutOlder = testdataFactory.createRollout();
final Rollout rollout = testdataFactory.createRollout();
final Rollout rolloutNewer = testdataFactory.createRollout();
int target = 0;
final List<Integer> expected = new ArrayList<>();
// old ro with less weight - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 0, Status.RUNNING, distributionSet);
// old ro with equal weight - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 10, Status.RUNNING, distributionSet);
// old ro with bigger weight, scheduled - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 11, Status.SCHEDULED, distributionSet);
// old ro with bigger weight, running - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 11, Status.RUNNING, distributionSet);
// old ro with bigger weight, running - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 11, Status.FINISHED, distributionSet);
// same ro - doesn't match
createAction(targets.get(target++), rollout, 10, Status.RUNNING, distributionSet);
// new ro with less weight - doesn't match
createAction(targets.get(target++), rolloutNewer, 0, Status.RUNNING, distributionSet);
// new ro with less weight - doesn't match
createAction(targets.get(target++), rolloutNewer, 5, Status.WARNING, distributionSet);
// NEW ro with EQUAL weight - doesn't match
createAction(targets.get(target++), rolloutNewer, 10, Status.RUNNING, distributionSet);
// new ro with BIGGER weight - doesn't match
createAction(targets.get(target), rolloutNewer, 20, Status.DOWNLOADED, distributionSet);
final Slice<Target> matching =
targetManagement.findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
rollout.getId(), "controllerid==dyn_action_filter_*", distributionSet.getType(), PAGE);
assertThat(matching.getNumberOfElements()).isEqualTo(expected.size());
assertThat(matching.stream()
.map(Target::getControllerId)
.map(s -> s.substring(targetPrefix.length()))
.map(Integer::parseInt)
.sorted()
.toList()).isEqualTo(expected);
}
/**
* Target matches filter for not existing DS.
*/
@@ -834,7 +776,6 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<
verifyThrownExceptionBy(
() -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*", PAGE),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(NOT_EXIST_IDL, PAGE), "RolloutGroup");
verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet");
verifyThrownExceptionBy(
() -> targetManagement.findByAssignedDistributionSetAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet");
@@ -1124,24 +1065,6 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<
}
}
private void createAction(final Target target, final Rollout rollout, final Integer weight, final Action.Status status,
final DistributionSet distributionSet) {
final JpaAction action = new JpaAction();
action.setActionType(Action.ActionType.FORCED);
action.setTarget(target);
action.setInitiatedBy("test");
if (rollout != null) {
action.setRollout(rollout);
}
if (weight != null) {
action.setWeight(weight);
}
action.setStatus(status);
action.setActive(status != Status.FINISHED && status != Status.ERROR && status != Status.CANCELED);
action.setDistributionSet(distributionSet);
actionRepository.save(action);
}
private void validateFoundTargetsByRsql(final String rsqlFilter, final String... controllerIds) {
final Page<? extends Target> foundTargetsByMetadataAndControllerId = targetManagement.findByRsql(rsqlFilter, PAGE);
final long foundTargetsByMetadataAndControllerIdCount = targetManagement.countByRsql(rsqlFilter);

View File

@@ -50,7 +50,7 @@ import org.springframework.data.domain.Slice;
* Story: Auto assign checker
*/
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
class JpaAutoAssignExecutorIntTest extends AbstractJpaIntegrationTest {
class AutoAssignExecutorIntTest extends AbstractJpaIntegrationTest {
private static final String SPACE_AND_DESCRIPTION = " description";

View File

@@ -45,7 +45,7 @@ import org.springframework.transaction.PlatformTransactionManager;
* Story: Auto assign checker
*/
@ExtendWith(MockitoExtension.class)
class JpaAutoAssignExecutorTest {
class AutoAssignExecutorTest {
@Mock
private TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement;

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.scheduler;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Slice;
public class RolloutExecutorTest extends AbstractJpaIntegrationTest {
@Autowired
private JpaRolloutExecutor jpaRolloutExecutor;
/**
* Tests action based aspects of the dynamic group assignment filters.
* Target matches filter no active action with ge weight.
*/
@Test
void findByTargetFilterQueryAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable() {
final String targetPrefix = "dyn_action_filter_";
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(targetPrefix, 10);
final Rollout rolloutOlder = testdataFactory.createRollout();
final Rollout rollout = testdataFactory.createRollout();
final Rollout rolloutNewer = testdataFactory.createRollout();
int target = 0;
final List<Integer> expected = new ArrayList<>();
// old ro with less weight - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 0, Action.Status.RUNNING, distributionSet);
// old ro with equal weight - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 10, Action.Status.RUNNING, distributionSet);
// old ro with bigger weight, scheduled - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 11, Action.Status.SCHEDULED, distributionSet);
// old ro with bigger weight, running - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 11, Action.Status.RUNNING, distributionSet);
// old ro with bigger weight, running - match
expected.add(target);
createAction(targets.get(target++), rolloutOlder, 11, Action.Status.FINISHED, distributionSet);
// same ro - doesn't match
createAction(targets.get(target++), rollout, 10, Action.Status.RUNNING, distributionSet);
// new ro with less weight - doesn't match
createAction(targets.get(target++), rolloutNewer, 0, Action.Status.RUNNING, distributionSet);
// new ro with less weight - doesn't match
createAction(targets.get(target++), rolloutNewer, 5, Action.Status.WARNING, distributionSet);
// NEW ro with EQUAL weight - doesn't match
createAction(targets.get(target++), rolloutNewer, 10, Action.Status.RUNNING, distributionSet);
// new ro with BIGGER weight - doesn't match
createAction(targets.get(target), rolloutNewer, 20, Action.Status.DOWNLOADED, distributionSet);
final Slice<Target> matching =
jpaRolloutExecutor.findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
rollout.getId(), "controllerid==dyn_action_filter_*", distributionSet.getType(), PAGE);
assertThat(matching.getNumberOfElements()).isEqualTo(expected.size());
assertThat(matching.stream()
.map(Target::getControllerId)
.map(s -> s.substring(targetPrefix.length()))
.map(Integer::parseInt)
.sorted()
.toList()).isEqualTo(expected);
}
private void createAction(
final Target target, final Rollout rollout, final Integer weight, final Action.Status status, final DistributionSet distributionSet) {
final JpaAction action = new JpaAction();
action.setActionType(Action.ActionType.FORCED);
action.setTarget(target);
action.setInitiatedBy("test");
if (rollout != null) {
action.setRollout(rollout);
}
if (weight != null) {
action.setWeight(weight);
}
action.setStatus(status);
action.setActive(status != Action.Status.FINISHED && status != Action.Status.ERROR && status != Action.Status.CANCELED);
action.setDistributionSet(distributionSet);
actionRepository.save(action);
}
}