Fix circular rollout dependencies (#1337)

* Do some refactoring to fix dependencies between rollout management, executor and evaluator beans.
* Move rollout retrieving in same transaction as execution.
* Do some refactoring. Extend logging and exception handling.
* Remove unnecessary transactional and validation annotations.
* remove catching never thrown bean
* Fix new rollout handling API
This commit is contained in:
Michael Herdt
2023-04-03 09:13:00 +02:00
committed by GitHub
parent 17bf633df9
commit fbda9764b1
29 changed files with 537 additions and 352 deletions

View File

@@ -34,8 +34,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.EvaluatorNotConfiguredException;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupEvaluationManager;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
@@ -53,8 +53,6 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
@@ -105,8 +103,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private final EventPublisherHolder eventPublisherHolder;
private final PlatformTransactionManager txManager;
private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final ApplicationContext context;
private final RolloutGroupEvaluationManager evaluationManager;
private final RolloutManagement rolloutManagement;
/**
* Constructor
*/
@@ -117,7 +116,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement,
final DeploymentManagement deploymentManagement, final TargetManagement targetManagement,
final EventPublisherHolder eventPublisherHolder, final PlatformTransactionManager txManager,
final RolloutApprovalStrategy rolloutApprovalStrategy, final ApplicationContext context) {
final RolloutApprovalStrategy rolloutApprovalStrategy,
final RolloutGroupEvaluationManager evaluationManager, final RolloutManagement rolloutManagement) {
this.rolloutTargetGroupRepository = rolloutTargetGroupRepository;
this.entityManager = entityManager;
this.rolloutRepository = rolloutRepository;
@@ -132,7 +132,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
this.eventPublisherHolder = eventPublisherHolder;
this.txManager = txManager;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.context = context;
this.evaluationManager = evaluationManager;
this.rolloutManagement = rolloutManagement;
}
@Override
@@ -285,7 +286,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
LOGGER.debug(
"handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING",
rollout.getId());
context.getBean(RolloutManagement.class).start(rollout.getId());
rolloutManagement.start(rollout.getId());
}
}
@@ -417,11 +418,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
try {
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the error action bean {}",
rolloutGroup.getErrorAction().getBeanName(), e);
evaluationManager.getErrorActionEvaluator(rolloutGroup.getErrorAction()).exec(rollout, rolloutGroup);
} catch (final EvaluatorNotConfiguredException e) {
LOGGER.error("Something bad happened when accessing the error action bean {}",
rolloutGroup.getErrorAction().name(), e);
}
}
@@ -443,11 +443,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return false;
}
try {
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
rolloutGroup, rolloutGroup.getErrorConditionExp());
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the error condition bean {}",
errorCondition.getBeanName(), e);
return evaluationManager.getErrorConditionEvaluator(errorCondition).eval(rollout, rolloutGroup,
rolloutGroup.getErrorConditionExp());
} catch (final EvaluatorNotConfiguredException e) {
LOGGER.error("Something bad happened when accessing the error condition bean {}", errorCondition.name(), e);
return false;
}
}
@@ -456,9 +455,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final RolloutGroupSuccessCondition finishCondition) {
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
try {
final boolean isFinished = context
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
final boolean isFinished = evaluationManager.getSuccessConditionEvaluator(finishCondition).eval(rollout,
rolloutGroup, rolloutGroup.getSuccessConditionExp());
if (isFinished) {
LOGGER.debug("Rolloutgroup {} is finished, starting next group", rolloutGroup);
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
@@ -466,16 +464,15 @@ public class JpaRolloutExecutor implements RolloutExecutor {
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
}
return isFinished;
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
finishCondition.getBeanName(), e);
} catch (final EvaluatorNotConfiguredException e) {
LOGGER.error("Something bad happened when accessing the finish condition or success action bean {}",
finishCondition.name(), e);
return false;
}
}
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
rolloutGroup, rolloutGroup.getSuccessActionExp());
evaluationManager.getSuccessActionEvaluator(rolloutGroup.getSuccessAction()).exec(rollout, rolloutGroup);
}
private void startFirstRolloutGroup(final Rollout rollout) {

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2023 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutHandler;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.transaction.PlatformTransactionManager;
/**
* JPA implementation of {@link RolloutHandler}.
*/
public class JpaRolloutHandler implements RolloutHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutHandler.class);
private final TenantAware tenantAware;
private final RolloutManagement rolloutManagement;
private final RolloutExecutor rolloutExecutor;
private final LockRegistry lockRegistry;
private final PlatformTransactionManager txManager;
/**
* Constructor
*
* @param tenantAware
* the {@link TenantAware} bean holding the tenant information
* @param rolloutManagement
* to fetch rollout related information from the datasource
* @param rolloutExecutor
* to trigger executions for a specific rollout
* @param lockRegistry
* to lock processes
* @param txManager
* transaction manager interface
*/
public JpaRolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement,
final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry,
final PlatformTransactionManager txManager) {
this.tenantAware = tenantAware;
this.rolloutManagement = rolloutManagement;
this.rolloutExecutor = rolloutExecutor;
this.lockRegistry = lockRegistry;
this.txManager = txManager;
}
@Override
public void handleAll() {
final List<Long> rollouts = rolloutManagement.findActiveRollouts();
if (rollouts.isEmpty()) {
return;
}
final String handlerId = createRolloutLockKey(tenantAware.getCurrentTenant());
final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Could not perform lock {}", lock);
}
return;
}
try {
LOGGER.trace("Trigger handling {} rollouts.", rollouts.size());
rollouts.forEach(rolloutId -> handleRolloutInNewTransaction(rolloutId, handlerId));
} finally {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Unlock lock {}", lock);
}
lock.unlock();
}
}
private static String createRolloutLockKey(final String tenant) {
return tenant + "-rollout";
}
private void handleRolloutInNewTransaction(final long rolloutId, final String handlerId) {
DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId, status -> {
rolloutManagement.get(rolloutId).ifPresentOrElse(
rollout -> runInUserContext(rollout, () -> rolloutExecutor.execute(rollout)),
() -> LOGGER.error("Could not retrieve rollout with id {}. Will not continue with execution.",
rolloutId));
return 0L;
});
}
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()),
tenantAware);
}
}

View File

@@ -16,9 +16,7 @@ import java.util.Collections;
import java.util.Comparator;
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.function.Function;
import java.util.stream.Collectors;
@@ -28,7 +26,6 @@ import javax.validation.ValidationException;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
@@ -52,10 +49,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -70,8 +65,6 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -82,14 +75,11 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.concurrent.ListenableFuture;
@@ -137,35 +127,26 @@ public class JpaRolloutManagement implements RolloutManagement {
private final TargetManagement targetManagement;
private final DistributionSetManagement distributionSetManagement;
private final VirtualPropertyReplacer virtualPropertyReplacer;
private final PlatformTransactionManager txManager;
private final TenantAware tenantAware;
private final LockRegistry lockRegistry;
private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private final RolloutExecutor rolloutExecutor;
private final EventPublisherHolder eventPublisherHolder;
private final Database database;
public JpaRolloutManagement(final TargetManagement targetManagement,
final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantAware tenantAware, final LockRegistry lockRegistry, final Database database,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database,
final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) {
final SystemSecurityContext systemSecurityContext) {
this.targetManagement = targetManagement;
this.distributionSetManagement = distributionSetManagement;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.txManager = txManager;
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
this.eventPublisherHolder = eventPublisherHolder;
this.database = database;
this.rolloutExecutor = rolloutExecutor;
}
@Override
@@ -435,43 +416,10 @@ public class JpaRolloutManagement implements RolloutManagement {
rolloutRepository.save(rollout);
}
@Override
// No transaction, will be created per handled rollout
@Transactional(propagation = Propagation.NEVER)
public void handleRollouts() {
final List<Long> rollouts = rolloutRepository.findByStatusIn(ACTIVE_ROLLOUTS);
if (rollouts.isEmpty()) {
return;
}
final String tenant = tenantAware.getCurrentTenant();
final String handlerId = createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) {
return;
}
try {
rollouts.forEach(rolloutId -> DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId,
status -> handleRollout(rolloutId)));
} finally {
lock.unlock();
}
}
public static String createRolloutLockKey(final String tenant) {
return tenant + "-rollout";
}
private long handleRollout(final long rolloutId) {
final JpaRollout rollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
runInUserContext(rollout, () -> rolloutExecutor.execute(rollout));
return 0;
}
@Override
@Transactional
@Retryable(include = {
@@ -517,6 +465,11 @@ public class JpaRolloutManagement implements RolloutManagement {
return findAll;
}
@Override
public List<Long> findActiveRollouts() {
return rolloutRepository.findByStatusIn(ACTIVE_ROLLOUTS);
}
@Override
public Optional<Rollout> getByName(final String rolloutName) {
return rolloutRepository.findByName(rolloutName);
@@ -649,11 +602,6 @@ public class JpaRolloutManagement implements RolloutManagement {
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class);
}
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()),
tenantAware);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set) {
@@ -778,7 +726,7 @@ public class JpaRolloutManagement implements RolloutManagement {
.filter(g -> RolloutGroupStatus.RUNNING.equals(g.getStatus())).findFirst()
.orElseThrow(() -> new RolloutIllegalStateException("No group is running"));
startNextRolloutGroupAction.eval(rollout, latestRunning, latestRunning.getSuccessActionExp());
startNextRolloutGroupAction.exec(rollout, latestRunning);
}
}

View File

@@ -36,6 +36,7 @@ import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutHandler;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
@@ -84,6 +85,9 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHol
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.jpa.rollout.RolloutScheduler;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupAction;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupEvaluationManager;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupSuccessCondition;
@@ -92,6 +96,7 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
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.RolloutGroup;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
@@ -119,7 +124,6 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -195,6 +199,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return new ThresholdRolloutGroupSuccessCondition(actionRepository);
}
@Bean
RolloutGroupEvaluationManager evaluationManager(
final List<RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupErrorCondition>> errorConditionEvaluators,
final List<RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupSuccessCondition>> successConditionEvaluators,
final List<RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction>> errorActionEvaluators,
final List<RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupSuccessAction>> successActionEvaluators) {
return new RolloutGroupEvaluationManager(errorConditionEvaluators, successConditionEvaluators,
errorActionEvaluators, successActionEvaluators);
}
@Bean
@ConditionalOnMissingBean
SystemManagementCacheKeyGenerator systemManagementCacheKeyGenerator() {
@@ -660,6 +674,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return new JpaSoftwareModuleTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository,
virtualPropertyReplacer, softwareModuleRepository, properties.getDatabase());
}
@Bean
@ConditionalOnMissingBean
RolloutHandler rolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement,
final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry,
final PlatformTransactionManager txManager) {
return new JpaRolloutHandler(tenantAware, rolloutManagement, rolloutExecutor, lockRegistry, txManager);
}
@Bean
@ConditionalOnMissingBean
@@ -670,25 +692,25 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement,
final DeploymentManagement deploymentManagement, final TargetManagement targetManagement,
final EventPublisherHolder eventPublisherHolder, final PlatformTransactionManager txManager,
final RolloutApprovalStrategy rolloutApprovalStrategy, final ApplicationContext context) {
final RolloutApprovalStrategy rolloutApprovalStrategy,
final RolloutGroupEvaluationManager evaluationManager, final RolloutManagement rolloutManagement) {
return new JpaRolloutExecutor(rolloutTargetGroupRepository, entityManager, rolloutRepository, actionRepository,
rolloutGroupRepository, afterCommit, tenantAware, rolloutGroupManagement, quotaManagement,
deploymentManagement, targetManagement, eventPublisherHolder, txManager, rolloutApprovalStrategy,
context);
evaluationManager, rolloutManagement);
}
@Bean
@ConditionalOnMissingBean
RolloutManagement rolloutManagement(final TargetManagement targetManagement,
final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantAware tenantAware, final LockRegistry lockRegistry, final JpaProperties properties,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties,
final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) {
final SystemSecurityContext systemSecurityContext) {
return new JpaRolloutManagement(targetManagement, distributionSetManagement, eventPublisherHolder,
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, properties.getDatabase(),
rolloutApprovalStrategy, tenantConfigurationManagement, systemSecurityContext, rolloutExecutor);
virtualPropertyReplacer, properties.getDatabase(), rolloutApprovalStrategy,
tenantConfigurationManagement, systemSecurityContext);
}
/**
@@ -913,7 +935,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*
* @param systemManagement
* to find all tenants
* @param rolloutManagement
* @param rolloutHandler
* to run the rollout handler
* @param systemSecurityContext
* to run as system
@@ -924,8 +946,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.rollout.scheduler", name = "enabled", matchIfMissing = true)
RolloutScheduler rolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext) {
return new RolloutScheduler(systemManagement, rolloutManagement, systemSecurityContext);
final RolloutHandler rolloutHandler, final SystemSecurityContext systemSecurityContext) {
return new RolloutScheduler(systemManagement, rolloutHandler, systemSecurityContext);
}
/**

View File

@@ -8,7 +8,7 @@
*/
package org.eclipse.hawkbit.repository.jpa.rollout;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutHandler;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.slf4j.Logger;
@@ -16,8 +16,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
/**
* Scheduler to schedule the {@link RolloutManagement#handleRollouts()}. The
* delay between the checks be be configured using the property from
* Scheduler to schedule the {@link RolloutHandler#handleAll()}. The
* delay between the checks be configured using the property from
* {#PROP_SCHEDULER_DELAY_PLACEHOLDER}.
*/
public class RolloutScheduler {
@@ -28,7 +28,7 @@ public class RolloutScheduler {
private final SystemManagement systemManagement;
private final RolloutManagement rolloutManagement;
private final RolloutHandler rolloutHandler;
private final SystemSecurityContext systemSecurityContext;
@@ -37,22 +37,22 @@ public class RolloutScheduler {
*
* @param systemManagement
* to find all tenants
* @param rolloutManagement
* @param rolloutHandler
* to run the rollout handler
* @param systemSecurityContext
* to run as system
*/
public RolloutScheduler(final SystemManagement systemManagement, final RolloutManagement rolloutManagement,
public RolloutScheduler(final SystemManagement systemManagement, final RolloutHandler rolloutHandler,
final SystemSecurityContext systemSecurityContext) {
this.systemManagement = systemManagement;
this.rolloutManagement = rolloutManagement;
this.rolloutHandler = rolloutHandler;
this.systemSecurityContext = systemSecurityContext;
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#handleRollouts()} in the
* tenants from the {@link SystemManagement#findTenants} and runs for each
* tenant the {@link RolloutHandler#handleAll()} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
@@ -63,13 +63,13 @@ public class RolloutScheduler {
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// execute a query without multi-tenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
systemManagement.forEachTenant(tenant -> rolloutManagement.handleRollouts());
systemManagement.forEachTenant(tenant -> rolloutHandler.handleAll());
return null;
});

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2023 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.rollout.condition;
/**
* Exception indicating that a specific evaluator is missing in the application
* context.
*/
public class EvaluatorNotConfiguredException extends RuntimeException {
private static final String MESSAGE_FORMAT = "Cannot find any configured evaluator for action/condition '%s'. Please ensure to configure one in the application context to make use of it.";
/**
* Constructor
*
* @param s
* the action/condition to evaluate
*/
public EvaluatorNotConfiguredException(final String s) {
super(String.format(MESSAGE_FORMAT, s));
}
}

View File

@@ -20,7 +20,7 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
* Error action evaluator which pauses the whole {@link Rollout} and sets the
* current {@link RolloutGroup} to error.
*/
public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator {
public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction> {
private final RolloutManagement rolloutManagement;
@@ -36,12 +36,12 @@ public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator {
}
@Override
public boolean verifyExpression(final String expression) {
return true;
public RolloutGroup.RolloutGroupErrorAction getAction() {
return RolloutGroup.RolloutGroupErrorAction.PAUSE;
}
@Override
public void eval(final Rollout rollout, final RolloutGroup rolloutG, final String expression) {
public void exec(final Rollout rollout, final RolloutGroup rolloutG) {
final JpaRolloutGroup rolloutGroup = (JpaRolloutGroup) rolloutG;
systemSecurityContext.runAsSystem(() -> {

View File

@@ -12,11 +12,11 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
*
* Interface to define a specific execution for an action
*/
public interface RolloutGroupActionEvaluator {
public interface RolloutGroupActionEvaluator<T> {
boolean verifyExpression(final String expression);
T getAction();
void eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression);
void exec(Rollout rollout, RolloutGroup rolloutGroup);
}

View File

@@ -12,20 +12,11 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
* Verifies {@link RolloutGroup#getErrorConditionExp()}.
* Interface for evaluate conditions define for a {@link RolloutGroup} based on a given expression
*/
@FunctionalInterface
public interface RolloutGroupConditionEvaluator {
public interface RolloutGroupConditionEvaluator<T> {
default boolean verifyExpression(final String expression) {
// percentage value between 0 and 100
try {
final Integer value = Integer.valueOf(expression);
return value >= 0 && value <= 100;
} catch (final NumberFormatException e) {
return false;
}
}
T getCondition();
boolean eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression);
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2023 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.rollout.condition;
import java.util.List;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manager class to collect all instances of
* {@link RolloutGroupConditionEvaluator} and
* {@link RolloutGroupActionEvaluator} for specific conditions and actions. The
* corresponding instance can be fetched by providing the action/condition.
*/
public class RolloutGroupEvaluationManager {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutGroupEvaluationManager.class);
private final List<RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupErrorCondition>> errorConditionEvaluators;
private final List<RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupSuccessCondition>> successConditionEvaluators;
private final List<RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction>> errorActionEvaluators;
private final List<RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupSuccessAction>> successActionEvaluators;
/**
* Constructor
*
* @param errorConditionEvaluators
* evaluators for instances of {@link RolloutGroupConditionEvaluator}
* handling the {@link RolloutGroup.RolloutGroupErrorCondition}
* @param successConditionEvaluators
* evaluators for instances of {@link RolloutGroupConditionEvaluator}
* handling the {@link RolloutGroup.RolloutGroupSuccessCondition}
* @param errorActionEvaluators
* evaluators for instances of {@link RolloutGroupActionEvaluator}
* handling the {@link RolloutGroup.RolloutGroupErrorAction}
* @param successActionEvaluators
* evaluators for instances of {@link RolloutGroupActionEvaluator}
* handling the {@link RolloutGroup.RolloutGroupSuccessAction}
*/
public RolloutGroupEvaluationManager(
final List<RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupErrorCondition>> errorConditionEvaluators,
final List<RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupSuccessCondition>> successConditionEvaluators,
final List<RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction>> errorActionEvaluators,
final List<RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupSuccessAction>> successActionEvaluators) {
this.errorConditionEvaluators = errorConditionEvaluators;
this.successConditionEvaluators = successConditionEvaluators;
this.errorActionEvaluators = errorActionEvaluators;
this.successActionEvaluators = successActionEvaluators;
}
public RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction> getErrorActionEvaluator(
final RolloutGroup.RolloutGroupErrorAction errorAction) {
return findFirstActionEvaluator(errorActionEvaluators, errorAction);
}
public RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupSuccessAction> getSuccessActionEvaluator(
final RolloutGroup.RolloutGroupSuccessAction successAction) {
return findFirstActionEvaluator(successActionEvaluators, successAction);
}
public RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupErrorCondition> getErrorConditionEvaluator(
final RolloutGroup.RolloutGroupErrorCondition errorCondition) {
return findFirstConditionEvaluator(errorConditionEvaluators, errorCondition);
}
public RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupSuccessCondition> getSuccessConditionEvaluator(
final RolloutGroup.RolloutGroupSuccessCondition successCondition) {
return findFirstConditionEvaluator(successConditionEvaluators, successCondition);
}
private static <T extends Enum<T>> RolloutGroupActionEvaluator<T> findFirstActionEvaluator(
final List<RolloutGroupActionEvaluator<T>> evaluators, final T action) {
return evaluators.stream().filter(evaluator -> evaluator.getAction() == action).findFirst().orElseThrow(() -> {
LOGGER.warn("Could not find suitable evaluator for the '{}' action.", action.name());
throw new EvaluatorNotConfiguredException(action.name());
});
}
private static <T extends Enum<T>> RolloutGroupConditionEvaluator<T> findFirstConditionEvaluator(
final List<RolloutGroupConditionEvaluator<T>> evaluators, final T condition) {
return evaluators.stream().filter(evaluator -> evaluator.getCondition() == condition).findFirst()
.orElseThrow(() -> {
LOGGER.warn("Could not find suitable evaluator for the '{}' condition.", condition.name());
throw new EvaluatorNotConfiguredException(condition.name());
});
}
}

View File

@@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory;
/**
* Success action which starts the next following {@link RolloutGroup}.
*/
public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActionEvaluator {
public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupSuccessAction> {
private static final Logger logger = LoggerFactory.getLogger(StartNextGroupRolloutGroupSuccessAction.class);
@@ -41,12 +41,12 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
}
@Override
public boolean verifyExpression(final String expression) {
return true;
public RolloutGroup.RolloutGroupSuccessAction getAction() {
return RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP;
}
@Override
public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) {
systemSecurityContext.runAsSystem(() -> {
startNextGroup(rollout, rolloutGroup);
return null;

View File

@@ -18,9 +18,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Evaluates if the {@link RolloutGroup#getErrorConditionExp()} is reached.
*/
public class ThresholdRolloutGroupErrorCondition implements RolloutGroupConditionEvaluator {
public class ThresholdRolloutGroupErrorCondition
implements RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupErrorCondition> {
private static final Logger LOGGER = LoggerFactory.getLogger(ThresholdRolloutGroupErrorCondition.class);
@@ -30,6 +31,11 @@ public class ThresholdRolloutGroupErrorCondition implements RolloutGroupConditio
this.actionRepository = actionRepository;
}
@Override
public RolloutGroup.RolloutGroupErrorCondition getCondition() {
return RolloutGroup.RolloutGroupErrorCondition.THRESHOLD;
}
@Override
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup((JpaRollout) rollout,

View File

@@ -16,10 +16,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Threshold to calculate if rollout group success condition is reached and the
* Threshold to calculate if the {@link RolloutGroup#getSuccessConditionExp()} is reached and the
* next rollout group can get started.
*/
public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupConditionEvaluator {
public class ThresholdRolloutGroupSuccessCondition
implements RolloutGroupConditionEvaluator<RolloutGroup.RolloutGroupSuccessCondition> {
private static final Logger LOGGER = LoggerFactory.getLogger(ThresholdRolloutGroupSuccessCondition.class);
private final ActionRepository actionRepository;
@@ -28,6 +29,11 @@ public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupCondit
this.actionRepository = actionRepository;
}
@Override
public RolloutGroup.RolloutGroupSuccessCondition getCondition() {
return RolloutGroup.RolloutGroupSuccessCondition.THRESHOLD;
}
@Override
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
@@ -44,7 +50,7 @@ public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupCondit
final long finished = this.actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
rolloutGroup.getId(), completeActionStatus);
try {
final Integer threshold = Integer.valueOf(expression);
final int threshold = Integer.parseInt(expression);
// calculate threshold
return ((float) finished / (float) totalGroup) >= ((float) threshold / 100F);