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; 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.Collection;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -50,7 +58,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
*/ */
public interface DeploymentManagement extends PermissionSupport { 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 @Override
default String permissionGroup() { default String permissionGroup() {
@@ -63,12 +72,12 @@ public interface DeploymentManagement extends PermissionSupport {
* @param deploymentRequests information about all target-ds-assignments that shall be made * @param deploymentRequests information about all target-ds-assignments that shall be made
* @return the list of assignment results * @return the list of assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as * @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 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 * @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 * @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) @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets(@Valid @NotEmpty List<DeploymentRequest> deploymentRequests); 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 * @param actionMessage an optional message for the action status
* @return the list of assignment results * @return the list of assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as * @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 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 * @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 * @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) @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets( 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 * @param assignments target IDs with the respective distribution set ID which they are supposed to be assigned to
* @return the assignment results * @return the assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as * @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 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 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 * @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) @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(String initiatedBy, Collection<Entry<String, Long>> assignments); 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 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 * @throws EntityNotFoundException if action with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY) @PreAuthorize(HAS_UPDATE_REPOSITORY)
Action cancelAction(long actionId); Action cancelAction(long actionId);
/** /**
@@ -139,11 +148,11 @@ public interface DeploymentManagement extends PermissionSupport {
* @param controllerId the target associated to the actions to count * @param controllerId the target associated to the actions to count
* @return the count value of found actions associated to the target * @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 * @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 RSQLParameterSyntaxException if the RSQL syntax is wrong
* @throws EntityNotFoundException if target with given ID does not exist * @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); long countActionsByTarget(@NotNull String rsql, @NotEmpty String controllerId);
/** /**
@@ -151,7 +160,7 @@ public interface DeploymentManagement extends PermissionSupport {
* *
* @return the total amount of stored actions * @return the total amount of stored actions
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
long countActionsAll(); long countActionsAll();
/** /**
@@ -160,7 +169,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param rsql RSQL query. * @param rsql RSQL query.
* @return the total number of actions matching the given 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); 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 * @return the count value of found actions associated to the target
* @throws EntityNotFoundException if target with given ID does not exist * @throws EntityNotFoundException if target with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
long countActionsByTarget(@NotEmpty String controllerId); long countActionsByTarget(@NotEmpty String controllerId);
/** /**
@@ -179,7 +188,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param actionId to be id of the action * @param actionId to be id of the action
* @return the corresponding {@link Action} * @return the corresponding {@link Action}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
Optional<Action> findAction(long actionId); Optional<Action> findAction(long actionId);
/** /**
@@ -188,7 +197,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable pagination parameter * @param pageable pagination parameter
* @return a paged list of {@link Action}s * @return a paged list of {@link Action}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
Slice<Action> findActionsAll(@NotNull Pageable pageable); 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 * @param pageable the page request parameter for paging and sorting the result
* @return a paged list of {@link Action}s. * @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); Slice<Action> findActions(@NotNull String rsql, @NotNull Pageable pageable);
/** /**
@@ -209,10 +218,10 @@ public interface DeploymentManagement extends PermissionSupport {
* @param pageable the page request * @param pageable the page request
* @return a slice of actions assigned to the specific target and the specification * @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 * @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 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); 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 * @param pageable the pageable request to limit, sort the actions
* @return a slice of actions found for a specific target * @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); 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} * @return the corresponding {@link Page} of {@link ActionStatus}
* @throws EntityNotFoundException if action with given ID does not exist * @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); 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 * @param pageable the page request parameter for paging and sorting the result
* @return a page of messages by a specific {@link ActionStatus} id * @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); 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 * @param actionId to be id of the action
* @return the corresponding {@link Action} * @return the corresponding {@link Action}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
Optional<Action> findActionWithDetails(long actionId); Optional<Action> findActionWithDetails(long actionId);
/** /**
@@ -263,7 +272,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return a list of actions associated with the given target * @return a list of actions associated with the given target
* @throws EntityNotFoundException if target with given ID does not exist * @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); 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 * @param maxActionCount max size of returned list
* @return the action * @return the action
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount); 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 CancelActionNotAllowedException in case the given action is not active
* @throws EntityNotFoundException if action with given ID does not exist * @throws EntityNotFoundException if action with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY) @PreAuthorize(HAS_UPDATE_REPOSITORY)
Action forceQuitAction(long actionId); Action forceQuitAction(long actionId);
/** /**
@@ -295,11 +304,12 @@ public interface DeploymentManagement extends PermissionSupport {
* @return the updated or the found {@link Action} * @return the updated or the found {@link Action}
* @throws EntityNotFoundException if action with given ID does not exist * @throws EntityNotFoundException if action with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY) @PreAuthorize(HAS_UPDATE_REPOSITORY)
Action forceTargetAction(long actionId); Action forceTargetAction(long actionId);
/** /**
* Deletes the current action by id. * Deletes the current action by id.
*
* @param actionId - action id * @param actionId - action id
*/ */
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')") @PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
@@ -307,6 +317,7 @@ public interface DeploymentManagement extends PermissionSupport {
/** /**
* Deletes actions matching the provided rsql filter * Deletes actions matching the provided rsql filter
*
* @param rsql - rsql filter for actions to be deleted * @param rsql - rsql filter for actions to be deleted
*/ */
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')") @PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
@@ -314,6 +325,7 @@ public interface DeploymentManagement extends PermissionSupport {
/** /**
* Deletes actions present in provided list of ids * Deletes actions present in provided list of ids
*
* @param actionIds - list of action ids to be deleted * @param actionIds - list of action ids to be deleted
*/ */
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')") @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 * @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); void cancelInactiveScheduledActionsForTargets(List<Long> targetIds);
/** /**
@@ -352,7 +364,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param distributionSetId to assign * @param distributionSetId to assign
* @param rolloutGroupParentId the parent rollout group the actions should reference. null references the first group * @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); 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 * @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); void startScheduledActions(final List<Action> rolloutGroupActions);
/** /**
@@ -370,7 +382,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return assigned {@link DistributionSet} * @return assigned {@link DistributionSet}
* @throws EntityNotFoundException if target with given ID does not exist * @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); Optional<DistributionSet> findAssignedDistributionSet(@NotEmpty String controllerId);
/** /**
@@ -380,7 +392,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @return installed {@link DistributionSet} * @return installed {@link DistributionSet}
* @throws EntityNotFoundException if target with given ID does not exist * @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); Optional<DistributionSet> findInstalledDistributionSet(@NotEmpty String controllerId);
/** /**
@@ -400,7 +412,7 @@ public interface DeploymentManagement extends PermissionSupport {
* @param targetId of target * @param targetId of target
* @return if actions in CANCELING state are present * @return if actions in CANCELING state are present
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) @PreAuthorize(HAS_READ_REPOSITORY)
boolean hasPendingCancellations(@NotNull Long targetId); 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 cancelationType defines if a force or soft cancel is executed
* @param set the distribution set for that the actions should be canceled * @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); 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); 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 { public interface RolloutManagement extends PermissionSupport {
String HAS_ROLLOUT_APPROVE = "hasPermission(#root, 'APPROVE')"; String HAS_ROLLOUT_APPROVE = "hasPermission(#root, 'APPROVE_${permissionGroup}')";
String HAS_ROLLOUT_HANDLE = "hasPermission(#root, 'HANDLE')"; String HAS_ROLLOUT_HANDLE = "hasPermission(#root, 'HANDLE_${permissionGroup}')";
@Override @Override
default String permissionGroup() { 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity; 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.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
@@ -144,49 +143,6 @@ public interface TargetManagement<T extends Target>
Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable( Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(
long distributionSetId, @NotNull String rsql, @NotNull Pageable pageable); 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}. * 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) @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
long countByRsqlAndNonDsAndCompatibleAndUpdatable(long distributionSetId, @NotNull String rsql); 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. * Deletes target with the given controller ID.
* *

View File

@@ -13,6 +13,7 @@ import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.Hierarchy; 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.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -46,7 +47,8 @@ public class RepositoryConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@SuppressWarnings("java:S3358") // java:S3358 better readable this way @SuppressWarnings("java:S3358")
// java:S3358 better readable this way
RoleHierarchy roleHierarchy( RoleHierarchy roleHierarchy(
// if configured replaces the hierarchy completely // if configured replaces the hierarchy completely
@Value("${hawkbit.hierarchy:}") final String hierarchy, @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) { public boolean hasPermission(final Authentication authentication, final Object targetDomainObject, final Object permission) {
if (targetDomainObject instanceof MethodSecurityExpressionOperations root && if (targetDomainObject instanceof MethodSecurityExpressionOperations root &&
root.getThis() instanceof PermissionSupport permissionSupport) { 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 // do permissions check
final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream() final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
.map(GrantedAuthority::getAuthority) .map(GrantedAuthority::getAuthority)
.anyMatch(authority -> authority.equals(neededPermission)); .anyMatch(authority -> authority.equals(neededPermission));
if (!hasPermission) { if (!hasPermission) {
log.debug( log.debug("User {} does not have permission {} for target {}",
"User {} does not have permission {} for target {}",
authentication.getName(), neededPermission, targetDomainObject); 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 PageRequest pageRequest = PageRequest.of(0, ACTION_PAGE_LIMIT);
final Page<Action> groupScheduledActions; final Page<Action> groupScheduledActions;
if (rolloutGroupParentId == null) { if (rolloutGroupParentId == null) {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIsNullAndStatus(pageRequest, rolloutId, groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIsNullAndStatus(
Action.Status.SCHEDULED); pageRequest, rolloutId, Action.Status.SCHEDULED);
} else { } else {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIdAndStatus(pageRequest, rolloutId, groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIdAndStatus(
rolloutGroupParentId, Action.Status.SCHEDULED); pageRequest, rolloutId, rolloutGroupParentId, Action.Status.SCHEDULED);
} }
if (groupScheduledActions.getContent().isEmpty()) { if (groupScheduledActions.getContent().isEmpty()) {
@@ -493,8 +493,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override @Override
public void startScheduledActions(final List<Action> rolloutGroupActions) { public void startScheduledActions(final List<Action> rolloutGroupActions) {
// Close actions already assigned and collect pending assignments // Close actions already assigned and collect pending assignments
final List<JpaAction> pendingTargetAssignments = rolloutGroupActions.stream().map(JpaAction.class::cast) final List<JpaAction> pendingTargetAssignments = rolloutGroupActions.stream()
.map(this::closeActionIfSetWasAlreadyAssigned).filter(Objects::nonNull).toList(); .map(JpaAction.class::cast).map(this::closeActionIfSetWasAlreadyAssigned).filter(Objects::nonNull).toList();
if (pendingTargetAssignments.isEmpty()) { if (pendingTargetAssignments.isEmpty()) {
return; return;
} }
@@ -987,10 +987,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final JpaTarget target = action.getTarget(); final JpaTarget target = action.getTarget();
if (target.getAssignedDistributionSet() != null && action.getDistributionSet().getId() if (target.getAssignedDistributionSet() != null && action.getDistributionSet().getId()
.equals(target.getAssignedDistributionSet().getId())) { .equals(target.getAssignedDistributionSet().getId())) {
// the target has already the distribution set assigned, we don't // the target has already the distribution set assigned, we don't need to start the scheduled action, just finish it.
// need to start the scheduled action, just finish it. log.debug("Target {} has distribution set {} assigned. Closing action...",
log.debug("Target {} has distribution set {} assigned. Closing action...", target.getControllerId(), target.getControllerId(), action.getDistributionSet().getName());
action.getDistributionSet().getName());
action.setStatus(Status.FINISHED); action.setStatus(Status.FINISHED);
action.setActive(false); action.setActive(false);
setSkipActionStatus(action); setSkipActionStatus(action);
@@ -1023,8 +1022,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private List<JpaAction> activateActionsOfRolloutGroup(final List<JpaAction> actions) { private List<JpaAction> activateActionsOfRolloutGroup(final List<JpaAction> actions) {
actions.forEach(action -> { actions.forEach(action -> {
action.setActive(true); action.setActive(true);
final boolean confirmationRequired = action.getRolloutGroup().isConfirmationRequired() && action.getTarget() final boolean confirmationRequired = action.getRolloutGroup().isConfirmationRequired() &&
.getAutoConfirmationStatus() == null; action.getTarget().getAutoConfirmationStatus() == null;
if (isConfirmationFlowEnabled() && confirmationRequired) { if (isConfirmationFlowEnabled() && confirmationRequired) {
action.setStatus(Status.WAIT_FOR_CONFIRMATION); action.setStatus(Status.WAIT_FOR_CONFIRMATION);
return; 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.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; 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.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet; 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.Target;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.qfields.TargetFields; import org.eclipse.hawkbit.repository.qfields.TargetFields;
@@ -84,7 +81,6 @@ public class JpaTargetManagement
private final JpaDistributionSetManagement distributionSetManagement; private final JpaDistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final TargetTypeRepository targetTypeRepository; private final TargetTypeRepository targetTypeRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final TargetTagRepository targetTagRepository; private final TargetTagRepository targetTagRepository;
@SuppressWarnings("java:S107") @SuppressWarnings("java:S107")
@@ -92,13 +88,11 @@ public class JpaTargetManagement
final TargetRepository jpaRepository, final EntityManager entityManager, final TargetRepository jpaRepository, final EntityManager entityManager,
final JpaDistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final JpaDistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetTypeRepository targetTypeRepository, final TargetTypeRepository targetTypeRepository,
final RolloutGroupRepository rolloutGroupRepository,
final TargetTagRepository targetTagRepository) { final TargetTagRepository targetTagRepository) {
super(jpaRepository, entityManager); super(jpaRepository, entityManager);
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement; this.quotaManagement = quotaManagement;
this.targetTypeRepository = targetTypeRepository; this.targetTypeRepository = targetTypeRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.targetTagRepository = targetTagRepository; this.targetTagRepository = targetTagRepository;
} }
@@ -162,50 +156,6 @@ public class JpaTargetManagement
.map(Target.class::cast); .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 @Override
public Page<Target> findByAssignedDistributionSet(final long distributionSetId, final Pageable pageable) { public Page<Target> findByAssignedDistributionSet(final long distributionSetId, final Pageable pageable) {
final DistributionSet validDistSet = distributionSetManagement.get(distributionSetId); final DistributionSet validDistSet = distributionSetManagement.get(distributionSetId);
@@ -289,29 +239,6 @@ public class JpaTargetManagement
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId)))); 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 @Override
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @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 // 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 @Override
public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) { public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) {
systemSecurityContext.runAsSystem(() -> { systemSecurityContext.runAsSystem(() -> {

View File

@@ -9,6 +9,7 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.scheduler; 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 static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -25,6 +26,7 @@ import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties; 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.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutHelper; import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement; 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.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; 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.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.management.JpaRolloutManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; 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.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository; 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.EvaluatorNotConfiguredException;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupEvaluationManager; 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.DeploymentHelper;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; 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.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType; import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet; 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;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup; 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.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.Target; 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.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionException;
@@ -106,7 +118,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private final RolloutGroupRepository rolloutGroupRepository; private final RolloutGroupRepository rolloutGroupRepository;
private final RolloutTargetGroupRepository rolloutTargetGroupRepository; private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
private final RolloutRepository rolloutRepository; private final RolloutRepository rolloutRepository;
private final TargetManagement<? extends Target> targetManagement; private final TargetRepository targetRepository;
private final DeploymentManagement deploymentManagement; private final DeploymentManagement deploymentManagement;
private final RolloutGroupManagement rolloutGroupManagement; private final RolloutGroupManagement rolloutGroupManagement;
private final RolloutManagement rolloutManagement; private final RolloutManagement rolloutManagement;
@@ -117,6 +129,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private final PlatformTransactionManager txManager; private final PlatformTransactionManager txManager;
private final TenantAware tenantAware; private final TenantAware tenantAware;
private final ContextAware contextAware; private final ContextAware contextAware;
private final SystemSecurityContext systemSecurityContext;
private final RepositoryProperties repositoryProperties; private final RepositoryProperties repositoryProperties;
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>(); private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
@@ -124,17 +137,18 @@ public class JpaRolloutExecutor implements RolloutExecutor {
public JpaRolloutExecutor( public JpaRolloutExecutor(
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository, final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
final RolloutTargetGroupRepository rolloutTargetGroupRepository, final RolloutTargetGroupRepository rolloutTargetGroupRepository,
final RolloutRepository rolloutRepository, final TargetManagement<? extends Target> targetManagement, final RolloutRepository rolloutRepository, final TargetRepository targetRepository,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final RolloutManagement rolloutManagement, final QuotaManagement quotaManagement, final RolloutManagement rolloutManagement, final QuotaManagement quotaManagement,
final RolloutGroupEvaluationManager evaluationManager, final RolloutApprovalStrategy rolloutApprovalStrategy, final RolloutGroupEvaluationManager evaluationManager, final RolloutApprovalStrategy rolloutApprovalStrategy,
final EntityManager entityManager, final PlatformTransactionManager txManager, 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.actionRepository = actionRepository;
this.rolloutGroupRepository = rolloutGroupRepository; this.rolloutGroupRepository = rolloutGroupRepository;
this.rolloutTargetGroupRepository = rolloutTargetGroupRepository; this.rolloutTargetGroupRepository = rolloutTargetGroupRepository;
this.rolloutRepository = rolloutRepository; this.rolloutRepository = rolloutRepository;
this.targetManagement = targetManagement; this.targetRepository = targetRepository;
this.deploymentManagement = deploymentManagement; this.deploymentManagement = deploymentManagement;
this.rolloutGroupManagement = rolloutGroupManagement; this.rolloutGroupManagement = rolloutGroupManagement;
this.rolloutManagement = rolloutManagement; this.rolloutManagement = rolloutManagement;
@@ -145,6 +159,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
this.txManager = txManager; this.txManager = txManager;
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
this.contextAware = contextAware; this.contextAware = contextAware;
this.systemSecurityContext = systemSecurityContext;
this.repositoryProperties = repositoryProperties; this.repositoryProperties = repositoryProperties;
} }
@@ -492,7 +507,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) { private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
if (rolloutGroup.isDynamic()) { if (rolloutGroup.isDynamic()) {
return targetManagement.countByActionsInRolloutGroup(rolloutGroup.getId()); return countByActionsInRolloutGroup(rolloutGroup.getId());
} else { } else {
return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId()); return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId());
} }
@@ -589,13 +604,13 @@ public class JpaRolloutExecutor implements RolloutExecutor {
targetsInGroupFilter = DeploymentHelper.runInNewTransaction( targetsInGroupFilter = DeploymentHelper.runInNewTransaction(
txManager, txManager,
"countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable", "countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable",
count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( count -> countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
groupTargetFilter, readyGroups, rollout.getDistributionSet().getType())); groupTargetFilter, readyGroups, rollout.getDistributionSet().getType()));
} else { // if it is a rollout retry } else { // if it is a rollout retry
targetsInGroupFilter = DeploymentHelper.runInNewTransaction( targetsInGroupFilter = DeploymentHelper.runInNewTransaction(
txManager, txManager,
"countByFailedRolloutAndNotInRolloutGroupsAndCompatible", "countByFailedRolloutAndNotInRolloutGroupsAndCompatible",
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups( count -> countByFailedRolloutAndNotInRolloutGroups(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()), readyGroups)); RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()), readyGroups));
} }
@@ -640,10 +655,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
rollout.getRolloutGroups(), RolloutGroupStatus.READY, group); rollout.getRolloutGroups(), RolloutGroupStatus.READY, group);
final Slice<Target> targets; final Slice<Target> targets;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) { if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targets = targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( targets = findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
readyGroups, targetFilter, rollout.getDistributionSet().getType(), pageRequest); readyGroups, targetFilter, rollout.getDistributionSet().getType(), pageRequest);
} else { } else {
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups( targets = findByFailedRolloutAndNotInRolloutGroups(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()), readyGroups, pageRequest); 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 // since it contains condition for device to be created before the rollout
rollout.getTargetFilterQuery(), group); rollout.getTargetFilterQuery(), group);
final Slice<Target> targets = targetManagement.findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable( final Slice<Target> targets = findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable(
rollout.getId(), groupTargetFilter, rollout.getDistributionSet().getType(), rollout.getId(), groupTargetFilter, rollout.getDistributionSet().getType(),
PageRequest.of(0, Math.toIntExact(Math.min(TRANSACTION_TARGETS, targetsLeftToAdd)))); PageRequest.of(0, Math.toIntExact(Math.min(TRANSACTION_TARGETS, targetsLeftToAdd))));
if (targets.getNumberOfElements() > 0) { if (targets.getNumberOfElements() > 0) {
@@ -798,7 +813,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private Long createActionsForTargetsInNewTransaction(final Rollout rollout, final RolloutGroup group) { private Long createActionsForTargetsInNewTransaction(final Rollout rollout, final RolloutGroup group) {
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> { 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)); group.getId(), PageRequest.of(0, JpaRolloutExecutor.TRANSACTION_TARGETS));
if (targets.getNumberOfElements() > 0) { if (targets.getNumberOfElements() > 0) {
@@ -858,7 +873,80 @@ public class JpaRolloutExecutor implements RolloutExecutor {
try { try {
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, actionRepository::countByTargetId); QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, actionRepository::countByTargetId);
} catch (final AssignmentQuotaExceededException ex) { } 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_TARGET, UPDATE_TARGET + "/type.id==" + targetType1.getId(),
READ_DISTRIBUTION_SET, READ_DISTRIBUTION_SET,
CREATE_ROLLOUT, READ_ROLLOUT, HANDLE_ROLLOUT), () -> { 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 Rollout rollout = testdataFactory.createRolloutByVariables("testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5");
final List<Long> groups = rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream(). final List<Long> groups = rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream().
map(Identifiable::getId).toList(); map(Identifiable::getId).toList();
assertThat(groups.stream().flatMap(
group -> targetManagement.findByInRolloutGroupWithoutAction(group, UNPAGED).get()).toList())
.containsExactly(target1Type1);
assertThat(groups.stream().flatMap( assertThat(groups.stream().flatMap(
group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group, UNPAGED).get()).toList()) group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group, UNPAGED).get()).toList())
.containsExactly(target1Type1); .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 // as system in context - doesn't apply scopes
final Rollout runAsSystem = systemSecurityContext.runAsSystem( final Rollout runAsSystem = systemSecurityContext.runAsSystem(
() -> testdataFactory.createRolloutByVariables( () -> testdataFactory.createRolloutByVariables(
"testRolloutAsSystem", "testDescriptionAsSystem", 3, "id==*", ds2Type2, "50", "5")); "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); final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34);
enableMultiAssignments(); enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests);
.assignDistributionSets(deploymentRequests);
assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size()); assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size());
final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).toList(); 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 io.github.classgraph.ScanResult;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.PermissionSupport; import org.eclipse.hawkbit.repository.PermissionSupport;
import org.eclipse.hawkbit.repository.TenantStatsManagement; import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; 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(0) instanceof VariableReference varRef && varRef.toStringAST().equals("#root")).isTrue();
assertThat(spelNode.getChild(1)).isInstanceOf(StringLiteral.class); assertThat(spelNode.getChild(1)).isInstanceOf(StringLiteral.class);
final StringLiteral literal = (StringLiteral) spelNode.getChild(1); 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); default -> throw new IllegalStateException("Unexpected MethodReference: " + method);
} }

View File

@@ -10,10 +10,13 @@
package org.eclipse.hawkbit.repository.jpa.management; package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat; 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.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import lombok.SneakyThrows;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; 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.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; 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.BeforeEach;
import org.junit.jupiter.api.Test; 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;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@@ -39,6 +45,15 @@ import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.repository.dynamicRolloutsMinInvolvePeriodMS=-1" }) @TestPropertySource(properties = { "hawkbit.server.repository.dynamicRolloutsMinInvolvePeriodMS=-1" })
class RolloutManagementFlowTest extends AbstractJpaIntegrationTest { class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
@Configuration
static class Config {
@Bean
SecurityContextSerializer securityContextSerializer() {
return SecurityContextSerializer.JSON_SERIALIZATION;
}
}
@BeforeEach @BeforeEach
void reset() { void reset() {
this.approvalStrategy.setApprovalNeeded(false); this.approvalStrategy.setApprovalNeeded(false);
@@ -47,19 +62,21 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/** /**
* Verifies a simple rollout flow * Verifies a simple rollout flow
*/ */
@SneakyThrows
@Test @Test
void rolloutFlow() { void rolloutFlow() {
final String rolloutName = "rollout-std"; final String rolloutName = "rollout-std";
final int amountGroups = 5; // static only final int amountGroups = 5; // static only
final String targetPrefix = "controller-rollout-std-"; 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); testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups, final Rollout rollout = callAs(
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, false); 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( final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
// add 2 targets not to be included // add 2 targets not to be included
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2); testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2);
@@ -89,22 +106,24 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/** /**
* Verifies a simple dynamic rollout flow * Verifies a simple dynamic rollout flow
*/ */
@SneakyThrows
@Test @Test
void dynamicRolloutFlow() { void dynamicRolloutFlow() {
final String rolloutName = "dynamic-rollout-std"; final String rolloutName = "dynamic-rollout-std";
final int amountGroups = 2; // static only final int amountGroups = 2; // static only
final String targetPrefix = "controller-dynamic-rollout-std-"; 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); testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups, final Rollout rollout = callAs(
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, true); 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 // rollout is READY
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout( List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
final RolloutGroup dynamic1 = groups.get(amountGroups); final RolloutGroup dynamic1 = groups.get(amountGroups);
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic
for (int i = 0; i < amountGroups; i++) { 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); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
groups = rolloutGroupManagement.findByRollout( groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
final RolloutGroup dynamic2 = groups.get(amountGroups + 1); final RolloutGroup dynamic2 = groups.get(amountGroups + 1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0); 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 * Verifies a simple dynamic rollout flow with a dynamic group template
*/ */
@SneakyThrows
@Test @Test
void dynamicRolloutTemplateFlow() { void dynamicRolloutTemplateFlow() {
final String rolloutName = "dynamic-template-rollout-std"; final String rolloutName = "dynamic-template-rollout-std";
final int amountGroups = 3; // static only final int amountGroups = 3; // static only
final String targetPrefix = "controller-template-dynamic-rollout-std-"; 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 // create rollout with amountGroups static groups * 3 targets and dynamic group template with 6 targets
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3); testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups, final Rollout rollout = callAs(
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", withUser("dynamicRolloutTemplateFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
Action.ActionType.FORCED, 1000, false, true, () -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build()); "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 // rollout is READY, amountGroups + 1 (dynamic) rollout groups and amountGroups * 3 targets in static groups
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout( List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
final RolloutGroup dynamic1 = groups.get(amountGroups); final RolloutGroup dynamic1 = groups.get(amountGroups);
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic
for (int i = 0; i < amountGroups; i++) { 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); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout( groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
final RolloutGroup dynamic2 = groups.get(amountGroups + 1); final RolloutGroup dynamic2 = groups.get(amountGroups + 1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0); 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 * Verifies a simple pure (no static groups) dynamic rollout flow with a dynamic group template
*/ */
@SneakyThrows
@Test @Test
void dynamicRolloutPureFlow() { void dynamicRolloutPureFlow() {
final String rolloutName = "pure-dynamic-rollout-std"; final String rolloutName = "pure-dynamic-rollout-std";
final String targetPrefix = "controller-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, final Rollout rollout = callAs(
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", withUser("dynamicRolloutPureFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
Action.ActionType.FORCED, 1000, false, true, () -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, 0,
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build()); "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 // rollout is READY, amountGroups + 1 (dynamic) rollout groups and amountGroups * 3 targets in static groups
assertRollout(rollout, true, RolloutStatus.READY, 1, 0); assertRollout(rollout, true, RolloutStatus.READY, 1, 0);
@@ -347,8 +369,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6); assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout( groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
final RolloutGroup dynamic2 = groups.get(1); final RolloutGroup dynamic2 = groups.get(1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0); assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
@@ -388,19 +409,21 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
/** /**
* Verifies a simple rollout flow * Verifies a simple rollout flow
*/ */
@SneakyThrows
@Test @Test
void rollout0ThresholdFlow() { void rollout0ThresholdFlow() {
final String rolloutName = "rollout-std-0threshold"; final String rolloutName = "rollout-std-0threshold";
final int amountGroups = 5; // static only final int amountGroups = 5; // static only
final String targetPrefix = "controller-rollout-std-0threshold-"; 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); testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups, final Rollout rollout = callAs(
"controllerid==" + targetPrefix + "*", distributionSet, "0", "25", false, false); 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( final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")) rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
).getContent();
// start rollout // start rollout
rolloutManagement.start(rollout.getId()); 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.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaNamedEntity_; 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.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; 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.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.NamedEntity; 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.Tag;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag; 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.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
/** /**
@@ -729,60 +725,6 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<
"notExisting", ds.getId(), "name==*")).isFalse(); "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. * Target matches filter for not existing DS.
*/ */
@@ -834,7 +776,6 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*", PAGE), () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*", PAGE),
"DistributionSet"); "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(NOT_EXIST_IDL, PAGE), "RolloutGroup");
verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet"); verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet");
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.findByAssignedDistributionSetAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet"); () -> 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) { private void validateFoundTargetsByRsql(final String rsqlFilter, final String... controllerIds) {
final Page<? extends Target> foundTargetsByMetadataAndControllerId = targetManagement.findByRsql(rsqlFilter, PAGE); final Page<? extends Target> foundTargetsByMetadataAndControllerId = targetManagement.findByRsql(rsqlFilter, PAGE);
final long foundTargetsByMetadataAndControllerIdCount = targetManagement.countByRsql(rsqlFilter); final long foundTargetsByMetadataAndControllerIdCount = targetManagement.countByRsql(rsqlFilter);

View File

@@ -50,7 +50,7 @@ import org.springframework.data.domain.Slice;
* Story: Auto assign checker * Story: Auto assign checker
*/ */
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes @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"; private static final String SPACE_AND_DESCRIPTION = " description";

View File

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

View File

@@ -39,11 +39,12 @@ public final class SpringEvalExpressions {
public static final String IS_SYSTEM_CODE = "hasAuthority('ROLE_SYSTEM_CODE')"; public static final String IS_SYSTEM_CODE = "hasAuthority('ROLE_SYSTEM_CODE')";
public static final String HAS_AUTH_SYSTEM_ADMIN = "hasAuthority('SYSTEM_ADMIN')"; public static final String HAS_AUTH_SYSTEM_ADMIN = "hasAuthority('SYSTEM_ADMIN')";
// evaluated to <permission>_<permissionGroup> (e.g. DISTRIBUTION_SET_CREATE) public static final String PERMISSION_GROUP_PLACEHOLDER = "${permissionGroup}";
public static final String HAS_CREATE_REPOSITORY = "hasPermission(#root, 'CREATE')"; // evaluated to <permission>_<permissionGroup> (e.g. CREATE_DISTRIBUTION_SET)
public static final String HAS_READ_REPOSITORY = "hasPermission(#root, 'READ')"; public static final String HAS_CREATE_REPOSITORY = "hasPermission(#root, 'CREATE_${permissionGroup}')";
public static final String HAS_UPDATE_REPOSITORY = "hasPermission(#root, 'UPDATE')"; public static final String HAS_READ_REPOSITORY = "hasPermission(#root, 'READ_${permissionGroup}')";
public static final String HAS_DELETE_REPOSITORY = "hasPermission(#root, 'DELETE')"; 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 + "')"; public static final String IS_CONTROLLER = "hasAnyRole('" + SpRole.CONTROLLER_ROLE_ANONYMOUS + "', '" + SpRole.CONTROLLER_ROLE + "')";
} }