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:
Stefan Behl
2021-04-15 12:23:14 +02:00
committed by GitHub
parent eaf6be8c94
commit cf67467fb5
14 changed files with 354 additions and 90 deletions

View File

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

View File

@@ -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);
}
/**

View File

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

View File

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

View File

@@ -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)