Involve all targets in dynamic rollouts (#1795)

* involve targets into dynamic rollouts eagerly - doesn't wait for dynamic group to become running in order to involve devices
* adds trottling for involving targes into dynamic groups
* small style refactoring

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com
This commit is contained in:
Avgustin Marinov
2024-08-01 15:36:29 +03:00
committed by GitHub
parent 2638de25d6
commit 6fd52d4b4a
5 changed files with 133 additions and 141 deletions

View File

@@ -72,4 +72,10 @@ public class RepositoryProperties {
private List<String> skipImplicitLockForTags = private List<String> skipImplicitLockForTags =
List.of("skip-implicit-lock", "skip_implicit_lock", "SKIP_IMPLICIT_LOCK", "SKIP-IMPLICIT-LOCK"); List.of("skip-implicit-lock", "skip_implicit_lock", "SKIP_IMPLICIT_LOCK", "SKIP-IMPLICIT-LOCK");
/**
* The minimum period (in milli-seconds) on which dynamic rollouts should make attempt to involve
* new targets
*/
private long dynamicRolloutsMinInvolvePeriodMS = 60_000;
} }

View File

@@ -13,9 +13,11 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@@ -24,6 +26,7 @@ import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy; import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.RolloutExecutor; import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutGroupManagement;
@@ -99,51 +102,55 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR, private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR,
Status.FINISHED, Status.CANCELED, Status.DOWNLOADED); Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
private final EntityManager entityManager;
private final RolloutRepository rolloutRepository;
private final ActionRepository actionRepository; private final ActionRepository actionRepository;
private final RolloutGroupRepository rolloutGroupRepository; private final RolloutGroupRepository rolloutGroupRepository;
private final AfterTransactionCommitExecutor afterCommit; private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
private final TenantAware tenantAware; private final RolloutRepository rolloutRepository;
private final RolloutGroupManagement rolloutGroupManagement;
private final QuotaManagement quotaManagement;
private final DeploymentManagement deploymentManagement;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
private final EventPublisherHolder eventPublisherHolder; private final DeploymentManagement deploymentManagement;
private final PlatformTransactionManager txManager; private final RolloutGroupManagement rolloutGroupManagement;
private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final RolloutGroupEvaluationManager evaluationManager;
private final RolloutManagement rolloutManagement; private final RolloutManagement rolloutManagement;
private final QuotaManagement quotaManagement;
/**
* Constructor private final RolloutGroupEvaluationManager evaluationManager;
*/ private final RolloutApprovalStrategy rolloutApprovalStrategy;
public JpaRolloutExecutor(final RolloutTargetGroupRepository rolloutTargetGroupRepository,
final EntityManager entityManager, final RolloutRepository rolloutRepository, private final EntityManager entityManager;
private final PlatformTransactionManager txManager;
private final AfterTransactionCommitExecutor afterCommit;
private final EventPublisherHolder eventPublisherHolder;
private final TenantAware tenantAware;
private final RepositoryProperties repositoryProperties;
public JpaRolloutExecutor(
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository, final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
final AfterTransactionCommitExecutor afterCommit, final TenantAware tenantAware, final RolloutTargetGroupRepository rolloutTargetGroupRepository,
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement, final RolloutRepository rolloutRepository, final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final EventPublisherHolder eventPublisherHolder, final PlatformTransactionManager txManager, final RolloutManagement rolloutManagement, final QuotaManagement quotaManagement,
final RolloutApprovalStrategy rolloutApprovalStrategy, final RolloutGroupEvaluationManager evaluationManager, final RolloutApprovalStrategy rolloutApprovalStrategy,
final RolloutGroupEvaluationManager evaluationManager, final RolloutManagement rolloutManagement) { final EntityManager entityManager, final PlatformTransactionManager txManager,
this.rolloutTargetGroupRepository = rolloutTargetGroupRepository; final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
this.entityManager = entityManager; final TenantAware tenantAware, final RepositoryProperties repositoryProperties) {
this.rolloutRepository = rolloutRepository;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
this.rolloutGroupRepository = rolloutGroupRepository; this.rolloutGroupRepository = rolloutGroupRepository;
this.afterCommit = afterCommit; this.rolloutTargetGroupRepository = rolloutTargetGroupRepository;
this.tenantAware = tenantAware; this.rolloutRepository = rolloutRepository;
this.rolloutGroupManagement = rolloutGroupManagement;
this.quotaManagement = quotaManagement;
this.deploymentManagement = deploymentManagement;
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.eventPublisherHolder = eventPublisherHolder; this.deploymentManagement = deploymentManagement;
this.txManager = txManager; this.rolloutGroupManagement = rolloutGroupManagement;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.evaluationManager = evaluationManager;
this.rolloutManagement = rolloutManagement; this.rolloutManagement = rolloutManagement;
this.quotaManagement = quotaManagement;
this.evaluationManager = evaluationManager;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.entityManager = entityManager;
this.txManager = txManager;
this.afterCommit = afterCommit;
this.eventPublisherHolder = eventPublisherHolder;
this.tenantAware = tenantAware;
this.repositoryProperties = repositoryProperties;
} }
@Override @Override
@@ -427,7 +434,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
// fakes getTotalTargets count to match expected for the last dynamic group // fakes getTotalTargets count to match expected for the last dynamic group
// so the evaluation to use total targets to properly // so the evaluation to use total targets to properly
private RolloutGroup evalProxy(final RolloutGroup group, final List<JpaRolloutGroup> rolloutGroups) { private RolloutGroup evalProxy(final RolloutGroup group) {
if (group.isDynamic()) { if (group.isDynamic()) {
final int expected = Math.max((int)group.getTargetPercentage(), 1); final int expected = Math.max((int)group.getTargetPercentage(), 1);
return (RolloutGroup) Proxy.newProxyInstance( return (RolloutGroup) Proxy.newProxyInstance(
@@ -457,7 +464,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
} }
final RolloutGroup evalProxy = rolloutGroup == rolloutGroups.get(rolloutGroups.size() - 1) ? final RolloutGroup evalProxy = rolloutGroup == rolloutGroups.get(rolloutGroups.size() - 1) ?
evalProxy(rolloutGroup, rolloutGroups) : rolloutGroup; evalProxy(rolloutGroup) : rolloutGroup;
// error state check, do we need to stop the whole // error state check, do we need to stop the whole
// rollout because of error? // rollout because of error?
final boolean isError = checkErrorState(rollout, evalProxy); final boolean isError = checkErrorState(rollout, evalProxy);
@@ -529,7 +536,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
} }
} }
private boolean checkSuccessCondition(final Rollout rollout, final RolloutGroup rolloutGroup, final RolloutGroup evalProxy, private void checkSuccessCondition(final Rollout rollout, final RolloutGroup rolloutGroup, final RolloutGroup evalProxy,
final RolloutGroupSuccessCondition successCondition) { final RolloutGroupSuccessCondition successCondition) {
log.trace("Checking finish condition {} on rolloutgroup {}", successCondition, rolloutGroup); log.trace("Checking finish condition {} on rolloutgroup {}", successCondition, rolloutGroup);
try { try {
@@ -541,11 +548,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
} else { } else {
log.debug("Rolloutgroup {} is still running", rolloutGroup); log.debug("Rolloutgroup {} is still running", rolloutGroup);
} }
return isFinished;
} catch (final EvaluatorNotConfiguredException e) { } catch (final EvaluatorNotConfiguredException e) {
log.error("Something bad happened when accessing the finish condition or success action bean {}", log.error("Something bad happened when accessing the finish condition or success action bean {}",
successCondition.name(), e); successCondition.name(), e);
return false;
} }
} }
@@ -675,32 +680,32 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}); });
} }
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
// return if group change is made // return if group change is made
private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) { private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) {
final AtomicLong lastFill = lastDynamicGroupFill.computeIfAbsent(rollout.getId(), id -> new AtomicLong(0));
final long now = System.currentTimeMillis();
if (now - lastFill.get() < repositoryProperties.getDynamicRolloutsMinInvolvePeriodMS()) {
// too early to make another dynamic involvement attempt
return false;
} else {
lastFill.set(now);
}
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.RUNNING); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.RUNNING);
final List<RolloutGroup> rolloutGroups = rollout.getRolloutGroups(); final List<RolloutGroup> rolloutGroups = rollout.getRolloutGroups();
final JpaRolloutGroup group = (JpaRolloutGroup)rolloutGroups.get(rolloutGroups.size() - 1); final JpaRolloutGroup group = (JpaRolloutGroup)rolloutGroups.get(rolloutGroups.size() - 1);
if (group.getStatus() == RolloutGroupStatus.FINISHED) {
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.RUNNING);
return true;
} else if (group.getStatus() != RolloutGroupStatus.RUNNING) {
return false;
}
// expected as last full group
final long expectedInGroup = Math.max((int)group.getTargetPercentage(), 1); final long expectedInGroup = Math.max((int)group.getTargetPercentage(), 1);
final long currentlyInGroup = group.getTotalTargets(); final long currentlyInGroup = group.getTotalTargets();
if (currentlyInGroup >= expectedInGroup) { if (currentlyInGroup >= expectedInGroup || group.getStatus() == RolloutGroupStatus.FINISHED) {
// the last one is filled. create new and start filling it // the last one is full. create new and start filling it on the next iteration
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.SCHEDULED); createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.SCHEDULED);
return true; return true;
} }
// there are more to be filled for that group // there are more to be filled for the last group do this until there are more matching
// do this until there are more matching
try { try {
long targetsLeftToAdd = expectedInGroup - currentlyInGroup; long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter( final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(
@@ -725,12 +730,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
if (newActions > 0) { if (newActions > 0) {
updateTotalTargetCount(group, group.getTotalTargets() + newActions); updateTotalTargetCount(group, group.getTotalTargets() + newActions);
if (targetsLeftToAdd == 0) {
// this is filled create a new one in scheduled state
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.SCHEDULED);
return true;
}
// TODO - try to return false and proceed with handleRunningRollout // TODO - try to return false and proceed with handleRunningRollout
// the problem is that OptimisticLockException is thrown in that case // the problem is that OptimisticLockException is thrown in that case
return true; return true;
@@ -800,7 +799,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final ActionType actionType = rollout.getActionType(); final ActionType actionType = rollout.getActionType();
final long forceTime = rollout.getForcedTime(); final long forceTime = rollout.getForcedTime();
final List<Action> newActions = createActions(targets.getContent(), distributionSet, actionType, forceTime, rollout, group); final List<Action> newActions = createActions(targets.getContent(), distributionSet, actionType, forceTime, rollout, group);
if (!newActions.isEmpty()) { if (!newActions.isEmpty() && group.getStatus() == RolloutGroupStatus.RUNNING) {
deploymentManagement.startScheduledActions(newActions); deploymentManagement.startScheduledActions(newActions);
} }
@@ -906,17 +905,14 @@ public class JpaRolloutExecutor implements RolloutExecutor {
} }
/** /**
* Enforces the quota defining the maximum number of {@link Action}s per * Enforces the quota defining the maximum number of {@link Action}s per {@link Target}.
* {@link Target}.
* *
* @param target * @param target the target
* The target * @param requested number of actions to check
* @param requested
* number of actions to check
*/ */
private void assertActionsPerTargetQuota(final Target target, final int requested) { private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget(); final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId); actionRepository::countByTargetId);
} }
} }

View File

@@ -739,19 +739,20 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
RolloutExecutor rolloutExecutor(final RolloutTargetGroupRepository rolloutTargetGroupRepository, RolloutExecutor rolloutExecutor(
final EntityManager entityManager, final RolloutRepository rolloutRepository,
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository, final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
final AfterTransactionCommitExecutor afterCommit, final TenantAware tenantAware, final RolloutTargetGroupRepository rolloutTargetGroupRepository,
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement, final RolloutRepository rolloutRepository, final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final EventPublisherHolder eventPublisherHolder, final PlatformTransactionManager txManager, final RolloutManagement rolloutManagement, final QuotaManagement quotaManagement,
final RolloutApprovalStrategy rolloutApprovalStrategy, final RolloutGroupEvaluationManager evaluationManager, final RolloutApprovalStrategy rolloutApprovalStrategy,
final RolloutGroupEvaluationManager evaluationManager, final RolloutManagement rolloutManagement) { final EntityManager entityManager, final PlatformTransactionManager txManager,
return new JpaRolloutExecutor(rolloutTargetGroupRepository, entityManager, rolloutRepository, actionRepository, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
rolloutGroupRepository, afterCommit, tenantAware, rolloutGroupManagement, quotaManagement, final TenantAware tenantAware, final RepositoryProperties repositoryProperties) {
deploymentManagement, targetManagement, eventPublisherHolder, txManager, rolloutApprovalStrategy, return new JpaRolloutExecutor(actionRepository, rolloutGroupRepository, rolloutTargetGroupRepository,
evaluationManager, rolloutManagement); rolloutRepository, targetManagement, deploymentManagement, rolloutGroupManagement, rolloutManagement,
quotaManagement, evaluationManager, rolloutApprovalStrategy, entityManager, txManager, afterCommit,
eventPublisherHolder, tenantAware, repositoryProperties);
} }
@Bean @Bean

View File

@@ -24,9 +24,11 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.context.TestPropertySource;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -38,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@Feature("Component Tests - Repository") @Feature("Component Tests - Repository")
@Story("Rollout Management (Flow)") @Story("Rollout Management (Flow)")
@TestPropertySource(properties = { "hawkbit.server.repository.dynamicRolloutsMinInvolvePeriodMS=-1" })
class RolloutManagementFlowTest extends AbstractJpaIntegrationTest { class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
@BeforeEach @BeforeEach
@@ -131,7 +134,8 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// fill first and create second // fill first and create second
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 2, 2); testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 2, 2);
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2 rolloutHandler.handleAll(); // fill first dynamic group
rolloutHandler.handleAll(); // and create a new dynamic2
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
groups = rolloutGroupManagement.findByRollout( groups = rolloutGroupManagement.findByRollout(
@@ -142,11 +146,11 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// create scheduled actions for the dynamic2 // create scheduled actions for the dynamic2
rolloutHandler.handleAll(); rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 4);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0); assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 1);
assertAndGetRunning(rollout, 4); // one from the last static group and 3 from the first dynamic assertAndGetRunning(rollout, 4); // one from the last static group and 3 from the first dynamic
assertScheduled(rollout, 0); assertScheduled(rollout, 1);
// executes last from static and dynamic1 without 1 target // executes last from static and dynamic1 without 1 target
assertAndGetRunning(rollout, 4)// one from the last static and 6 for the first dynamic assertAndGetRunning(rollout, 4)// one from the last static and 6 for the first dynamic
@@ -158,15 +162,12 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertAndGetRunning(rollout, 1); // remains on in the first dynamic assertAndGetRunning(rollout, 1); // remains on in the first dynamic
rolloutHandler.handleAll(); rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 4);
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3); assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0); // first dynamic threshold is reached, second is started
rolloutHandler.handleAll(); // add 1 action to now running second dynamic
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 4);
assertAndGetRunning(rollout, 2);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 1); assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 1);
assertAndGetRunning(rollout, 2);
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 1); testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 1);
rolloutManagement.pauseRollout(rollout.getId()); rolloutManagement.pauseRollout(rollout.getId());
@@ -184,6 +185,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// finish the second dynamic group // finish the second dynamic group
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 5, 1); testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 5, 1);
rolloutHandler.handleAll(); rolloutHandler.handleAll();
rolloutHandler.handleAll(); // create next
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 3, amountGroups * 3 + 6); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 3, amountGroups * 3 + 6);
assertAndGetRunning(rollout, 4); // one from the dynamic1 and 3 from the dynamic2 assertAndGetRunning(rollout, 4); // one from the dynamic1 and 3 from the dynamic2
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 3); // assign the target created when paused assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 3); // assign the target created when paused
@@ -249,7 +251,8 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// fill first (2) and create fill partially the second (+2 new) // fill first (2) and create fill partially the second (+2 new)
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 4); testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 4);
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2 rolloutHandler.handleAll(); // fill first dynamic group
rolloutHandler.handleAll(); // and create a new dynamic2
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout( groups = rolloutGroupManagement.findByRollout(
@@ -260,11 +263,11 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// create scheduled actions for the dynamic2 // create scheduled actions for the dynamic2
rolloutHandler.handleAll(); rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 8);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0); assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 2);
assertAndGetRunning(rollout, 7); // one from the last static group and 6 from the first dynamic assertAndGetRunning(rollout, 7); // one from the last static group and 6 from the first dynamic
assertScheduled(rollout, 0); assertScheduled(rollout, 2);
// executes last from static and dynamic1 without 1 target // executes last from static and dynamic1 without 1 target
assertAndGetRunning(rollout, 7)// one from the last static and 6 for the first dynamic assertAndGetRunning(rollout, 7)// one from the last static and 6 for the first dynamic
@@ -276,15 +279,12 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertAndGetRunning(rollout, 1); // remains on in the first dynamic assertAndGetRunning(rollout, 1); // remains on in the first dynamic
rolloutHandler.handleAll(); rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6); assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 8);
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3); assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0); // first dynamic threshold is reached, second is started
rolloutHandler.handleAll(); // add 2 action to now running second dynamic
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 8);
assertAndGetRunning(rollout, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2);
assertAndGetRunning(rollout, 3);
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 8, 2); testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 8, 2);
rolloutManagement.pauseRollout(rollout.getId()); rolloutManagement.pauseRollout(rollout.getId());
@@ -339,7 +339,8 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// fill first (2) and create fill partially the second (+2 new) // fill first (2) and create fill partially the second (+2 new)
testdataFactory.createTargets(targetPrefix, 4, 4); testdataFactory.createTargets(targetPrefix, 4, 4);
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2 rolloutHandler.handleAll(); // fill first dynamic group
rolloutHandler.handleAll(); // and create a new dynamic2
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6); assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout( groups = rolloutGroupManagement.findByRollout(
@@ -350,25 +351,22 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
// create scheduled actions for the dynamic2 // create scheduled actions for the dynamic2
rolloutHandler.handleAll(); rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6); assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 8);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0); assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 2);
assertAndGetRunning(rollout, 6); // 6 from the first dynamic assertAndGetRunning(rollout, 6); // 6 from the first dynamic
assertScheduled(rollout, 0); assertScheduled(rollout, 2);
// executes dynamic1 without 1 target // executes dynamic1 without 1 target
executeWithoutOneTargetFromAGroup(dynamic1, rollout, 6); executeWithoutOneTargetFromAGroup(dynamic1, rollout, 6);
assertAndGetRunning(rollout, 1); // remains on in the first dynamic assertAndGetRunning(rollout, 1); // remains on in the first dynamic
rolloutHandler.handleAll(); rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6); assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 8);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6); assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0); // first dynamic threshold is reached, second is started
rolloutHandler.handleAll(); // add 2 action to now running second dynamic
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 8);
assertAndGetRunning(rollout, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2);
assertAndGetRunning(rollout, 3);
testdataFactory.createTargets(targetPrefix, 8, 2); testdataFactory.createTargets(targetPrefix, 8, 2);
rolloutManagement.pauseRollout(rollout.getId()); rolloutManagement.pauseRollout(rollout.getId());
@@ -387,6 +385,8 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
private void executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll( private void executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(
final List<RolloutGroup> groups, final List<RolloutGroup> groups,
final Rollout rollout, final int amountGroups) { final Rollout rollout, final int amountGroups) {
// create dynamic group if needed
rolloutHandler.handleAll();
// execute groups (without on of the last) // execute groups (without on of the last)
assertThat(refresh(groups.get(0)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); assertThat(refresh(groups.get(0)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
for (int i = 0; i < amountGroups; i++) { for (int i = 0; i < amountGroups; i++) {

View File

@@ -52,7 +52,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCond
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder; import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions; import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy; import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch; import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
@@ -70,6 +69,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultMatcher;
import io.qameta.allure.Description; import io.qameta.allure.Description;
@@ -82,6 +82,7 @@ import io.qameta.allure.Story;
*/ */
@Feature("Component Tests - Management API") @Feature("Component Tests - Management API")
@Story("Rollout Resource") @Story("Rollout Resource")
@TestPropertySource(locations = "classpath:/mgmt-test.properties", properties = { "hawkbit.server.repository.dynamicRolloutsMinInvolvePeriodMS=-1" })
class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
private static final String HREF_ROLLOUT_PREFIX = "http://localhost/rest/v1/rollouts/"; private static final String HREF_ROLLOUT_PREFIX = "http://localhost/rest/v1/rollouts/";
@@ -256,25 +257,29 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(jsonPath("content[0].totalGroups", equalTo(2))); .andExpect(jsonPath("content[0].totalGroups", equalTo(2)));
final int amountOfDynamicTargets = 2; final int amountOfDynamicTargets = 2;
List<Target> additionalTargets = testdataFactory.createTargets(amountOfDynamicTargets, "rollout-dynamic-addition-", "rollout"); testdataFactory.createTargets(amountOfDynamicTargets, "rollout-dynamic-addition-", "rollout");
rolloutHandler.handleAll(); rolloutHandler.handleAll();
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent(); final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent();
groups.forEach(group -> { groups.forEach(group -> {
if (!group.getName().contains("-dynamic")) { if (!group.isDynamic()) {
rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, group.getId()).forEach(target -> deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).forEach(action -> { rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, group.getId())
.forEach(target -> deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE)
.forEach(action -> {
deploymentManagement.cancelAction(action.getId());
awaitActionStatus(action.getId(), Status.CANCELING);
deploymentManagement.cancelAction(action.getId()); deploymentManagement.forceQuitAction(action.getId());
awaitActionStatus(action.getId(), Status.CANCELING); awaitActionStatus(action.getId(), Status.CANCELED);
}));
deploymentManagement.forceQuitAction(action.getId());
awaitActionStatus(action.getId(), Status.CANCELED);
}));
} }
}); });
awaitPendingDeviceInRollout(additionalTargets.get(0).getControllerId()); // process as much as needed to start first dynamic
for (int i = 0; i < 5; i++) {
rolloutHandler.handleAll();
}
mvc.perform(get("/rest/v1/rollouts?representation=full").accept(MediaType.APPLICATION_JSON)) mvc.perform(get("/rest/v1/rollouts?representation=full").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -285,16 +290,16 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(jsonPath("content[0].status", equalTo("running"))) .andExpect(jsonPath("content[0].status", equalTo("running")))
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==rollout*"))) .andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==rollout*")))
.andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue()))) .andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue())))
.andExpect(jsonPath("content[0].totalTargets", equalTo(11))) .andExpect(jsonPath("content[0].totalTargets", equalTo(amountTargets + amountOfDynamicTargets)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus").exists()) .andExpect(jsonPath("content[0].totalTargetsPerStatus").exists())
.andExpect(jsonPath("content[0].totalTargetsPerStatus.running", equalTo(1))) .andExpect(jsonPath("content[0].totalTargetsPerStatus.running", equalTo(1)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.notstarted", equalTo(0))) .andExpect(jsonPath("content[0].totalTargetsPerStatus.notstarted", equalTo(0)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.scheduled", equalTo(0))) .andExpect(jsonPath("content[0].totalTargetsPerStatus.scheduled", equalTo(1)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.cancelled", equalTo(10))) .andExpect(jsonPath("content[0].totalTargetsPerStatus.cancelled", equalTo(10)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.finished", equalTo(0))) .andExpect(jsonPath("content[0].totalTargetsPerStatus.finished", equalTo(0)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.error", equalTo(0))) .andExpect(jsonPath("content[0].totalTargetsPerStatus.error", equalTo(0)))
.andExpect(jsonPath("content[0].deleted", equalTo(false))) .andExpect(jsonPath("content[0].deleted", equalTo(false)))
.andExpect(jsonPath("content[0].totalGroups", equalTo(3))) .andExpect(jsonPath("content[0].totalGroups", equalTo(4)))
.andExpect(jsonPath("content[0]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX))); .andExpect(jsonPath("content[0]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX)));
} }
@@ -1323,22 +1328,6 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
.getStatus().equals(status)); .getStatus().equals(status));
} }
private void awaitPendingDeviceInRollout(final String controllerId) {
Awaitility.await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofMillis(100)).with()
.until(() -> {
Optional<Target> maybeTarget = SecurityContextSwitch.runAsPrivileged(() -> {
rolloutHandler.handleAll();
List<Target> targets = targetManagement.findByRsql(PAGE, "controllerId==" + controllerId).getContent();
if (targets.size() == 1) {
return Optional.of(targets.get(0));
} else {
return Optional.empty();
}
});
return maybeTarget.isPresent() && maybeTarget.get().getUpdateStatus().equals(TargetUpdateStatus.PENDING);
});
}
@Test @Test
@Description("Deletion of a rollout") @Description("Deletion of a rollout")
void deleteRollout() throws Exception { void deleteRollout() throws Exception {