added triggeredBy for rollouts and autoassignment (#1017)

* added triggeredBy for rollouts and autoassignment

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* use createdBy as fallback for triggeredBy in AutoAssignChecker

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* removed 'NOT NULL' from db migration scripts for JpaTargetFilterQuery

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* fixed tests

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* fixed review findings

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* inlined redundant method in JpaDeploymentManagement

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* added tests + renamed property to 'initiatedBy'

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>

* fixed review findings

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>
This commit is contained in:
Ahmed Sayed
2020-09-28 09:35:19 +02:00
committed by GitHub
parent e82b9cee70
commit ba96c68eb0
27 changed files with 173 additions and 44 deletions

View File

@@ -21,6 +21,11 @@ public interface TenantAware {
*/ */
String getCurrentTenant(); String getCurrentTenant();
/**
* @return the username of the currently logged-in user
*/
String getCurrentUsername();
/** /**
* Gives the possibility to run a certain code under a specific given * Gives the possibility to run a certain code under a specific given
* {@code tenant}. Only the given {@link TenantRunner} is executed under the * {@code tenant}. Only the given {@link TenantRunner} is executed under the

View File

@@ -76,13 +76,14 @@ public interface DeploymentManagement {
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets( List<DistributionSetAssignmentResult> assignDistributionSets(@Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
@Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
/** /**
* Assigns {@link DistributionSet}s to {@link Target}s according to the * Assigns {@link DistributionSet}s to {@link Target}s according to the
* {@link DeploymentRequest}. * {@link DeploymentRequest}.
* *
* @param initiatedBy
* the username of the user who initiated the assignment
* @param deploymentRequests * @param deploymentRequests
* information about all target-ds-assignments that shall be made * information about all target-ds-assignments that shall be made
* @param actionMessage * @param actionMessage
@@ -107,7 +108,7 @@ public interface DeploymentManagement {
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets( List<DistributionSetAssignmentResult> assignDistributionSets(String initiatedBy,
@Valid @NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage); @Valid @NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage);
/** /**

View File

@@ -130,6 +130,11 @@ public interface Action extends TenantAwareBaseEntity {
*/ */
String getExternalRef(); String getExternalRef();
/**
* @return the username that initiated this action (directly or indirectly)
*/
String getInitiatedBy();
/** /**
* checks if the {@link #getForcedTime()} is hit by the given * checks if the {@link #getForcedTime()} is hit by the given
* {@code hitTimeMillis}, by means if the given milliseconds are greater * {@code hitTimeMillis}, by means if the given milliseconds are greater

View File

@@ -77,4 +77,8 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity {
*/ */
Optional<Integer> getAutoAssignWeight(); Optional<Integer> getAutoAssignWeight();
/**
* @return the user that triggered the auto assignment
*/
String getAutoAssignInitiatedBy();
} }

View File

@@ -201,8 +201,8 @@ public abstract class AbstractDsAssignmentStrategy {
new CancelTargetAssignmentEvent(target, actionId, eventPublisherHolder.getApplicationId()))); new CancelTargetAssignmentEvent(target, actionId, eventPublisherHolder.getApplicationId())));
} }
JpaAction createTargetAction(final TargetWithActionType targetWithActionType, final List<JpaTarget> targets, JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final JpaDistributionSet set) { final List<JpaTarget> targets, final JpaDistributionSet set) {
final Optional<JpaTarget> optTarget = targets.stream() final Optional<JpaTarget> optTarget = targets.stream()
.filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst(); .filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst();
@@ -219,6 +219,7 @@ public abstract class AbstractDsAssignmentStrategy {
actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule()); actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule());
actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration()); actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration());
actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone()); actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone());
actionForTarget.setInitiatedBy(initiatedBy);
return actionForTarget; return actionForTarget;
}).orElseGet(() -> { }).orElseGet(() -> {
LOG.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId()); LOG.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId());

View File

@@ -182,33 +182,34 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
.map(entry -> DeploymentManagement.deploymentRequest(entry.getKey(), entry.getValue()).build()) .map(entry -> DeploymentManagement.deploymentRequest(entry.getKey(), entry.getValue()).build())
.collect(Collectors.toList()); .collect(Collectors.toList());
return assignDistributionSets(deploymentRequests, null, offlineDsAssignmentStrategy); return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null,
offlineDsAssignmentStrategy);
} }
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets( public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests) { final List<DeploymentRequest> deploymentRequests) {
return assignDistributionSets(deploymentRequests, null); return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null);
} }
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets( public List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage) { final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement) WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.validate(deploymentRequests); .validate(deploymentRequests);
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy); return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
} }
private List<DistributionSetAssignmentResult> assignDistributionSets( private List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage, final List<DeploymentRequest> deploymentRequests, final String actionMessage,
final AbstractDsAssignmentStrategy strategy) { final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> validatedRequests = validateRequestForAssignments(deploymentRequests); final List<DeploymentRequest> validatedRequests = validateRequestForAssignments(deploymentRequests);
final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests); final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests);
final List<DistributionSetAssignmentResult> results = assignmentsByDsIds.entrySet().stream() final List<DistributionSetAssignmentResult> results = assignmentsByDsIds.entrySet().stream()
.map(entry -> assignDistributionSetToTargetsWithRetry(entry.getKey(), entry.getValue(), actionMessage, .map(entry -> assignDistributionSetToTargetsWithRetry(initiatedBy, entry.getKey(), entry.getValue(), actionMessage,
strategy)) strategy))
.collect(Collectors.toList()); .collect(Collectors.toList());
strategy.sendDeploymentEvents(results); strategy.sendDeploymentEvents(results);
@@ -238,11 +239,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
} }
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final Long dsID, private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy, final Long dsID,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) { final AbstractDsAssignmentStrategy assignmentStrategy) {
final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback = retryContext -> assignDistributionSetToTargets( final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback = retryContext -> assignDistributionSetToTargets(
dsID, targetsWithActionType, actionMessage, assignmentStrategy); initiatedBy, dsID, targetsWithActionType, actionMessage, assignmentStrategy);
return retryTemplate.execute(retryCallback); return retryTemplate.execute(retryCallback);
} }
@@ -260,6 +261,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* status to {@link TargetUpdateStatus#IN_SYNC} <br/> * status to {@link TargetUpdateStatus#IN_SYNC} <br/>
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/> * D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
* *
* @param initiatedBy
* the username of the user who initiated the assignment
* @param dsID * @param dsID
* the ID of the distribution set to assign * the ID of the distribution set to assign
* @param targetsWithActionType * @param targetsWithActionType
@@ -274,7 +277,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* {@link SoftwareModuleType} are not assigned as define by the * {@link SoftwareModuleType} are not assigned as define by the
* {@link DistributionSetType}. * {@link DistributionSetType}.
*/ */
private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID, private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsID,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) { final AbstractDsAssignmentStrategy assignmentStrategy) {
@@ -296,8 +299,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final List<TargetWithActionType> existingTargetsWithActionType = targetsWithActionType.stream() final List<TargetWithActionType> existingTargetsWithActionType = targetsWithActionType.stream()
.filter(target -> existingTargetIds.contains(target.getControllerId())).collect(Collectors.toList()); .filter(target -> existingTargetIds.contains(target.getControllerId())).collect(Collectors.toList());
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(existingTargetsWithActionType, final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(initiatedBy,
actionMessage, assignmentStrategy, distributionSetEntity, targetEntities); existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSetEntity,
targetEntities);
return buildAssignmentResult(distributionSetEntity, assignedActions, existingTargetsWithActionType.size()); return buildAssignmentResult(distributionSetEntity, assignedActions, existingTargetsWithActionType.size());
} }
@@ -310,7 +314,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
Collections.emptyList()); Collections.emptyList());
} }
private List<JpaAction> doAssignDistributionSetToTargets( private List<JpaAction> doAssignDistributionSetToTargets(final String initiatedBy,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet distributionSetEntity, final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet distributionSetEntity,
final List<JpaTarget> targetEntities) { final List<JpaTarget> targetEntities) {
@@ -325,8 +329,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets); targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity, setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity,
targetEntitiesIdsChunks); targetEntitiesIdsChunks);
final List<JpaAction> assignedActions = createActions(targetsWithActionType, targetEntities, assignmentStrategy, final List<JpaAction> assignedActions = createActions(initiatedBy, targetsWithActionType, targetEntities,
distributionSetEntity); assignmentStrategy, distributionSetEntity);
// create initial action status when action is created so we remember // create initial action status when action is created so we remember
// the initial running status because we will change the status // the initial running status because we will change the status
// of the action itself and with this action status we have a nicer // of the action itself and with this action status we have a nicer
@@ -417,12 +421,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser); assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser);
} }
private List<JpaAction> createActions(final Collection<TargetWithActionType> targetsWithActionType, private List<JpaAction> createActions(final String initiatedBy,
final List<JpaTarget> targets, final AbstractDsAssignmentStrategy assignmentStrategy, final Collection<TargetWithActionType> targetsWithActionType, final List<JpaTarget> targets,
final JpaDistributionSet set) { final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet set) {
return targetsWithActionType.stream().map(twt -> assignmentStrategy.createTargetAction(twt, targets, set)) return targetsWithActionType.stream()
.filter(Objects::nonNull).map(actionRepository::save).collect(Collectors.toList()); .map(twt -> assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set))
.filter(Objects::nonNull)
.map(actionRepository::save)
.collect(Collectors.toList());
} }
private void createActionsStatus(final Collection<JpaAction> actions, private void createActionsStatus(final Collection<JpaAction> actions,

View File

@@ -624,6 +624,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
action.setStatus(Status.SCHEDULED); action.setStatus(Status.SCHEDULED);
action.setRollout(rollout); action.setRollout(rollout);
action.setRolloutGroup(rolloutGroup); action.setRolloutGroup(rolloutGroup);
action.setInitiatedBy(rollout.getCreatedBy());
rollout.getWeight().ifPresent(action::setWeight); rollout.getWeight().ifPresent(action::setWeight);
actionRepository.save(action); actionRepository.save(action);
}); });

View File

@@ -35,13 +35,13 @@ import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper; import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.jpa.utils.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
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.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
@@ -74,6 +74,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final TenantConfigurationManagement tenantConfigurationManagement; private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext; private final SystemSecurityContext systemSecurityContext;
private final TenantAware tenantAware;
private final Database database; private final Database database;
@@ -81,7 +82,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer, final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final Database database, final TenantConfigurationManagement tenantConfigurationManagement, final Database database, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) { final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware) {
this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
@@ -90,6 +91,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
this.database = database; this.database = database;
this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext; this.systemSecurityContext = systemSecurityContext;
this.tenantAware = tenantAware;
} }
@Override @Override
@@ -240,6 +242,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetFilterQuery.setAutoAssignDistributionSet(null); targetFilterQuery.setAutoAssignDistributionSet(null);
targetFilterQuery.setAutoAssignActionType(null); targetFilterQuery.setAutoAssignActionType(null);
targetFilterQuery.setAutoAssignWeight(null); targetFilterQuery.setAutoAssignWeight(null);
targetFilterQuery.setAutoAssignInitiatedBy(null);
} else { } else {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update); WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
// we cannot be sure that the quota was enforced at creation time // we cannot be sure that the quota was enforced at creation time
@@ -250,6 +253,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId()); final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId());
verifyDistributionSetAndThrowExceptionIfNotValid(ds); verifyDistributionSetAndThrowExceptionIfNotValid(ds);
targetFilterQuery.setAutoAssignDistributionSet(ds); targetFilterQuery.setAutoAssignDistributionSet(ds);
targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername());
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType())); targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
targetFilterQuery.setAutoAssignWeight(update.getWeight()); targetFilterQuery.setAutoAssignWeight(update.getWeight());
} }

View File

@@ -90,9 +90,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
protected JpaAction createTargetAction(final TargetWithActionType targetWithActionType, protected JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) { final List<JpaTarget> targets, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(targetWithActionType, targets, set); final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set);
if (result != null) { if (result != null) {
result.setStatus(Status.FINISHED); result.setStatus(Status.FINISHED);
result.setActive(Boolean.FALSE); result.setActive(Boolean.FALSE);

View File

@@ -127,9 +127,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
JpaAction createTargetAction(final TargetWithActionType targetWithActionType, final List<JpaTarget> targets, JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final JpaDistributionSet set) { final List<JpaTarget> targets, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(targetWithActionType, targets, set); final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set);
if (result != null) { if (result != null) {
result.setStatus(Status.RUNNING); result.setStatus(Status.RUNNING);
} }

View File

@@ -533,6 +533,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* to access quotas * to access quotas
* @param properties * @param properties
* JPA properties * JPA properties
* @param tenantAware
* the {@link TenantAware} bean holding the tenant information
* *
* @return a new {@link TargetFilterQueryManagement} * @return a new {@link TargetFilterQueryManagement}
*/ */
@@ -543,10 +545,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final VirtualPropertyReplacer virtualPropertyReplacer, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement, final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) { final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware) {
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository, return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository,
virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(), virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(),
tenantConfigurationManagement, systemSecurityContext); tenantConfigurationManagement, systemSecurityContext, tenantAware);
} }
/** /**

View File

@@ -32,6 +32,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
/** /**
* Checks if targets need a new distribution set (DS) based on the target filter * Checks if targets need a new distribution set (DS) based on the target filter
@@ -146,12 +147,19 @@ public class AutoAssignChecker implements AutoAssignExecutor {
targetFilterQuery.getAutoAssignWeight().orElse(null), PAGE_SIZE); targetFilterQuery.getAutoAssignWeight().orElse(null), PAGE_SIZE);
final int count = deploymentRequests.size(); final int count = deploymentRequests.size();
if (count > 0) { if (count > 0) {
deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage); deploymentManagement.assignDistributionSets(getAutoAssignmentInitiatedBy(targetFilterQuery),
deploymentRequests, actionMessage);
} }
return count; return count;
}); });
} }
private static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
return StringUtils.isEmpty(targetFilterQuery.getAutoAssignInitiatedBy()) ?
targetFilterQuery.getCreatedBy() :
targetFilterQuery.getAutoAssignInitiatedBy();
}
/** /**
* Gets all matching targets with the designated action from the target * Gets all matching targets with the designated action from the target
* management * management

View File

@@ -36,6 +36,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
@EntityListeners({ AuditingEntityListener.class, EntityPropertyChangeListener.class, EntityInterceptorListener.class }) @EntityListeners({ AuditingEntityListener.class, EntityPropertyChangeListener.class, EntityInterceptorListener.class })
public abstract class AbstractJpaBaseEntity implements BaseEntity { public abstract class AbstractJpaBaseEntity implements BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
protected static final int USERNAME_FIELD_LENGTH = 64;
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -67,7 +68,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
@Override @Override
@Access(AccessType.PROPERTY) @Access(AccessType.PROPERTY)
@Column(name = "created_by", insertable = true, updatable = false, nullable = false, length = 64) @Column(name = "created_by", insertable = true, updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
public String getCreatedBy() { public String getCreatedBy() {
return createdBy; return createdBy;
} }
@@ -81,7 +82,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
@Override @Override
@Access(AccessType.PROPERTY) @Access(AccessType.PROPERTY)
@Column(name = "last_modified_by", insertable = true, updatable = true, nullable = false, length = 64) @Column(name = "last_modified_by", insertable = true, updatable = true, nullable = false, length = USERNAME_FIELD_LENGTH)
public String getLastModifiedBy() { public String getLastModifiedBy() {
return lastModifiedBy; return lastModifiedBy;
} }

View File

@@ -137,6 +137,9 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Column(name = "external_ref", length = Action.EXTERNAL_REF_MAX_LENGTH) @Column(name = "external_ref", length = Action.EXTERNAL_REF_MAX_LENGTH)
private String externalRef; private String externalRef;
@Column(name = "initiated_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
private String initiatedBy;
@Override @Override
public DistributionSet getDistributionSet() { public DistributionSet getDistributionSet() {
return distributionSet; return distributionSet;
@@ -363,4 +366,13 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
public String getExternalRef() { public String getExternalRef() {
return externalRef; return externalRef;
} }
public void setInitiatedBy(final String initiatedBy) {
this.initiatedBy = initiatedBy;
}
@Override
public String getInitiatedBy() {
return initiatedBy;
}
} }

View File

@@ -77,6 +77,9 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
@Column(name = "auto_assign_weight", nullable = true) @Column(name = "auto_assign_weight", nullable = true)
private Integer autoAssignWeight; private Integer autoAssignWeight;
@Column(name = "auto_assign_initiated_by", nullable = true, length = USERNAME_FIELD_LENGTH)
private String autoAssignInitiatedBy;
public JpaTargetFilterQuery() { public JpaTargetFilterQuery() {
// Default constructor for JPA. // Default constructor for JPA.
} }
@@ -149,6 +152,14 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
this.autoAssignWeight = weight; this.autoAssignWeight = weight;
} }
public String getAutoAssignInitiatedBy() {
return autoAssignInitiatedBy;
}
public void setAutoAssignInitiatedBy(final String autoAssignInitiatedBy) {
this.autoAssignInitiatedBy = autoAssignInitiatedBy;
}
@Override @Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) { public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent( EventPublisherHolder.getInstance().getEventPublisher().publishEvent(

View File

@@ -0,0 +1,2 @@
ALTER TABLE sp_action ADD COLUMN initiated_by VARCHAR(64) NOT NULL;
ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_initiated_by VARCHAR(64);

View File

@@ -0,0 +1,2 @@
ALTER TABLE sp_action ADD COLUMN initiated_by VARCHAR(64) NOT NULL;
ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_initiated_by VARCHAR(64);

View File

@@ -0,0 +1,2 @@
ALTER TABLE sp_action ADD COLUMN initiated_by VARCHAR(64) NOT NULL;
ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_initiated_by VARCHAR(64);

View File

@@ -0,0 +1,5 @@
ALTER TABLE sp_action
ADD COLUMN initiated_by VARCHAR (64) NOT NULL;
ALTER TABLE sp_target_filter_query
ADD COLUMN auto_assign_initiated_by VARCHAR (64);

View File

@@ -0,0 +1,2 @@
ALTER TABLE sp_action ADD initiated_by VARCHAR(64) NOT NULL;
ALTER TABLE sp_target_filter_query ADD auto_assign_initiated_by VARCHAR(64);

View File

@@ -104,6 +104,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
generateAction.setTarget(testdataFactory.createTarget("Test")); generateAction.setTarget(testdataFactory.createTarget("Test"));
generateAction.setDistributionSet(dsA); generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING); generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
final Action action = actionRepository.save(generateAction); final Action action = actionRepository.save(generateAction);

View File

@@ -84,6 +84,7 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
generateAction.setTarget(target); generateAction.setTarget(target);
generateAction.setDistributionSet(distributionSet); generateAction.setDistributionSet(distributionSet);
generateAction.setStatus(Status.RUNNING); generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
return actionRepository.save(generateAction); return actionRepository.save(generateAction);
} }

View File

@@ -480,7 +480,9 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionRepository.count()).isEqualTo(20); assertThat(actionRepository.count()).isEqualTo(20);
assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active") assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active")
.allMatch(action -> !action.isActive()); .allMatch(action -> !action.isActive())
.as("Actions should be initiated by current user")
.allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername()));
assertThat(targetManagement.findByInstalledDistributionSet(PAGE, ds.getId()).getContent()) assertThat(targetManagement.findByInstalledDistributionSet(PAGE, ds.getId()).getContent())
.usingElementComparator(controllerIdComparator()).containsAll(targets).hasSize(10) .usingElementComparator(controllerIdComparator()).containsAll(targets).hasSize(10)
@@ -514,6 +516,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(getResultingActionCount(assignmentResults)).isEqualTo(4); assertThat(getResultingActionCount(assignmentResults)).isEqualTo(4);
targetIds.forEach(controllerId -> { targetIds.forEach(controllerId -> {
final List<Long> assignedDsIds = actionRepository.findByTargetControllerId(PAGE, controllerId).stream() final List<Long> assignedDsIds = actionRepository.findByTargetControllerId(PAGE, controllerId).stream()
.peek(a -> assertThat(a.getInitiatedBy()).as("Actions should be initiated by current user")
.isEqualTo(tenantAware.getCurrentUsername()))
.map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); .map(action -> action.getDistributionSet().getId()).collect(Collectors.toList());
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds); assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
}); });
@@ -587,10 +591,12 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
private void assertDsExclusivelyAssignedToTargets(final List<Target> targets, final long dsId, final boolean active, private void assertDsExclusivelyAssignedToTargets(final List<Target> targets, final long dsId, final boolean active,
final Status status) { final Status status) {
final List<Action> assignment = actionRepository.findByDistributionSetId(PAGE, dsId).getContent(); final List<Action> assignment = actionRepository.findByDistributionSetId(PAGE, dsId).getContent();
final String currentUsername = tenantAware.getCurrentUsername();
assertThat(assignment).hasSize(10).allMatch(action -> action.isActive() == active) assertThat(assignment).hasSize(10).allMatch(action -> action.isActive() == active)
.as("Is assigned to DS " + dsId).allMatch(action -> action.getDistributionSet().getId().equals(dsId)) .as("Is assigned to DS " + dsId).allMatch(action -> action.getDistributionSet().getId().equals(dsId))
.as("State is " + status).allMatch(action -> action.getStatus() == status); .as("State is " + status).allMatch(action -> action.getStatus() == status)
.as("Initiated by " + currentUsername).allMatch(a -> a.getInitiatedBy().equals(currentUsername));
final long[] targetIds = targets.stream().mapToLong(Target::getId).toArray(); final long[] targetIds = targets.stream().mapToLong(Target::getId).toArray();
assertThat(targetIds).as("All targets represented in assignment").containsExactlyInAnyOrder( assertThat(targetIds).as("All targets represented in assignment").containsExactlyInAnyOrder(
assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray()); assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray());
@@ -617,7 +623,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toList()); final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toList());
targets.forEach(target -> { targets.forEach(target -> {
final List<Long> assignedDsIds = actionRepository.findByTargetControllerId(PAGE, target.getControllerId()) final List<Long> assignedDsIds = actionRepository.findByTargetControllerId(PAGE, target.getControllerId())
.stream().map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); .stream()
.peek(a -> assertThat(a.getInitiatedBy()).as("Initiated by current user")
.isEqualTo(tenantAware.getCurrentUsername()))
.map(action -> action.getDistributionSet().getId()).collect(Collectors.toList());
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds); assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
}); });
} }
@@ -647,6 +656,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
targets.forEach(target -> { targets.forEach(target -> {
actionRepository.findByTargetControllerId(PAGE, target.getControllerId()).forEach(action -> { actionRepository.findByTargetControllerId(PAGE, target.getControllerId()).forEach(action -> {
assertThat(action.getDistributionSet().getId()).isIn(dsIds); assertThat(action.getDistributionSet().getId()).isIn(dsIds);
assertThat(action.getInitiatedBy()).as("Should be Initiated by current user")
.isEqualTo(tenantAware.getCurrentUsername());
deploymentManagement.cancelAction(action.getId()); deploymentManagement.cancelAction(action.getId());
}); });
}); });
@@ -851,7 +862,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assignDistributionSet(ds, savedDeployedTargets); assignDistributionSet(ds, savedDeployedTargets);
// verify that one Action for each assignDistributionSet // verify that one Action for each assignDistributionSet
assertThat(actionRepository.findAll(PAGE).getNumberOfElements()).as("wrong size of actions").isEqualTo(20); final Page<JpaAction> actions = actionRepository.findAll(PAGE);
assertThat(actions.getNumberOfElements()).as("wrong size of actions").isEqualTo(20);
assertThat(actions).as("Actions should be initiated by current user")
.allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername()));
final Iterable<Target> allFoundTargets = targetManagement.findAll(PAGE).getContent(); final Iterable<Target> allFoundTargets = targetManagement.findAll(PAGE).getContent();
@@ -945,6 +959,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// retrieving all Actions created by the assignDistributionSet call // retrieving all Actions created by the assignDistributionSet call
final Page<JpaAction> page = actionRepository.findAll(PAGE); final Page<JpaAction> page = actionRepository.findAll(PAGE);
assertThat(page).as("Actions should be initiated by current user")
.allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername()));
// and verify the number // and verify the number
assertThat(page.getTotalElements()).as("wrong size of actions") assertThat(page.getTotalElements()).as("wrong size of actions")
.isEqualTo(noOfDeployedTargets * noOfDistributionSets); .isEqualTo(noOfDeployedTargets * noOfDistributionSets);

View File

@@ -252,6 +252,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// running // running
final List<Action> runningActions = findActionsByRolloutAndStatus(createdRollout, Status.RUNNING); final List<Action> runningActions = findActionsByRolloutAndStatus(createdRollout, Status.RUNNING);
assertThat(runningActions).hasSize(amountTargetsForRollout / amountGroups); assertThat(runningActions).hasSize(amountTargetsForRollout / amountGroups);
assertThat(runningActions).as("Created actions are initiated by rollout creator")
.allMatch(a -> a.getInitiatedBy().equals(createdRollout.getCreatedBy()));
// the rest targets are only scheduled // the rest targets are only scheduled
assertThat(findActionsByRolloutAndStatus(createdRollout, Status.SCHEDULED)) assertThat(findActionsByRolloutAndStatus(createdRollout, Status.SCHEDULED))
.hasSize(amountTargetsForRollout - (amountTargetsForRollout / amountGroups)); .hasSize(amountTargetsForRollout - (amountTargetsForRollout / amountGroups));

View File

@@ -12,10 +12,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -26,6 +28,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import io.qameta.allure.Description; import io.qameta.allure.Description;
@@ -44,6 +47,9 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
@Autowired @Autowired
private AutoAssignChecker autoAssignChecker; private AutoAssignChecker autoAssignChecker;
@Autowired
private ActionRepository actionRepository;
@Test @Test
@Description("Verifies that a running action is auto canceled by a AutoAssignment which assigns another distribution-set.") @Description("Verifies that a running action is auto canceled by a AutoAssignment which assigns another distribution-set.")
public void autoAssignDistributionSetAndAutoCloseOldActions() { public void autoAssignDistributionSetAndAutoCloseOldActions() {
@@ -99,10 +105,11 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); final DistributionSet setB = testdataFactory.createDistributionSet("dsB");
// target filter query that matches all targets // target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(
.create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")); entityFactory.targetFilterQuery()
targetFilterQueryManagement.updateAutoAssignDS( .updateAutoAssign(targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId()).ds(setA.getId())); entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId())
.ds(setA.getId()));
final String targetDsAIdPref = "targ"; final String targetDsAIdPref = "targ";
final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref, final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref,
@@ -134,6 +141,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// first 5 should keep their dsB, because they already had the dsA once // first 5 should keep their dsB, because they already had the dsA once
verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount);
verifyThatCreatedActionsAreInitiatedByCurrentUser(targetFilterQuery, setA, targets);
} }
@Test @Test
@@ -208,6 +216,18 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
} }
@Step
private void verifyThatCreatedActionsAreInitiatedByCurrentUser(final TargetFilterQuery targetFilterQuery,
final DistributionSet distributionSet, final List<Target> targets) {
final Set<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet());
actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId())
.stream().filter(a -> targetIds.contains(a.getTarget().getControllerId()))
.forEach(a -> assertThat(a.getInitiatedBy()).as(
"Action should be initiated by the user who initiated the auto assignment")
.isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy()));
}
@Test @Test
@Description("Test auto assignment of a distribution set with FORCED, SOFT and DOWNLOAD_ONLY action types") @Description("Test auto assignment of a distribution set with FORCED, SOFT and DOWNLOAD_ONLY action types")
public void checkAutoAssignWithDifferentActionTypes() { public void checkAutoAssignWithDifferentActionTypes() {

View File

@@ -47,6 +47,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
action.setTarget(target); action.setTarget(target);
action.setStatus(Status.RUNNING); action.setStatus(Status.RUNNING);
action.setWeight(45); action.setWeight(45);
action.setInitiatedBy(tenantAware.getCurrentUsername());
target.addAction(action); target.addAction(action);
actionRepository.save(action); actionRepository.save(action);
@@ -58,6 +59,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
newAction.setStatus(Status.RUNNING); newAction.setStatus(Status.RUNNING);
newAction.setTarget(target); newAction.setTarget(target);
newAction.setWeight(45); newAction.setWeight(45);
newAction.setInitiatedBy(tenantAware.getCurrentUsername());
actionRepository.save(newAction); actionRepository.save(newAction);
target.addAction(newAction); target.addAction(newAction);
} }

View File

@@ -46,6 +46,18 @@ public class SecurityContextTenantAware implements TenantAware {
return null; return null;
} }
@Override
public String getCurrentUsername() {
final SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null) {
final Object principal = context.getAuthentication().getPrincipal();
if (principal instanceof UserPrincipal) {
return ((UserPrincipal) principal).getUsername();
}
}
return null;
}
@Override @Override
public <T> T runAsTenant(final String tenant, final TenantRunner<T> callable) { public <T> T runAsTenant(final String tenant, final TenantRunner<T> callable) {
final SecurityContext originalContext = SecurityContextHolder.getContext(); final SecurityContext originalContext = SecurityContextHolder.getContext();