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

@@ -9,6 +9,14 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DISTRIBUTION_SET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.HANDLE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
@@ -50,7 +58,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
*/
public interface DeploymentManagement extends PermissionSupport {
String HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET = SpringEvalExpressions.HAS_UPDATE_REPOSITORY + " and hasAuthority('READ_" + SpPermission.DISTRIBUTION_SET + "')";
String HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET = HAS_UPDATE_REPOSITORY + " and hasPermission(#root, 'READ_" + DISTRIBUTION_SET + "')";
String HAS_UPDATE_TARGET_OR_CREATE_UPDATE_ROLLOUT = HAS_UPDATE_REPOSITORY + " or hasPermission(#root, '" + CREATE_ROLLOUT + "')" + " or hasPermission(#root, '" + UPDATE_ROLLOUT + "')";
@Override
default String permissionGroup() {
@@ -63,12 +72,12 @@ public interface DeploymentManagement extends PermissionSupport {
* @param deploymentRequests information about all target-ds-assignments that shall be made
* @return the list of assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
* defined by the {@link DistributionSetType}.
* defined by the {@link DistributionSetType}.
* @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist
* @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
* assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same
* target and multi-assignment is disabled
* target and multi-assignment is disabled
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets(@Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
@@ -81,12 +90,12 @@ public interface DeploymentManagement extends PermissionSupport {
* @param actionMessage an optional message for the action status
* @return the list of assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
* defined by the {@link DistributionSetType}.
* defined by the {@link DistributionSetType}.
* @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist
* @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
* assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same
* target and multi-assignment is disabled
* target and multi-assignment is disabled
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets(
@@ -108,11 +117,11 @@ public interface DeploymentManagement extends PermissionSupport {
* @param assignments target IDs with the respective distribution set ID which they are supposed to be assigned to
* @return the assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
* defined by the {@link DistributionSetType}.
* defined by the {@link DistributionSetType}.
* @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist
* @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same
* target and multi-assignment is disabled
* target and multi-assignment is disabled
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(String initiatedBy, Collection<Entry<String, Long>> assignments);
@@ -129,7 +138,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @throws CancelActionNotAllowedException in case the given action is not active or is already a cancel action
* @throws EntityNotFoundException if action with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
@PreAuthorize(HAS_UPDATE_REPOSITORY)
Action cancelAction(long actionId);
/**
@@ -139,11 +148,11 @@ public interface DeploymentManagement extends PermissionSupport {
* @param controllerId the target associated to the actions to count
* @return the count value of found actions associated to the target
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
* given {@code fieldNameProvider}
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
long countActionsByTarget(@NotNull String rsql, @NotEmpty String controllerId);
/**
@@ -151,7 +160,7 @@ public interface DeploymentManagement extends PermissionSupport {
*
* @return the total amount of stored actions
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
long countActionsAll();
/**
@@ -160,7 +169,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param rsql RSQL query.
* @return the total number of actions matching the given RSQL query.
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
long countActions(@NotNull String rsql);
/**
@@ -170,7 +179,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return the count value of found actions associated to the target
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
long countActionsByTarget(@NotEmpty String controllerId);
/**
@@ -179,7 +188,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param actionId to be id of the action
* @return the corresponding {@link Action}
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Optional<Action> findAction(long actionId);
/**
@@ -188,7 +197,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable pagination parameter
* @return a paged list of {@link Action}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Slice<Action> findActionsAll(@NotNull Pageable pageable);
/**
@@ -198,7 +207,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable the page request parameter for paging and sorting the result
* @return a paged list of {@link Action}s.
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Slice<Action> findActions(@NotNull String rsql, @NotNull Pageable pageable);
/**
@@ -209,10 +218,10 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable the page request
* @return a slice of actions assigned to the specific target and the specification
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
* given {@code fieldNameProvider}
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Slice<Action> findActionsByTarget(@NotNull String rsql, @NotEmpty String controllerId, @NotNull Pageable pageable);
/**
@@ -222,7 +231,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable the pageable request to limit, sort the actions
* @return a slice of actions found for a specific target
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Slice<Action> findActionsByTarget(@NotEmpty String controllerId, @NotNull Pageable pageable);
/**
@@ -233,7 +242,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return the corresponding {@link Page} of {@link ActionStatus}
* @throws EntityNotFoundException if action with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Page<ActionStatus> findActionStatusByAction(long actionId, @NotNull Pageable pageable);
/**
@@ -243,7 +252,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable the page request parameter for paging and sorting the result
* @return a page of messages by a specific {@link ActionStatus} id
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Page<String> findMessagesByActionStatusId(long actionStatusId, @NotNull Pageable pageable);
/**
@@ -252,7 +261,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param actionId to be id of the action
* @return the corresponding {@link Action}
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Optional<Action> findActionWithDetails(long actionId);
/**
@@ -263,7 +272,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return a list of actions associated with the given target
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Page<Action> findActiveActionsByTarget(@NotEmpty String controllerId, @NotNull Pageable pageable);
/**
@@ -273,7 +282,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param maxActionCount max size of returned list
* @return the action
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount);
/**
@@ -285,7 +294,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @throws CancelActionNotAllowedException in case the given action is not active
* @throws EntityNotFoundException if action with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
@PreAuthorize(HAS_UPDATE_REPOSITORY)
Action forceQuitAction(long actionId);
/**
@@ -295,11 +304,12 @@ public interface DeploymentManagement extends PermissionSupport {
* @return the updated or the found {@link Action}
* @throws EntityNotFoundException if action with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
@PreAuthorize(HAS_UPDATE_REPOSITORY)
Action forceTargetAction(long actionId);
/**
* Deletes the current action by id.
*
* @param actionId - action id
*/
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
@@ -307,6 +317,7 @@ public interface DeploymentManagement extends PermissionSupport {
/**
* Deletes actions matching the provided rsql filter
*
* @param rsql - rsql filter for actions to be deleted
*/
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
@@ -314,6 +325,7 @@ public interface DeploymentManagement extends PermissionSupport {
/**
* Deletes actions present in provided list of ids
*
* @param actionIds - list of action ids to be deleted
*/
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
@@ -342,7 +354,7 @@ public interface DeploymentManagement extends PermissionSupport {
*
* @param targetIds ids of the {@link Target}s the actions belong to
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
@PreAuthorize(HAS_UPDATE_TARGET_OR_CREATE_UPDATE_ROLLOUT)
void cancelInactiveScheduledActionsForTargets(List<Long> targetIds);
/**
@@ -352,7 +364,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param distributionSetId to assign
* @param rolloutGroupParentId the parent rollout group the actions should reference. null references the first group
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_UPDATE_TARGET_OR_CREATE_UPDATE_ROLLOUT)
void startScheduledActionsByRolloutGroupParent(long rolloutId, long distributionSetId, Long rolloutGroupParentId);
/**
@@ -360,7 +372,7 @@ public interface DeploymentManagement extends PermissionSupport {
*
* @param rolloutGroupActions rollouts group actions part of a same group
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_UPDATE_TARGET_OR_CREATE_UPDATE_ROLLOUT)
void startScheduledActions(final List<Action> rolloutGroupActions);
/**
@@ -370,7 +382,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return assigned {@link DistributionSet}
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Optional<DistributionSet> findAssignedDistributionSet(@NotEmpty String controllerId);
/**
@@ -380,7 +392,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return installed {@link DistributionSet}
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
Optional<DistributionSet> findInstalledDistributionSet(@NotEmpty String controllerId);
/**
@@ -400,7 +412,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param targetId of target
* @return if actions in CANCELING state are present
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
@PreAuthorize(HAS_READ_REPOSITORY)
boolean hasPendingCancellations(@NotNull Long targetId);
/**
@@ -409,9 +421,9 @@ public interface DeploymentManagement extends PermissionSupport {
* @param cancelationType defines if a force or soft cancel is executed
* @param set the distribution set for that the actions should be canceled
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
@PreAuthorize(HAS_UPDATE_REPOSITORY)
void cancelActionsForDistributionSet(final ActionCancellationType cancelationType, final DistributionSet set);
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY + " or " + SpringEvalExpressions.IS_SYSTEM_CODE)
@PreAuthorize(HAS_UPDATE_REPOSITORY + " or " + SpringEvalExpressions.IS_SYSTEM_CODE)
void handleMaxAssignmentsExceeded(Long targetId, Long requested, AssignmentQuotaExceededException quotaExceededException);
}

View File

@@ -57,8 +57,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
*/
public interface RolloutManagement extends PermissionSupport {
String HAS_ROLLOUT_APPROVE = "hasPermission(#root, 'APPROVE')";
String HAS_ROLLOUT_HANDLE = "hasPermission(#root, 'HANDLE')";
String HAS_ROLLOUT_APPROVE = "hasPermission(#root, 'APPROVE_${permissionGroup}')";
String HAS_ROLLOUT_HANDLE = "hasPermission(#root, 'HANDLE_${permissionGroup}')";
@Override
default String permissionGroup() {

View File

@@ -39,7 +39,6 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldExc
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetTag;
@@ -144,49 +143,6 @@ public interface TargetManagement<T extends Target>
Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(
long distributionSetId, @NotNull String rsql, @NotNull Pageable pageable);
/**
* 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}.
*
* @param groups the list of {@link RolloutGroup}s
* @param rsql filter definition in RSQL syntax
* @param distributionSetType type of the {@link DistributionSet} the targets must be compatible withs
* @param pageable the pageable to enhance the query for paging and sorting
* @return a page of the found {@link Target}s
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT)
Slice<Target> findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
@NotEmpty Collection<Long> groups, @NotNull String rsql, @NotNull DistributionSetType distributionSetType,
@NotNull Pageable pageable);
/**
* 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 rolloutId rolloutId of the rollout to be retried.
* @param groups the list of {@link RolloutGroup}s
* @param pageable the pageable to enhance the query for paging and sorting
* @return a page of the found {@link Target}s
*/
@PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT)
Slice<Target> findByFailedRolloutAndNotInRolloutGroups(
@NotNull String rolloutId, @NotEmpty Collection<Long> groups, @NotNull Pageable pageable);
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT)
Slice<Target> findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
final long rolloutId, @NotNull String rsql, @NotNull DistributionSetType distributionSetType, @NotNull Pageable pageable);
/**
* Finds all targets of the provided {@link RolloutGroup} that have no Action for the RolloutGroup.
*
* @param group the {@link RolloutGroup}
* @param pageable the pageable to enhance the query for paging and sorting
* @return the found {@link Target}s
* @throws EntityNotFoundException if rollout group with given ID does not exist
*/
@PreAuthorize(HAS_READ_REPOSITORY)
Slice<Target> findByInRolloutGroupWithoutAction(long group, @NotNull Pageable pageable);
/**
* Retrieves {@link Target}s by the assigned {@link DistributionSet}.
*
@@ -299,33 +255,6 @@ public interface TargetManagement<T extends Target>
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
long countByRsqlAndNonDsAndCompatibleAndUpdatable(long distributionSetId, @NotNull String rsql);
/**
* 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}.
*
* @param rsql filter definition in RSQL syntax
* @param groups the list of {@link RolloutGroup}s
* @param distributionSetType type of the {@link DistributionSet} the targets must be compatible with
* @return count of the found {@link Target}s
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT)
long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
@NotNull String rsql, @NotEmpty Collection<Long> groups, @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 rolloutId rolloutId of the rollout to be retried.
* @param groups the list of {@link RolloutGroup}s
* @return count of the found {@link Target}s
*/
@PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT)
long countByFailedRolloutAndNotInRolloutGroups(@NotNull String rolloutId, @NotEmpty Collection<Long> groups);
@PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT)
long countByActionsInRolloutGroup(final long rolloutGroupId);
/**
* Deletes target with the given controller ID.
*

View File

@@ -13,6 +13,7 @@ import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.Hierarchy;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Value;
@@ -46,7 +47,8 @@ public class RepositoryConfiguration {
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("java:S3358") // java:S3358 better readable this way
@SuppressWarnings("java:S3358")
// java:S3358 better readable this way
RoleHierarchy roleHierarchy(
// if configured replaces the hierarchy completely
@Value("${hawkbit.hierarchy:}") final String hierarchy,
@@ -66,15 +68,15 @@ public class RepositoryConfiguration {
public boolean hasPermission(final Authentication authentication, final Object targetDomainObject, final Object permission) {
if (targetDomainObject instanceof MethodSecurityExpressionOperations root &&
root.getThis() instanceof PermissionSupport permissionSupport) {
final String neededPermission = permission + "_" + permissionSupport.permissionGroup();
final String neededPermission = String.valueOf(permission)
.replace(SpringEvalExpressions.PERMISSION_GROUP_PLACEHOLDER, permissionSupport.permissionGroup());
// do permissions check
final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(authority -> authority.equals(neededPermission));
if (!hasPermission) {
log.debug(
"User {} does not have permission {} for target {}",
log.debug("User {} does not have permission {} for target {}",
authentication.getName(), neededPermission, targetDomainObject);
}

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