Introduce Pause Success Action (#2867)
* Introduce Pause Success Action Signed-off-by: vasilchev <vasil.ilchev@bosch.com> * Instead of overriding SuccessAction, trigger next group from resume rollout Fix Rollout Mgmt Resource to accept new Pause Action Signed-off-by: vasilchev <vasil.ilchev@bosch.com> * Review findings Signed-off-by: vasilchev <vasil.ilchev@bosch.com> * Remove unused import --------- Signed-off-by: vasilchev <vasil.ilchev@bosch.com>
This commit is contained in:
@@ -169,6 +169,7 @@ public interface RolloutGroup extends NamedEntity {
|
||||
* is hit.
|
||||
*/
|
||||
enum RolloutGroupSuccessAction {
|
||||
NEXTGROUP
|
||||
NEXTGROUP,
|
||||
PAUSE
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,8 @@ import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupErrorAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupSuccessAction;
|
||||
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;
|
||||
@@ -213,9 +214,9 @@ public class JpaRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
PauseRolloutGroupAction pauseRolloutGroupAction(
|
||||
PauseRolloutGroupErrorAction pauseRolloutGroupErrorAction(
|
||||
final RolloutManagement rolloutManagement, final RolloutGroupRepository rolloutGroupRepository) {
|
||||
return new PauseRolloutGroupAction(rolloutManagement, rolloutGroupRepository);
|
||||
return new PauseRolloutGroupErrorAction(rolloutManagement, rolloutGroupRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -225,6 +226,13 @@ public class JpaRepositoryConfiguration {
|
||||
return new StartNextGroupRolloutGroupSuccessAction(rolloutGroupRepository, deploymentManagement);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
PauseRolloutGroupSuccessAction pauseRolloutGroupSuccessAction(final RolloutManagement rolloutManagement,
|
||||
final RolloutGroupRepository rolloutGroupRepository) {
|
||||
return new PauseRolloutGroupSuccessAction(rolloutManagement, rolloutGroupRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ThresholdRolloutGroupErrorCondition thresholdRolloutGroupErrorCondition(final ActionRepository actionRepository) {
|
||||
|
||||
@@ -66,6 +66,7 @@ import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupEvaluationManager;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
|
||||
@@ -86,8 +87,10 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
import org.eclipse.hawkbit.repository.qfields.RolloutFields;
|
||||
import org.eclipse.hawkbit.utils.ObjectCopyUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -118,6 +121,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
RolloutStatus.CREATING, RolloutStatus.READY, RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.STARTING, RolloutStatus.RUNNING,
|
||||
RolloutStatus.PAUSED, RolloutStatus.APPROVAL_DENIED);
|
||||
|
||||
private static final Comparator<RolloutGroup> ROLLOUT_GROUP_DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
|
||||
|
||||
private RolloutGroupEvaluationManager rolloutGroupEvaluationManager;
|
||||
@Value("${hawkbit.repository.jpa.management.rollout.max.actions.per.transaction:5000}")
|
||||
private int maxActions;
|
||||
|
||||
@@ -164,6 +170,13 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private void setRolloutGroupEvaluationManager(
|
||||
final RolloutGroupEvaluationManager rolloutGroupEvaluationManager) {
|
||||
this.rolloutGroupEvaluationManager = rolloutGroupEvaluationManager;
|
||||
}
|
||||
|
||||
public static String createRolloutLockKey(final String tenant) {
|
||||
return tenant + "-rollout";
|
||||
}
|
||||
@@ -348,10 +361,34 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is " +
|
||||
rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
final List<RolloutGroup> allStartedGroups = rollout.getRolloutGroups().stream()
|
||||
.filter(g -> RolloutGroupStatus.SCHEDULED != g.getStatus()).toList();
|
||||
if (!allStartedGroups.isEmpty()) {
|
||||
final RolloutGroup lastStartedGroup = allStartedGroups.get(allStartedGroups.size() - 1);
|
||||
if (shouldStartNextGroupOnResume(rollout, lastStartedGroup)) {
|
||||
startNextRolloutGroupAction.exec(rollout, lastStartedGroup);
|
||||
}
|
||||
}
|
||||
rollout.setStatus(RolloutStatus.RUNNING);
|
||||
rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if on resume of a paused rollout the next group shall be started directly.
|
||||
* Cases where we need to manually start the next group:
|
||||
* - last running group is in error state and there is still some old group in running state, only running groups would be evaluated which would leave Rollout in running state but no trigger new group
|
||||
* - last running group has success action to PAUSE and the success condition is fulfilled
|
||||
* @param rollout
|
||||
* @param lastStartedGroup
|
||||
* @return true if next group shall be started directly on resume, false otherwise
|
||||
*/
|
||||
private boolean shouldStartNextGroupOnResume(final JpaRollout rollout, final RolloutGroup lastStartedGroup) {
|
||||
return lastStartedGroup.getStatus().equals(RolloutGroupStatus.ERROR) ||
|
||||
(lastStartedGroup.getSuccessAction() == RolloutGroup.RolloutGroupSuccessAction.PAUSE &&
|
||||
rolloutGroupEvaluationManager.getSuccessConditionEvaluator(lastStartedGroup.getSuccessCondition())
|
||||
.eval(rollout, lastStartedGroup, lastStartedGroup.getSuccessConditionExp()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
@@ -482,13 +519,15 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
throw new RolloutIllegalStateException("Rollout does not have any groups left to be triggered");
|
||||
}
|
||||
|
||||
final RolloutGroup latestRunning = groups.stream()
|
||||
.sorted(Comparator.comparingLong(RolloutGroup::getId).reversed())
|
||||
.filter(g -> RolloutGroupStatus.RUNNING.equals(g.getStatus()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RolloutIllegalStateException("No group is running"));
|
||||
|
||||
startNextRolloutGroupAction.exec(rollout, latestRunning);
|
||||
final List<JpaRolloutGroup> startedRolloutGroups = rollout.getRolloutGroups().stream()
|
||||
.filter(group -> group.getStatus() != RolloutGroupStatus.SCHEDULED)
|
||||
.sorted(ROLLOUT_GROUP_DESC_COMP)
|
||||
.map(JpaRolloutGroup.class::cast)
|
||||
.toList();
|
||||
if (startedRolloutGroups.isEmpty()) {
|
||||
throw new RolloutIllegalStateException("Cannot find any started rollout group to trigger next from");
|
||||
}
|
||||
startNextRolloutGroupAction.exec(rollout, startedRolloutGroups.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
@@ -12,38 +12,27 @@ package org.eclipse.hawkbit.repository.jpa.rollout.condition;
|
||||
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
|
||||
/**
|
||||
* Error action evaluator which pauses the whole {@link Rollout} and sets the
|
||||
* current {@link RolloutGroup} to error.
|
||||
* Abstract class for pausing a rollout group.
|
||||
*
|
||||
* @param <T> the type of the action
|
||||
*/
|
||||
public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction> {
|
||||
public abstract class AbstractPauseRolloutGroupAction<T extends Enum<T>> implements RolloutGroupActionEvaluator<T> {
|
||||
|
||||
private final RolloutManagement rolloutManagement;
|
||||
private final RolloutGroupRepository rolloutGroupRepository;
|
||||
protected final RolloutManagement rolloutManagement;
|
||||
protected final RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
public PauseRolloutGroupAction(final RolloutManagement rolloutManagement,
|
||||
protected AbstractPauseRolloutGroupAction(final RolloutManagement rolloutManagement,
|
||||
final RolloutGroupRepository rolloutGroupRepository) {
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.rolloutGroupRepository = rolloutGroupRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RolloutGroup.RolloutGroupErrorAction getAction() {
|
||||
return RolloutGroup.RolloutGroupErrorAction.PAUSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec(final Rollout rollout, final RolloutGroup rolloutG) {
|
||||
final JpaRolloutGroup rolloutGroup = (JpaRolloutGroup) rolloutG;
|
||||
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.ERROR);
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
// Refresh latest rollout state in order to avoid cases when
|
||||
// previous group have matched error condition and paused the rollout
|
||||
// and this one tries to pause the rollout too but throws an exception
|
||||
@@ -56,3 +45,4 @@ public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator<Roll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rollout.condition;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
|
||||
/**
|
||||
* Error action evaluator which pauses the whole {@link Rollout} and sets the
|
||||
* current {@link RolloutGroup} to error.
|
||||
*/
|
||||
public class PauseRolloutGroupErrorAction extends AbstractPauseRolloutGroupAction<RolloutGroup.RolloutGroupErrorAction> {
|
||||
|
||||
public PauseRolloutGroupErrorAction(final RolloutManagement rolloutManagement,
|
||||
final RolloutGroupRepository rolloutGroupRepository) {
|
||||
super(rolloutManagement, rolloutGroupRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RolloutGroup.RolloutGroupErrorAction getAction() {
|
||||
return RolloutGroup.RolloutGroupErrorAction.PAUSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec(final Rollout rollout, final RolloutGroup rolloutG) {
|
||||
// set rollout group status to error
|
||||
final JpaRolloutGroup rolloutGroup = (JpaRolloutGroup) rolloutG;
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.ERROR);
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
// pause the rollout
|
||||
super.exec(rollout, rolloutGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rollout.condition;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
|
||||
/**
|
||||
* Success action evaluator which pauses the whole {@link Rollout}.
|
||||
*/
|
||||
public class PauseRolloutGroupSuccessAction
|
||||
extends AbstractPauseRolloutGroupAction<RolloutGroup.RolloutGroupSuccessAction> {
|
||||
|
||||
public PauseRolloutGroupSuccessAction(final RolloutManagement rolloutManagement,
|
||||
final RolloutGroupRepository rolloutGroupRepository) {
|
||||
super(rolloutManagement, rolloutGroupRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RolloutGroup.RolloutGroupSuccessAction getAction() {
|
||||
return RolloutGroup.RolloutGroupSuccessAction.PAUSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
if (!rolloutGroupRepository
|
||||
.findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroup.RolloutGroupStatus.SCHEDULED).isEmpty()) {
|
||||
// if there are still scheduled child groups, do pause the rollout, otherwise just let it in running state
|
||||
super.exec(rollout, rolloutGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
*/
|
||||
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES =
|
||||
List.of(Status.ERROR, Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
|
||||
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
|
||||
private static final String TRANSACTION_ASSIGNING_TARGETS_TO_ROLLOUT_GROUP_FAILED = "Transaction assigning Targets to RolloutGroup failed";
|
||||
|
||||
private final ActionRepository actionRepository;
|
||||
@@ -353,11 +352,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
.filter(group -> group.getStatus() == RolloutGroupStatus.RUNNING)
|
||||
.map(JpaRolloutGroup.class::cast)
|
||||
.toList();
|
||||
|
||||
if (runningGroups.isEmpty()) {
|
||||
// no running rollouts, probably there was an error somewhere at the latest group. And the latest group has
|
||||
// been switched from running into error state. So we need to find the latest group which
|
||||
executeLatestRolloutGroup(rollout);
|
||||
asSystem(() -> rolloutManagement.triggerNextGroup(rollout.getId()));
|
||||
} else {
|
||||
log.debug("Rollout {} has {} running groups", rollout.getId(), runningGroups.size());
|
||||
executeRunningGroups(rollout, runningGroups, rollout.getRolloutGroups().get(rollout.getRolloutGroups().size() - 1));
|
||||
@@ -410,18 +408,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
return groupsActiveLeft == 0;
|
||||
}
|
||||
|
||||
private void executeLatestRolloutGroup(final JpaRollout rollout) {
|
||||
final List<JpaRolloutGroup> latestRolloutGroup = rollout.getRolloutGroups().stream()
|
||||
.filter(group -> group.getStatus() != RolloutGroupStatus.SCHEDULED)
|
||||
.sorted(DESC_COMP)
|
||||
.map(JpaRolloutGroup.class::cast)
|
||||
.toList();
|
||||
if (latestRolloutGroup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
||||
}
|
||||
|
||||
// fakes getTotalTargets count to match expected for the last dynamic group
|
||||
// so the evaluation to use total targets to properly
|
||||
private RolloutGroup evalProxy(final RolloutGroup group) {
|
||||
@@ -530,7 +516,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
.eval(rollout, evalProxy, rolloutGroup.getSuccessConditionExp());
|
||||
if (isFinished) {
|
||||
log.debug("Rollout group {} is finished, starting next group", rolloutGroup);
|
||||
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
||||
evaluationManager.getSuccessActionEvaluator(rolloutGroup.getSuccessAction()).exec(rollout, rolloutGroup);
|
||||
} else {
|
||||
log.debug("Rollout group {} is still running", rolloutGroup);
|
||||
}
|
||||
@@ -539,10 +525,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
evaluationManager.getSuccessActionEvaluator(rolloutGroup.getSuccessAction()).exec(rollout, rolloutGroup);
|
||||
}
|
||||
|
||||
private void startFirstRolloutGroup(final JpaRollout rollout) {
|
||||
log.debug("startFirstRolloutGroup called for rollout {}", rollout.getId());
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -52,7 +53,21 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void rolloutFlow() {
|
||||
void rolloutDefaultFlow() {
|
||||
rolloutFlow(RolloutGroupSuccessAction.NEXTGROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple rollout flow
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void rolloutPauseFlow() {
|
||||
rolloutFlow(RolloutGroupSuccessAction.PAUSE);
|
||||
}
|
||||
|
||||
|
||||
void rolloutFlow(final RolloutGroupSuccessAction successAction) throws Exception {
|
||||
final String rolloutName = "rollout-std";
|
||||
final int amountGroups = 5; // static only
|
||||
final String targetPrefix = "controller-rollout-std-";
|
||||
@@ -62,7 +77,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
final Rollout rollout = callAs(
|
||||
withUser("rolloutFlowUser", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
|
||||
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, false));
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", successAction,"30", false, false));
|
||||
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
|
||||
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
|
||||
|
||||
@@ -78,7 +93,9 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
assertGroup(groups.get(i), false, i == 0 ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.SCHEDULED, 3);
|
||||
}
|
||||
|
||||
executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(groups, rollout, amountGroups);
|
||||
executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(groups, rollout, amountGroups, successAction);
|
||||
|
||||
assertRollout(rollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
|
||||
|
||||
rolloutManagement.pauseRollout(rollout.getId());
|
||||
rolloutHandler.handleAll();
|
||||
@@ -92,11 +109,24 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple dynamic rollout flow
|
||||
* Verifies a simple dynamic rollout flow with default {@link RolloutGroupSuccessAction#NEXTGROUP} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void dynamicRolloutFlow() {
|
||||
void dynamicRolloutDefaultFlow() {
|
||||
dynamicRolloutFlow(RolloutGroupSuccessAction.NEXTGROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple dynamic rollout flow with {@link RolloutGroupSuccessAction#PAUSE} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void dynamicRolloutPauseFlow() {
|
||||
dynamicRolloutFlow(RolloutGroupSuccessAction.PAUSE);
|
||||
}
|
||||
|
||||
void dynamicRolloutFlow(final RolloutGroup.RolloutGroupSuccessAction successAction) throws Exception {
|
||||
final String rolloutName = "dynamic-rollout-std";
|
||||
final int amountGroups = 2; // static only
|
||||
final String targetPrefix = "controller-dynamic-rollout-std-";
|
||||
@@ -106,7 +136,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
final Rollout rollout = callAs(
|
||||
withUser("dynamicRolloutFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
|
||||
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, true));
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", successAction,"30", false, true));
|
||||
|
||||
// rollout is READY
|
||||
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
|
||||
@@ -132,7 +162,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
assertGroup(dynamic1, true, RolloutGroupStatus.SCHEDULED, 0);
|
||||
|
||||
executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(groups, rollout, amountGroups);
|
||||
executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(groups, rollout, amountGroups, successAction);
|
||||
|
||||
// partially fill the first dynamic (it is running and now create actions for 2 targets)
|
||||
rolloutHandler.handleAll();
|
||||
@@ -166,7 +196,13 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
.forEach(this::finishAction);
|
||||
executeWithoutOneTargetFromAGroup(dynamic1, rollout, 3);
|
||||
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
|
||||
|
||||
if (successAction == RolloutGroupSuccessAction.PAUSE) {
|
||||
// let success pause action run
|
||||
rolloutHandler.handleAll();
|
||||
// external resume rollout
|
||||
rolloutManagement.resumeRollout(rollout.getId());
|
||||
}
|
||||
// start next group
|
||||
rolloutHandler.handleAll();
|
||||
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 4);
|
||||
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
|
||||
@@ -208,12 +244,26 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(refresh(dynamic2).getStatus()).isEqualTo(RolloutGroupStatus.FINISHED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies a simple dynamic rollout flow with a dynamic group template
|
||||
* Verifies a simple dynamic rollout flow with a dynamic group template with default {@link RolloutGroupSuccessAction#NEXTGROUP} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void dynamicRolloutTemplateFlow() {
|
||||
void dynamicDefaultRolloutTemplateFlow() {
|
||||
dynamicRolloutTemplateFlow(RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple dynamic rollout flow with a dynamic group template with {@link RolloutGroupSuccessAction#PAUSE} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void dynamicPauseRolloutTemplateFlow() {
|
||||
dynamicRolloutTemplateFlow(RolloutGroup.RolloutGroupSuccessAction.PAUSE);
|
||||
}
|
||||
|
||||
void dynamicRolloutTemplateFlow(final RolloutGroup.RolloutGroupSuccessAction successAction) throws Exception {
|
||||
final String rolloutName = "dynamic-template-rollout-std";
|
||||
final int amountGroups = 3; // static only
|
||||
final String targetPrefix = "controller-template-dynamic-rollout-std-";
|
||||
@@ -224,7 +274,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
final Rollout rollout = callAs(
|
||||
withUser("dynamicRolloutTemplateFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
|
||||
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", successAction, "30",
|
||||
Action.ActionType.FORCED, 1000, false, true,
|
||||
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build()));
|
||||
|
||||
@@ -252,7 +302,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
assertGroup(dynamic1, true, RolloutGroupStatus.SCHEDULED, 0);
|
||||
|
||||
executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(groups, rollout, amountGroups);
|
||||
executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(groups, rollout, amountGroups, successAction);
|
||||
|
||||
// partially fill the first dynamic (it is running and now create actions for 4 targets)
|
||||
rolloutHandler.handleAll();
|
||||
@@ -287,6 +337,13 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
executeWithoutOneTargetFromAGroup(dynamic1, rollout, 6);
|
||||
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
|
||||
|
||||
if (successAction == RolloutGroupSuccessAction.PAUSE) {
|
||||
// let success pause action run
|
||||
rolloutHandler.handleAll();
|
||||
// external resume rollout
|
||||
rolloutManagement.resumeRollout(rollout.getId());
|
||||
}
|
||||
// start next group
|
||||
rolloutHandler.handleAll();
|
||||
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 8);
|
||||
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
|
||||
@@ -310,11 +367,24 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple pure (no static groups) dynamic rollout flow with a dynamic group template
|
||||
* Verifies a simple pure (no static groups) dynamic rollout flow with a dynamic group template with default {@link RolloutGroupSuccessAction#NEXTGROUP} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void dynamicRolloutPureFlow() {
|
||||
void dynamicDefaultRolloutPureFlow() {
|
||||
dynamicRolloutPureFlow(RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple pure (no static groups) dynamic rollout flow with a dynamic group template with {@link RolloutGroupSuccessAction#PAUSE} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void dynamicPauseRolloutPureFlow() {
|
||||
dynamicRolloutPureFlow(RolloutGroup.RolloutGroupSuccessAction.PAUSE);
|
||||
}
|
||||
|
||||
void dynamicRolloutPureFlow(final RolloutGroup.RolloutGroupSuccessAction successAction) throws Exception {
|
||||
final String rolloutName = "pure-dynamic-rollout-std";
|
||||
final String targetPrefix = "controller-pure-dynamic-rollout-std-";
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSetLocked("dsFor" + rolloutName);
|
||||
@@ -322,7 +392,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
final Rollout rollout = callAs(
|
||||
withUser("dynamicRolloutPureFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
|
||||
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, 0,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "60", successAction,"30",
|
||||
Action.ActionType.FORCED, 1000, false, true,
|
||||
RolloutManagement.DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build()));
|
||||
|
||||
@@ -373,6 +443,13 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
executeWithoutOneTargetFromAGroup(dynamic1, rollout, 6);
|
||||
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
|
||||
|
||||
if (successAction == RolloutGroupSuccessAction.PAUSE) {
|
||||
// let success pause action run
|
||||
rolloutHandler.handleAll();
|
||||
// external resume rollout
|
||||
rolloutManagement.resumeRollout(rollout.getId());
|
||||
}
|
||||
// start next group
|
||||
rolloutHandler.handleAll();
|
||||
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 8);
|
||||
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
|
||||
@@ -395,11 +472,24 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple rollout flow
|
||||
* Verifies a simple rollout flow with {@link RolloutGroupSuccessAction#NEXTGROUP} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void rollout0ThresholdFlow() {
|
||||
void rolloutDefault0ThresholdFlow() {
|
||||
rollout0ThresholdFlow(RolloutGroupSuccessAction.NEXTGROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a simple rollout flow with {@link RolloutGroupSuccessAction#PAUSE} success action
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void rolloutPause0ThresholdFlow() {
|
||||
rollout0ThresholdFlow(RolloutGroupSuccessAction.PAUSE);
|
||||
}
|
||||
|
||||
void rollout0ThresholdFlow(final RolloutGroup.RolloutGroupSuccessAction successAction) throws Exception {
|
||||
final String rolloutName = "rollout-std-0threshold";
|
||||
final int amountGroups = 5; // static only
|
||||
final String targetPrefix = "controller-rollout-std-0threshold-";
|
||||
@@ -409,7 +499,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
final Rollout rollout = callAs(
|
||||
withUser("rollout0ThresholdFlow", "READ_DISTRIBUTION_SET", "READ_TARGET", "READ_ROLLOUT", "CREATE_ROLLOUT"),
|
||||
() -> testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "0", "25", false, false));
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "0", successAction, "25", false, false));
|
||||
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
|
||||
rollout.getId(), new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id"))).getContent();
|
||||
|
||||
@@ -423,6 +513,12 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
for (int i = 0; i < amountGroups; i++) {
|
||||
assertGroup(groups.get(i), false, i < step ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.SCHEDULED, 3);
|
||||
}
|
||||
|
||||
// expect all groups without last to trigger PAUSE action
|
||||
if (step < amountGroups && successAction == RolloutGroupSuccessAction.PAUSE) {
|
||||
rolloutHandler.handleAll();
|
||||
rolloutManagement.resumeRollout(rollout.getId());
|
||||
}
|
||||
// starting the next group
|
||||
rolloutHandler.handleAll();
|
||||
}
|
||||
@@ -430,7 +526,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private void executeStaticWithoutOneTargetFromTheLastGroupAndHandleAll(
|
||||
final List<RolloutGroup> groups,
|
||||
final Rollout rollout, final int amountGroups) {
|
||||
final Rollout rollout, final int amountGroups, final RolloutGroupSuccessAction successAction) {
|
||||
// create dynamic group if needed
|
||||
rolloutHandler.handleAll();
|
||||
// execute groups (without on of the last)
|
||||
@@ -455,6 +551,10 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||
.forEach(this::finishAction);
|
||||
assertAndGetRunning(rollout, i + 1 == amountGroups ? 1 : 0);
|
||||
rolloutHandler.handleAll();
|
||||
if (i + 1 < groups.size() && successAction == RolloutGroupSuccessAction.PAUSE) {
|
||||
rolloutManagement.resumeRollout(rollout.getId());
|
||||
rolloutHandler.handleAll();
|
||||
}
|
||||
final RolloutGroupStatus expectedStatus =
|
||||
i + 1 == amountGroups ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.FINISHED;
|
||||
assertThat(refresh(groups.get(i)).getStatus())
|
||||
|
||||
@@ -117,7 +117,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 2);
|
||||
final Rollout dynamicRollout = testdataFactory.createRolloutByVariables("dynamic", "static rollout", amountGroups,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "0", "30", ActionType.FORCED, 1000, false, true);
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "0", RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, "30", ActionType.FORCED, 1000, false, true);
|
||||
rolloutManagement.start(dynamicRollout.getId());
|
||||
rolloutHandler.handleAll();
|
||||
assertRollout(dynamicRollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 2);
|
||||
@@ -152,7 +152,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
testdataFactory.createTargets(targetPrefix, amountGroups * 2, amountGroups);
|
||||
final Rollout staticRollout = testdataFactory.createRolloutByVariables("static", "static rollout", amountGroups,
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "0", "30", ActionType.FORCED, 0, false, false);
|
||||
"controllerid==" + targetPrefix + "*", distributionSet, "0", RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP,"30", ActionType.FORCED, 0, false, false);
|
||||
rolloutManagement.start(staticRollout.getId());
|
||||
rolloutHandler.handleAll();
|
||||
assertRollout(staticRollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.NamedEntity;
|
||||
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||
@@ -982,10 +983,10 @@ public class TestdataFactory {
|
||||
|
||||
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
||||
final String successCondition, final String errorCondition, final boolean confirmationRequired,
|
||||
final String successCondition, final RolloutGroup.RolloutGroupSuccessAction successAction, final String errorCondition, final boolean confirmationRequired,
|
||||
final boolean dynamic) {
|
||||
return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet,
|
||||
successCondition, errorCondition, Action.ActionType.FORCED, null, confirmationRequired, dynamic);
|
||||
successCondition, successAction, errorCondition, Action.ActionType.FORCED, null, confirmationRequired, dynamic);
|
||||
}
|
||||
|
||||
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||
@@ -993,7 +994,7 @@ public class TestdataFactory {
|
||||
final String successCondition, final String errorCondition, final Action.ActionType actionType,
|
||||
final Integer weight, final boolean confirmationRequired) {
|
||||
return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet,
|
||||
successCondition, errorCondition, actionType, weight, confirmationRequired, false);
|
||||
successCondition, RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, errorCondition, actionType, weight, confirmationRequired, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1014,19 +1015,21 @@ public class TestdataFactory {
|
||||
*/
|
||||
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
||||
final String successCondition, final String errorCondition, final Action.ActionType actionType,
|
||||
final String successCondition, final RolloutGroup.RolloutGroupSuccessAction successAction, final String errorCondition, final Action.ActionType actionType,
|
||||
final Integer weight, final boolean confirmationRequired, final boolean dynamic) {
|
||||
return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet,
|
||||
successCondition, errorCondition, actionType, weight, confirmationRequired, dynamic, null);
|
||||
successCondition, successAction, errorCondition, actionType, weight, confirmationRequired, dynamic, null);
|
||||
}
|
||||
|
||||
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
||||
final String successCondition, final String errorCondition, final Action.ActionType actionType,
|
||||
final String successCondition, final RolloutGroup.RolloutGroupSuccessAction successAction, final String errorCondition,
|
||||
final Action.ActionType actionType,
|
||||
final Integer weight, final boolean confirmationRequired, final boolean dynamic,
|
||||
final RolloutManagement.DynamicRolloutGroupTemplate dynamicRolloutGroupTemplate) {
|
||||
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
|
||||
.successAction(successAction, "")
|
||||
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
|
||||
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user