Execute rollouts and auto assignments in the correct user context (#1100)
* Execute rollouts and auto assignments in correct user context Signed-off-by: Stefan Behl <stefan.behl@bosch.io> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch.io> * Cleanup usage of lenient Signed-off-by: Stefan Behl <stefan.behl@bosch.io>
This commit is contained in:
@@ -14,6 +14,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -62,6 +63,7 @@ import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
@@ -564,8 +566,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
try {
|
||||
long actionsCreated;
|
||||
do {
|
||||
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(),
|
||||
TRANSACTION_TARGETS);
|
||||
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(), TRANSACTION_TARGETS);
|
||||
totalActionsCreated += actionsCreated;
|
||||
} while (actionsCreated > 0);
|
||||
|
||||
@@ -576,7 +577,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
return totalActionsCreated;
|
||||
}
|
||||
|
||||
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) {
|
||||
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId,
|
||||
final int limit) {
|
||||
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
|
||||
final PageRequest pageRequest = PageRequest.of(0, limit);
|
||||
final Rollout rollout = rolloutRepository.findById(rolloutId)
|
||||
@@ -828,17 +830,21 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
|
||||
try {
|
||||
rollouts.forEach(rolloutId -> DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId,
|
||||
status -> executeFittingHandler(rolloutId)));
|
||||
status -> handleRollout(rolloutId)));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private long executeFittingHandler(final long rolloutId) {
|
||||
LOGGER.debug("handle rollout {}", rolloutId);
|
||||
private long handleRollout(final long rolloutId) {
|
||||
final JpaRollout rollout = rolloutRepository.findById(rolloutId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
|
||||
runInUserContext(rollout, () -> handleRollout(rollout));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void handleRollout(final JpaRollout rollout) {
|
||||
LOGGER.debug("Handle rollout {}", rollout.getId());
|
||||
switch (rollout.getStatus()) {
|
||||
case CREATING:
|
||||
handleCreateRollout(rollout);
|
||||
@@ -859,8 +865,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void handleStartingRollout(final Rollout rollout) {
|
||||
@@ -1147,4 +1151,9 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
|
||||
actionRepository::countByTargetId);
|
||||
}
|
||||
|
||||
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
|
||||
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), tenantAware);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -760,9 +760,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
AutoAssignExecutor autoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||
final PlatformTransactionManager transactionManager) {
|
||||
final PlatformTransactionManager transactionManager, final TenantAware tenantAware) {
|
||||
return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement,
|
||||
transactionManager);
|
||||
transactionManager, tenantAware);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.autoassign;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
@@ -24,6 +25,7 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -45,14 +47,6 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignChecker.class);
|
||||
|
||||
private final TargetFilterQueryManagement targetFilterQueryManagement;
|
||||
|
||||
private final TargetManagement targetManagement;
|
||||
|
||||
private final DeploymentManagement deploymentManagement;
|
||||
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
|
||||
/**
|
||||
* Maximum for target filter queries with auto assign DS Maximum for targets
|
||||
* that are fetched in one turn
|
||||
@@ -65,6 +59,16 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
*/
|
||||
private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s";
|
||||
|
||||
private final TargetFilterQueryManagement targetFilterQueryManagement;
|
||||
|
||||
private final TargetManagement targetManagement;
|
||||
|
||||
private final DeploymentManagement deploymentManagement;
|
||||
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
|
||||
private final TenantAware tenantAware;
|
||||
|
||||
/**
|
||||
* Instantiates a new auto assign checker
|
||||
*
|
||||
@@ -76,14 +80,17 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
* to assign distribution sets to targets
|
||||
* @param transactionManager
|
||||
* to run transactions
|
||||
* @param tenantAware
|
||||
* to handle the tenant context
|
||||
*/
|
||||
public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||
final PlatformTransactionManager transactionManager) {
|
||||
final PlatformTransactionManager transactionManager, final TenantAware tenantAware) {
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
this.targetManagement = targetManagement;
|
||||
this.deploymentManagement = deploymentManagement;
|
||||
this.transactionManager = transactionManager;
|
||||
this.tenantAware = tenantAware;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,10 +102,9 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
|
||||
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(pageRequest);
|
||||
|
||||
// we should ensure that the filter queries are executed
|
||||
// in the order of weights
|
||||
// make sure the filter queries are executed in the order of weights
|
||||
for (final TargetFilterQuery filterQuery : filterQueries) {
|
||||
checkByTargetFilterQueryAndAssignDS(filterQuery);
|
||||
runInUserContext(filterQuery, () -> checkByTargetFilterQueryAndAssignDS(filterQuery));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,7 +133,7 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs one page of target assignments within a dedicated transaction
|
||||
*
|
||||
@@ -154,12 +160,6 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
* management
|
||||
@@ -168,8 +168,6 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
* the query the targets have to match
|
||||
* @param dsId
|
||||
* dsId the targets are not allowed to have in their action history
|
||||
* @param type
|
||||
* action type for targets auto assignment
|
||||
* @param count
|
||||
* maximum amount of targets to retrieve
|
||||
* @return list of targets with action type
|
||||
@@ -185,5 +183,16 @@ public class AutoAssignChecker implements AutoAssignExecutor {
|
||||
return targets.getContent().stream().map(t -> DeploymentManagement.deploymentRequest(t.getControllerId(), dsId)
|
||||
.setActionType(autoAssignActionType).setWeight(weight).build()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void runInUserContext(final TargetFilterQuery targetFilterQuery, final Runnable handler) {
|
||||
DeploymentHelper.runInNonSystemContext(handler,
|
||||
() -> Objects.requireNonNull(getAutoAssignmentInitiatedBy(targetFilterQuery)), tenantAware);
|
||||
}
|
||||
|
||||
private static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
|
||||
return StringUtils.isEmpty(targetFilterQuery.getAutoAssignInitiatedBy()) ?
|
||||
targetFilterQuery.getCreatedBy() :
|
||||
targetFilterQuery.getAutoAssignInitiatedBy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
@@ -20,12 +21,17 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class for deployment related topics.
|
||||
@@ -33,6 +39,8 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
*/
|
||||
public final class DeploymentHelper {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeploymentHelper.class);
|
||||
|
||||
private DeploymentHelper() {
|
||||
// utility class
|
||||
}
|
||||
@@ -110,4 +118,37 @@ public final class DeploymentHelper {
|
||||
def.setIsolationLevel(isolationLevel);
|
||||
return new TransactionTemplate(txManager, def).execute(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given handler in a non-system user context. Switches to the user
|
||||
* which is provided by the given callback.
|
||||
*
|
||||
* @param handler
|
||||
* The handler to be invoked in the right user context.
|
||||
* @param username
|
||||
* Callback to obtain the real user the user context should be
|
||||
* established for.
|
||||
* @param tenantAware
|
||||
* The {@link TenantAware} bean to determine the current tenant
|
||||
* context.
|
||||
*/
|
||||
public static void runInNonSystemContext(@NotNull final Runnable handler, @NotNull final Supplier<String> username,
|
||||
@NotNull final TenantAware tenantAware) {
|
||||
final String currentUser = tenantAware.getCurrentUsername();
|
||||
if (isNonSystemUser(currentUser)) {
|
||||
handler.run();
|
||||
return;
|
||||
}
|
||||
final String user = username.get();
|
||||
LOG.debug("Switching user context from '{}' to '{}'", currentUser, user);
|
||||
tenantAware.runAsTenantAsUser(tenantAware.getCurrentTenant(), user, () -> {
|
||||
handler.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isNonSystemUser(final String user) {
|
||||
return (!(StringUtils.isEmpty(user) || SecurityContextTenantAware.SYSTEM_USER.equals(user)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -38,11 +39,13 @@ import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.cloud.bus.ConditionalOnBusEnabled;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
@@ -118,23 +121,30 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
}
|
||||
|
||||
@Bean
|
||||
TenantAware tenantAware() {
|
||||
return new SecurityContextTenantAware();
|
||||
UserAuthoritiesResolver authoritiesResolver() {
|
||||
return (tenant, username) -> Collections.emptyList();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TenantAwareCacheManager cacheManager() {
|
||||
return new TenantAwareCacheManager(new CaffeineCacheManager(), tenantAware());
|
||||
TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) {
|
||||
return new SecurityContextTenantAware(authoritiesResolver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TenantAwareCacheManager cacheManager(final TenantAware tenantAware) {
|
||||
return new TenantAwareCacheManager(new CaffeineCacheManager(), tenantAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean for the download id cache.
|
||||
*
|
||||
* @param cacheManager
|
||||
* The {@link CacheManager}
|
||||
* @return the cache
|
||||
*/
|
||||
@Bean
|
||||
DownloadIdCache downloadIdCache() {
|
||||
return new DefaultDownloadIdCache(cacheManager());
|
||||
DownloadIdCache downloadIdCache(final CacheManager cacheManager) {
|
||||
return new DefaultDownloadIdCache(cacheManager);
|
||||
}
|
||||
|
||||
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
|
||||
|
||||
Reference in New Issue
Block a user