Add support for dynamic rollout group template (#1752)

1. Add support in REST and Mgmt API for dynamic group template
2. If present - groups follows the pattern of this template, otherwise - the last static group
3. This allows to create pure dynamic rollout with 0 static groups - auto assignment equivalent with groups

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-06-26 08:31:01 +03:00
committed by GitHub
parent 40f99962d2
commit 8b3434fc17
15 changed files with 486 additions and 111 deletions

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.management.JpaRolloutManagement;
@@ -221,7 +222,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
// When all groups are ready the rollout status can be changed to be
// ready, too.
if (readyGroups == rolloutGroups.size()) {
if (rollout.isDynamic()) {
if (rollout.isDynamic() && !rolloutGroups.get(rolloutGroups.size() - 1).isDynamic()) {
// add first dynamic group one by using the last as a parent and as a pattern
createDynamicGroup(rollout, rolloutGroups.get(rolloutGroups.size() - 1), rolloutGroups.size(), RolloutGroupStatus.READY);
}
@@ -395,9 +396,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}
private void sendRolloutGroupDeletedEvents(final JpaRollout rollout) {
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId)
.collect(Collectors.toList());
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId).toList();
afterCommit.afterCommit(() -> groupIds.forEach(rolloutGroupId -> eventPublisherHolder.getEventPublisher()
.publishEvent(new RolloutGroupDeletedEvent(tenantAware.getCurrentTenant(), rolloutGroupId,
JpaRolloutGroup.class, eventPublisherHolder.getApplicationId()))));
@@ -425,24 +424,11 @@ public class JpaRolloutExecutor implements RolloutExecutor {
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
}
private static final int DEFAULT_DYNAMIC_GROUP_EXPECTED = 100;
private static int expectedDynamicGroupSize(final List<? extends RolloutGroup> rolloutGroups) {
int expected = 0;
// gets the size of last static group (it is a pattern for dynamic groups)
for (final RolloutGroup rolloutGroup : rolloutGroups) {
if (rolloutGroup.isDynamic()) {
break;
}
expected = rolloutGroup.getTotalTargets();
}
return expected <= 0 ? DEFAULT_DYNAMIC_GROUP_EXPECTED /* default if the last static group has been empty */ : expected;
}
// 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, final List<JpaRolloutGroup> rolloutGroups) {
if (group.isDynamic()) {
final int expected = expectedDynamicGroupSize(rolloutGroups);
final int expected = Math.max((int)group.getTargetPercentage(), 1);
return (RolloutGroup) Proxy.newProxyInstance(
RolloutGroup.class.getClassLoader(),
new Class<?>[] {RolloutGroup.class},
@@ -702,8 +688,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return false;
}
// expected as last full group - last static or previously filled in dynamic group
final long expectedInGroup = expectedDynamicGroupSize(rolloutGroups);
// expected as last full group
final long expectedInGroup = Math.max((int)group.getTargetPercentage(), 1);
final long currentlyInGroup = group.getTotalTargets();
if (currentlyInGroup >= expectedInGroup) {
@@ -739,7 +725,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
updateTotalTargetCount(group, group.getTotalTargets() + newActions);
if (targetsLeftToAdd == 0) {
// this is filled create a new one in sheduled state
// this is filled create a new one in scheduled state
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.SCHEDULED);
return true;
}
@@ -755,19 +741,32 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}
private void createDynamicGroup(final JpaRollout rollout, final RolloutGroup lastGroup, final int groupCount, final RolloutGroupStatus status) {
try {
RolloutHelper.verifyRolloutGroupParameter(groupCount + 1, quotaManagement);
} catch (final AssignmentQuotaExceededException e) {
log.warn("Quota exceeded for dynamic rollout group creation: {}. Stop it", e.getMessage());
if (isRolloutComplete(rollout)) {
rollout.setStatus(RolloutStatus.STOPPED);
rolloutRepository.save(rollout);
}
return;
}
final JpaRolloutGroup group = new JpaRolloutGroup();
final String nameAndDesc = "group-" + (groupCount + 1) + "-dynamic";
group.setDynamic(true);
final String lastGroupWithoutSuffix = "group-" + groupCount;
final String suffix = lastGroup.getName().startsWith(lastGroupWithoutSuffix) ? lastGroup.getName().substring(lastGroupWithoutSuffix.length()) : "";
final String nameAndDesc = "group-" + (groupCount + 1) + suffix;
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
group.setRollout(rollout);
group.setParent(lastGroup);
group.setDynamic(true);
// no need to be filled with targets, directly in ready (if first on create - it will be scheduled on start)
// or scheduled state (for next dynamic groups)
group.setStatus(status);
group.setConfirmationRequired(lastGroup.isConfirmationRequired());
group.setTargetPercentage(lastGroup.getTargetPercentage());
// for dynamic groups the target count is kept in target percentage
group.setTargetPercentage(lastGroup.isDynamic() ? lastGroup.getTargetPercentage() : lastGroup.getTotalTargets());
group.setTargetFilterQuery(lastGroup.getTargetFilterQuery());
addSuccessAndErrorConditionsAndActions(group, lastGroup.getSuccessCondition(),

View File

@@ -22,8 +22,10 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -36,6 +38,7 @@ import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.builder.DynamicRolloutGroupTemplate;
import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
@@ -181,15 +184,37 @@ public class JpaRolloutManagement implements RolloutManagement {
return rolloutRepository.findById(rolloutId).map(Rollout.class::cast);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout create(@NotNull @Valid RolloutCreate create, int amountGroup, boolean confirmationRequired,
@NotNull RolloutGroupConditions conditions) {
return create(create, amountGroup, confirmationRequired, conditions, null);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout create(final RolloutCreate rollout, final int amountGroup, final boolean confirmationRequired,
final RolloutGroupConditions conditions) {
RolloutHelper.verifyRolloutGroupParameter(amountGroup, quotaManagement);
final JpaRollout savedRollout = createRollout((JpaRollout) rollout.build());
return createRolloutGroups(amountGroup, conditions, savedRollout, confirmationRequired);
final RolloutGroupConditions conditions, final DynamicRolloutGroupTemplate dynamicRolloutGroupTemplate) {
if (amountGroup == 0) {
if (dynamicRolloutGroupTemplate == null) {
throw new ValidationException(
"When amount of groups is 0, the rollouts shall be dynamic and a dynamic group template must be provided");
}
} else {
RolloutHelper.verifyRolloutGroupParameter(amountGroup, quotaManagement);
}
final JpaRollout rolloutRequest = (JpaRollout) rollout.build();
if (dynamicRolloutGroupTemplate != null && !rolloutRequest.isDynamic()) {
throw new ValidationException("Dynamic group template is only allowed for dynamic rollouts");
}
final JpaRollout savedRollout = createRollout(rolloutRequest, amountGroup == 0);
return createRolloutGroups(amountGroup, conditions, savedRollout, confirmationRequired, dynamicRolloutGroupTemplate);
}
@Override
@@ -199,29 +224,35 @@ public class JpaRolloutManagement implements RolloutManagement {
public Rollout create(final RolloutCreate rollout, final List<RolloutGroupCreate> groups,
final RolloutGroupConditions conditions) {
RolloutHelper.verifyRolloutGroupParameter(groups.size(), quotaManagement);
final JpaRollout savedRollout = createRollout((JpaRollout) rollout.build());
final JpaRollout rolloutRequest = (JpaRollout) rollout.build();
final JpaRollout savedRollout = createRollout(rolloutRequest, false);
return createRolloutGroups(groups, conditions, savedRollout);
}
private JpaRollout createRollout(final JpaRollout rollout) {
private JpaRollout createRollout(final JpaRollout rollout, final boolean pureDynamic) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final JpaDistributionSet distributionSet = (JpaDistributionSet) rollout.getDistributionSet();
final long totalTargets;
final String errMsg;
if (RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
totalTargets = targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()),
distributionSet.getType().getId());
errMsg = "No failed targets in Rollout";
} else {
totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(),
distributionSet.getType().getId());
errMsg = "Rollout does not match any existing targets";
if (pureDynamic) {
rollout.setTotalTargets(0);
} else {
final long totalTargets;
final String errMsg;
if (RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
totalTargets = targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()),
distributionSet.getType().getId());
errMsg = "No failed targets in Rollout";
} else {
totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(),
distributionSet.getType().getId());
errMsg = "Rollout does not match any existing targets";
}
if (totalTargets == 0) {
throw new ValidationException(errMsg);
}
rollout.setTotalTargets(totalTargets);
}
if (totalTargets == 0) {
throw new ValidationException(errMsg);
}
rollout.setTotalTargets(totalTargets);
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId());
@@ -235,45 +266,71 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
final JpaRollout rollout, final boolean isConfirmationRequired) {
final JpaRollout rollout, final boolean isConfirmationRequired, final DynamicRolloutGroupTemplate dynamicRolloutGroupTemplate) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
RolloutHelper.verifyRolloutGroupConditions(conditions);
final JpaRollout savedRollout = rollout;
// we can enforce the 'max targets per group' quota right here because
// we want to distribute the targets equally to the different groups
assertTargetsPerRolloutGroupQuota(rollout.getTotalTargets() / amountOfGroups);
RolloutGroup lastSavedGroup = null;
for (int i = 0; i < amountOfGroups; i++) {
final String nameAndDesc = "group-" + (i + 1);
if (amountOfGroups == 0) {
if (dynamicRolloutGroupTemplate == null) {
throw new ConstraintDeclarationException(
"At least one static rollout group must be defined for a static rollout");
}
} else {
// we can enforce the 'max targets per group' quota right here because
// we want to distribute the targets equally to the different groups
assertTargetsPerRolloutGroupQuota(rollout.getTotalTargets() / amountOfGroups);
for (int i = 0; i < amountOfGroups; i++) {
final String nameAndDesc = "group-" + (i + 1);
final JpaRolloutGroup group = new JpaRolloutGroup();
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
group.setRollout(rollout);
group.setParent(lastSavedGroup);
group.setStatus(RolloutGroupStatus.CREATING);
group.setConfirmationRequired(isConfirmationRequired);
addSuccessAndErrorConditionsAndActions(group, conditions);
// total percent of the all devices. Before, it was relative percent -
// the percent of the "rest" of the devices. Thus, if you have
// first a group 10% (the rest is 90%) and the second group is 50%
// then the percent would be 50% of 90% - 45%.
// This is very unintuitive and is switched in order to be interpreted easier.
// the "new style" (vs "old style") rollouts could be detected by
// JpaRollout#isNewStyleTargetPercent (which uses that old style rollouts
// have null as dynamic
group.setTargetPercentage(100.0F / amountOfGroups);
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
}
}
if (dynamicRolloutGroupTemplate != null && rollout.isDynamic()) { // if not null then it is a dynamic rollout (already validated), but for sure
// create first template rollout group
final String nameAndDesc = "group-" + (amountOfGroups + 1) + dynamicRolloutGroupTemplate.getNameSuffix();
final JpaRolloutGroup group = new JpaRolloutGroup();
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
group.setRollout(savedRollout);
group.setRollout(rollout);
group.setParent(lastSavedGroup);
group.setStatus(RolloutGroupStatus.CREATING);
group.setDynamic(true);
group.setStatus(RolloutGroupStatus.READY);
group.setConfirmationRequired(isConfirmationRequired);
addSuccessAndErrorConditionsAndActions(group, conditions);
// total percent of the all devices. Before, it was relative percent -
// the percent of the "rest" of the devices. Thus, if you have
// first a group 10% (the rest is 90%) and the second group is 50%
// then the percent would be 50% of 90% - 45%.
// This is very unintuitive and is switched in order to be interpreted easier.
// the "new style" (vs "old style") rollouts could be detected by
// JpaRollout#isNewStyleTargetPercent (which uses that old style rollouts
// have null as dynamic
group.setTargetPercentage(100.0F / amountOfGroups);
// for dynamic groups the target count is kept in target percentage
group.setTargetPercentage(dynamicRolloutGroupTemplate.getTargetCount());
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
}
savedRollout.setRolloutGroupsCreated(amountOfGroups);
return rolloutRepository.save(savedRollout);
rollout.setRolloutGroupsCreated(lastSavedGroup.isDynamic() ? amountOfGroups + 1 : amountOfGroups);
return rolloutRepository.save(rollout);
}
private Rollout createRolloutGroups(final List<RolloutGroupCreate> groupList,

View File

@@ -174,28 +174,28 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
protected void assertRollout(final Rollout rollout, final boolean dynamic, final Rollout.RolloutStatus status, final int groupCreated, final long totalTargets) {
final Rollout refreshed = refresh(rollout);
assertThat(refreshed.isDynamic()).isEqualTo(dynamic);
assertThat(refreshed.getStatus()).isEqualTo(status);
assertThat(refreshed.getRolloutGroupsCreated()).isEqualTo(groupCreated);
assertThat(refreshed.getTotalTargets()).isEqualTo(totalTargets);
assertThat(refreshed.isDynamic()).as("Is dynamic").isEqualTo(dynamic);
assertThat(refreshed.getStatus()).as("Status").isEqualTo(status);
assertThat(refreshed.getRolloutGroupsCreated()).as("Groups created").isEqualTo(groupCreated);
assertThat(refreshed.getTotalTargets()).as("Total targets").isEqualTo(totalTargets);
}
protected void assertGroup(final RolloutGroup group, final boolean dynamic, final RolloutGroup.RolloutGroupStatus status, final long totalTargets) {
final RolloutGroup refreshed = refresh(group);
assertThat(refreshed.isDynamic()).isEqualTo(dynamic);
assertThat(refreshed.getStatus()).isEqualTo(status);
assertThat(refreshed.getTotalTargets()).isEqualTo(totalTargets);
assertThat(refreshed.isDynamic()).as("Is dynamic").isEqualTo(dynamic);
assertThat(refreshed.getStatus()).as("Status").isEqualTo(status);
assertThat(refreshed.getTotalTargets()).as("Total targets").isEqualTo(totalTargets);
}
protected Page<JpaAction> assertAndGetRunning(final Rollout rollout, final int count) {
final Page<JpaAction> running = actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Action.Status.RUNNING);
assertThat(running.getTotalElements()).isEqualTo(count);
assertThat(running.getTotalElements()).as("Action count").isEqualTo(count);
return running;
}
protected void assertScheduled(final Rollout rollout, final int count) {
final Page<JpaAction> running = actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Action.Status.SCHEDULED);
assertThat(running.getTotalElements()).isEqualTo(count);
assertThat(running.getTotalElements()).as("Action count").isEqualTo(count);
}
protected void finishAction(final Action action) {

View File

@@ -13,7 +13,9 @@ import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.builder.DynamicRolloutGroupTemplate;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
@@ -85,7 +87,7 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
@Description("Verifies a simple dynamic rollout flow")
void dynamicRolloutFlow() {
final String rolloutName = "dynamic-rollout-std";
final int amountGroups = 5; // static only
final int amountGroups = 3; // static only
final String targetPrefix = "controller-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
@@ -177,6 +179,193 @@ class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); // assign the target created when paused
}
@Test
@Description("Verifies a simple dynamic rollout flow with a dynamic group template")
void dynamicRolloutTemplateFlow() {
final String rolloutName = "dynamic-template-rollout-std";
final int amountGroups = 3; // static only
final String targetPrefix = "controller-template-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
// create rollout with amountGroups static groups * 3 targets and dynamic group template with 6 targets
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
Action.ActionType.FORCED, 1000, false, true,
DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build());
// rollout is READY, amountGroups + 1 (dynamic) rollout groups and amountGroups * 3 targets in static groups
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
rollout.getId()).getContent();
final RolloutGroup dynamic1 = groups.get(amountGroups);
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic
for (int i = 0; i < amountGroups; i++) {
assertGroup(groups.get(i), false, RolloutGroupStatus.READY, 3);
}
assertGroup(dynamic1, true, RolloutGroupStatus.READY, 0);
// add 4 targets for the first dynamic group, fill partially
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 4);
// start rollout
rolloutManagement.start(rollout.getId());
// handleStartingRollout (no handleRunning called yet)
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 3);
for (int i = 0; i < amountGroups; i++) {
assertGroup(groups.get(i), false, i == 0 ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.SCHEDULED, 3);
}
assertGroup(dynamic1, true, RolloutGroupStatus.SCHEDULED, 0);
executeStaticWithoutOneTargetFromTheLastGroup(groups, rollout, amountGroups);
// partially fill the first dynamic (it is running and now create actions for 4 targets)
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 3 + 4);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 4);
// fill first (2) and create fill partially the second (+2 new)
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 4);
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
rollout.getId()).getContent();
final RolloutGroup dynamic2 = groups.get(amountGroups + 1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
// create scheduled actions for the dynamic2
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
assertAndGetRunning(rollout, 7); // one from the last static group and 6 from the first dynamic
assertScheduled(rollout, 0);
// executes last from static and dynamic1 without 1 target
assertAndGetRunning(rollout, 7)// one from the last static and 6 from the first dynamic
.stream()
// remove the last assigned to dynamic1 - it could be amountGroups * 3 + 2 or bigger by id
.filter(action -> Integer.parseInt(action.getTarget().getControllerId().substring(targetPrefix.length())) < amountGroups * 3 + 5)
.forEach(this::finishAction);
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 6);
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0);
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);
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 8, 2);
rolloutManagement.pauseRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.PAUSED, amountGroups + 2, amountGroups * 3 + 8);
assertAndGetRunning(rollout, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); // no new assignment
rolloutManagement.resumeRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 10);
assertAndGetRunning(rollout, 5);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 4); // assign the target created when paused
}
@Test
@Description("Verifies a simple pure (no static groups) dynamic rollout flow with a dynamic group template")
void dynamicRolloutPureFlow() {
final String rolloutName = "pure-dynamic-rollout-std";
final String targetPrefix = "controller-pure-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, 0,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30",
Action.ActionType.FORCED, 1000, false, true,
DynamicRolloutGroupTemplate.builder().nameSuffix("-dyn").targetCount(6).build());
// rollout is READY, amountGroups + 1 (dynamic) rollout groups and amountGroups * 3 targets in static groups
assertRollout(rollout, true, RolloutStatus.READY, 1, 0);
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, 10, Sort.by(Direction.ASC, "id")),
rollout.getId()).getContent();
final RolloutGroup dynamic1 = groups.get(0);
assertRollout(rollout, true, RolloutStatus.READY, 1, 0); // + dynamic
assertGroup(dynamic1, true, RolloutGroupStatus.READY, 0);
// add 4 targets for the first dynamic group, fill partially
testdataFactory.createTargets(targetPrefix, 0, 4);
// start rollout
rolloutManagement.start(rollout.getId());
// handleStartingRollout (no handleRunning called yet)
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 1, 0);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 0);
// partially fill the first dynamic (it is running and now create actions for 4 targets)
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 1, 4);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 4);
// fill first (2) and create fill partially the second (+2 new)
testdataFactory.createTargets(targetPrefix, 4, 4);
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
groups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, 10, Sort.by(Direction.ASC, "id")),
rollout.getId()).getContent();
final RolloutGroup dynamic2 = groups.get(1);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
// create scheduled actions for the dynamic2
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
assertAndGetRunning(rollout, 6); // 6 from the first dynamic
assertScheduled(rollout, 0);
// executes dynamic1 without 1 target
assertAndGetRunning(rollout, 6)// 6 from the first dynamic
.stream()
// remove the last assigned to dynamic1 - it could be amountGroups * 3 + 2 or bigger by id
.filter(action -> Integer.parseInt(action.getTarget().getControllerId().substring(targetPrefix.length())) < 5)
.forEach(this::finishAction);
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 6);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 6);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0);
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);
testdataFactory.createTargets(targetPrefix, 8, 2);
rolloutManagement.pauseRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.PAUSED, 2, 8);
assertAndGetRunning(rollout, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); // no new assignment
rolloutManagement.resumeRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, 2, 10);
assertAndGetRunning(rollout, 5);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 4); // assign the target created when paused
}
private void executeStaticWithoutOneTargetFromTheLastGroup(
final List<RolloutGroup> groups,
final Rollout rollout, final int amountGroups) {