From 20bb41c51cc59a1294f2d566db9dc0c58bcc3c61 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Tue, 18 Nov 2025 08:44:35 +0200 Subject: [PATCH] Move rollout executor related target management methods in executor (#2812) Signed-off-by: Avgustin Marinov --- .../repository/DeploymentManagement.java | 84 +++++++------ .../hawkbit/repository/RolloutManagement.java | 4 +- .../hawkbit/repository/TargetManagement.java | 71 ----------- .../repository/RepositoryConfiguration.java | 10 +- .../management/JpaDeploymentManagement.java | 23 ++-- .../jpa/management/JpaTargetManagement.java | 73 ----------- ...artNextGroupRolloutGroupSuccessAction.java | 2 +- .../jpa/scheduler/JpaRolloutExecutor.java | 114 ++++++++++++++++-- .../jpa/acm/TargetManagementTest.java | 17 --- .../management/DeploymentManagementTest.java | 3 +- .../management/ManagementSecurityTest.java | 4 +- .../management/RolloutManagementFlowTest.java | 89 +++++++++----- .../jpa/management/TargetManagementTest.java | 77 ------------ ...st.java => AutoAssignExecutorIntTest.java} | 2 +- ...rTest.java => AutoAssignExecutorTest.java} | 2 +- .../jpa/scheduler/RolloutExecutorTest.java | 103 ++++++++++++++++ .../authentication/SpringEvalExpressions.java | 11 +- 17 files changed, 340 insertions(+), 349 deletions(-) rename hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/{JpaAutoAssignExecutorIntTest.java => AutoAssignExecutorIntTest.java} (99%) rename hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/{JpaAutoAssignExecutorTest.java => AutoAssignExecutorTest.java} (99%) create mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutExecutorTest.java diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index c1a99482e..1152042ce 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -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 assignDistributionSets(@Valid @NotEmpty List 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 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 offlineAssignedDistributionSets(String initiatedBy, Collection> 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 7410011dc..02968654a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -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() { diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 56ce40226..1fdf3ad89 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -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 Slice 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 findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( - @NotEmpty Collection 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 findByFailedRolloutAndNotInRolloutGroups( - @NotNull String rolloutId, @NotEmpty Collection groups, @NotNull Pageable pageable); - - @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT) - Slice 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 findByInRolloutGroupWithoutAction(long group, @NotNull Pageable pageable); - /** * Retrieves {@link Target}s by the assigned {@link DistributionSet}. * @@ -299,33 +255,6 @@ public interface TargetManagement @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 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 groups); - - @PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT) - long countByActionsInRolloutGroup(final long rolloutGroupId); - /** * Deletes target with the given controller ID. * diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RepositoryConfiguration.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RepositoryConfiguration.java index e10ec1bd1..d7c1db584 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RepositoryConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RepositoryConfiguration.java @@ -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); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java index b4afde055..3129afb6e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java @@ -473,11 +473,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl final PageRequest pageRequest = PageRequest.of(0, ACTION_PAGE_LIMIT); final Page 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 rolloutGroupActions) { // Close actions already assigned and collect pending assignments - final List pendingTargetAssignments = rolloutGroupActions.stream().map(JpaAction.class::cast) - .map(this::closeActionIfSetWasAlreadyAssigned).filter(Objects::nonNull).toList(); + final List 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 activateActionsOfRolloutGroup(final List 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; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index beaed04f1..3e237f7a0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -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 findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( - final Collection 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 findByFailedRolloutAndNotInRolloutGroups(String rolloutId, Collection groups, Pageable pageable) { - final List> specList = List.of( - TargetSpecifications.failedActionsForRollout(rolloutId), - TargetSpecifications.isNotInRolloutGroups(groups)); - return JpaManagementHelper.findAllWithCountBySpec(jpaRepository, specList, pageable); - } - - @Override - public Slice 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 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 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 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 groups) { - final List> 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, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java index 88ed64309..18e9f01a0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java @@ -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(() -> { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java index 2e42d87a9..4a5663a9a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java @@ -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 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 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 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 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 targets = targetManagement.findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable( + final Slice 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 targets = targetManagement.findByInRolloutGroupWithoutAction( + final Slice 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 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 findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( + final Collection 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 findByFailedRolloutAndNotInRolloutGroups( + final String rolloutId, final Collection groups, final Pageable pageable) { + final List> 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 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 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 groups) { + final List> specList = List.of( + TargetSpecifications.failedActionsForRollout(rolloutId), + TargetSpecifications.isNotInRolloutGroups(groups)); + return JpaManagementHelper.countBySpec(targetRepository, specList); + } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java index 1e2bb9f85..c8a661bea 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java @@ -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 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); }); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java index 01b3cfc5b..c257edc9a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java @@ -672,8 +672,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { final List deploymentRequests = createAssignmentRequests(distributionSets, targets, 34); enableMultiAssignments(); - final List results = deploymentManagement - .assignDistributionSets(deploymentRequests); + final List results = deploymentManagement.assignDistributionSets(deploymentRequests); assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size()); final List dsIds = distributionSets.stream().map(DistributionSet::getId).toList(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java index 188dbb830..01b8af205 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java @@ -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); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementFlowTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementFlowTest.java index 39b9ab0f3..bf2bf0da2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementFlowTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementFlowTest.java @@ -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 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 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 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 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()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java index 1af0c3544..e6fe687d5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java @@ -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 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 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 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 foundTargetsByMetadataAndControllerId = targetManagement.findByRsql(rsqlFilter, PAGE); final long foundTargetsByMetadataAndControllerIdCount = targetManagement.countByRsql(rsqlFilter); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaAutoAssignExecutorIntTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignExecutorIntTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaAutoAssignExecutorIntTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignExecutorIntTest.java index 4465fae01..97e0acc20 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaAutoAssignExecutorIntTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignExecutorIntTest.java @@ -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"; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaAutoAssignExecutorTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignExecutorTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaAutoAssignExecutorTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignExecutorTest.java index 83174563f..b170ca307 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaAutoAssignExecutorTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignExecutorTest.java @@ -45,7 +45,7 @@ import org.springframework.transaction.PlatformTransactionManager; * Story: Auto assign checker */ @ExtendWith(MockitoExtension.class) -class JpaAutoAssignExecutorTest { +class AutoAssignExecutorTest { @Mock private TargetFilterQueryManagement targetFilterQueryManagement; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutExecutorTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutExecutorTest.java new file mode 100644 index 000000000..92b5f4f41 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutExecutorTest.java @@ -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 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 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 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); + } +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpringEvalExpressions.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpringEvalExpressions.java index c45d7a2e4..666dc8b82 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpringEvalExpressions.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpringEvalExpressions.java @@ -39,11 +39,12 @@ public final class SpringEvalExpressions { public static final String IS_SYSTEM_CODE = "hasAuthority('ROLE_SYSTEM_CODE')"; public static final String HAS_AUTH_SYSTEM_ADMIN = "hasAuthority('SYSTEM_ADMIN')"; - // evaluated to _ (e.g. DISTRIBUTION_SET_CREATE) - public static final String HAS_CREATE_REPOSITORY = "hasPermission(#root, 'CREATE')"; - public static final String HAS_READ_REPOSITORY = "hasPermission(#root, 'READ')"; - public static final String HAS_UPDATE_REPOSITORY = "hasPermission(#root, 'UPDATE')"; - public static final String HAS_DELETE_REPOSITORY = "hasPermission(#root, 'DELETE')"; + public static final String PERMISSION_GROUP_PLACEHOLDER = "${permissionGroup}"; + // evaluated to _ (e.g. CREATE_DISTRIBUTION_SET) + public static final String HAS_CREATE_REPOSITORY = "hasPermission(#root, 'CREATE_${permissionGroup}')"; + public static final String HAS_READ_REPOSITORY = "hasPermission(#root, 'READ_${permissionGroup}')"; + public static final String HAS_UPDATE_REPOSITORY = "hasPermission(#root, 'UPDATE_${permissionGroup}')"; + public static final String HAS_DELETE_REPOSITORY = "hasPermission(#root, 'DELETE_${permissionGroup}')"; public static final String IS_CONTROLLER = "hasAnyRole('" + SpRole.CONTROLLER_ROLE_ANONYMOUS + "', '" + SpRole.CONTROLLER_ROLE + "')"; } \ No newline at end of file