[#1548] Add support for dynamic rollouts (#1533)

* [#1548] Add support for dynamic rollouts

-- Current status --

Initial draft only !!!, to be improved

TODO:
 * evaluate the target count - if update group/rollout total count fails dynamic updates could (?), actually, contain more targets
 * is it needed to break handler on group creating?
 * if dynamic group schedulers occur to be heavy - maybe a handler per tenant will ensure that one tenant won't break all

*Concept for dynamic groups*:

Rollouts are static and dynamic.
Static rollouts consist of static groups only while dynamic rollouts have a number of static groups (first groups) and then an unlimited number of dynamic groups.

Group targets assignments:
* static groups include ALL matching targets created at the time the rollout was created, nevertheless they have active actions with bigger weight or not. Actions for the rollout and included targeets however are created at the start time.
* dynamic groups however are filled in when started and consider the action weight. The targets included in a dynamic group are:
  * matching (filter and distribution set compatible)
  * not included in this or following rollout static groups (if already included in any of the following rollouts - it's intended to be overridden)
  * not in active actions of any rollouts with equal or bigger weight

In general, when you create a rollout it contains all matching targets available at create time overriding any previous rollouts, actions, and so on. If the rollout is dynamic when its dynamic group becomes running it gets only matching targets that doesn't belong to static groups or have actions with great or equal weight

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

* [#1548] Add 1000 weight for actions, rollouts and auto assignments without weight

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-01-18 11:37:01 +02:00
committed by GitHub
parent b98b224964
commit 7768e543fd
50 changed files with 1005 additions and 177 deletions

View File

@@ -106,6 +106,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
generateAction.setWeight(1000);
final Action action = actionRepository.save(generateAction);
@@ -132,6 +133,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
generateAction.setWeight(1000);
final Action action = actionRepository.save(generateAction);

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.assertj.core.api.Assertions.assertThat;
import net.bytebuddy.agent.builder.AgentBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -86,6 +87,7 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
generateAction.setDistributionSet(distributionSet);
generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
generateAction.setWeight(1000);
return actionRepository.save(generateAction);
}

View File

@@ -19,6 +19,9 @@ import javax.persistence.PersistenceContext;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
@@ -40,6 +43,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
@@ -52,6 +56,7 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
import org.springframework.data.domain.Page;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
@@ -59,6 +64,8 @@ import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import static org.assertj.core.api.Assertions.assertThat;
@ContextConfiguration(classes = {
RepositoryApplicationConfiguration.class, TestConfiguration.class,
TestSupportBinderAutoConfiguration.class })
@@ -159,4 +166,42 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), type.getId());
}
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);
}
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);
}
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);
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);
}
protected void finishAction(final Action action) {
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Action.Status.FINISHED));
}
private JpaRollout refresh(final Rollout rollout) {
return rolloutRepository.findById(rollout.getId()).get();
}
protected JpaRolloutGroup refresh(final RolloutGroup group) {
return rolloutGroupRepository.findById(group.getId()).get();
}
}

View File

@@ -440,7 +440,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest {
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions).hasSize(amountOfTargets);
assertThat(actions).allMatch(action -> !action.getWeight().isPresent());
assertThat(actions).allMatch(action -> action.getWeight().isPresent());
}
@Test

View File

@@ -115,6 +115,7 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
assertThat(actionRepository.count()).isEqualTo(3);
waitNextMillis();
autoActionCleanup.run();
assertThat(actionRepository.count()).isEqualTo(1);

View File

@@ -1101,6 +1101,9 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
.status(Action.Status.RUNNING).occurredAt(System.currentTimeMillis())
.messages(Lists.newArrayList("proceeding message 1")));
final long createTime = System.currentTimeMillis();
waitNextMillis();
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
.status(Action.Status.RUNNING).occurredAt(System.currentTimeMillis())
.messages(Lists.newArrayList("proceeding message 2")));

View File

@@ -972,18 +972,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("An assignment request containing a weight causes an error when multi assignment in disabled.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
void weightAllowedWhenMultiAssignmentModeNotEnabled() {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final DeploymentRequest assignWithoutWeight = DeploymentManagement.deploymentRequest(targetId, dsId)
.setWeight(456).build();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class).isThrownBy(
() -> deploymentManagement.assignDistributionSets(Collections.singletonList(assignWithoutWeight)));
deploymentManagement.assignDistributionSets(Collections.singletonList(assignWithoutWeight));
}
@Test

View File

@@ -0,0 +1,205 @@
/**
* Copyright (c) 2023 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.management;
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.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
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.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Junit tests for RolloutManagement.
*/
@Feature("Component Tests - Repository")
@Story("Rollout Management (Flow)")
class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
@BeforeEach
void reset() {
this.approvalStrategy.setApprovalNeeded(false);
}
@Test
@Description("Verifies a simple rollout flow")
void rolloutFlow() {
final String rolloutName = "rollout-std";
final int amountGroups = 5; // static only
final String targetPrefix = "controller-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, false);
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
rollout.getId()).getContent();
// add 2 targets not to be included
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2);
// start rollout
rolloutManagement.start(rollout.getId());
// handleStartingRollout (no handleRunning called yet)
rolloutHandler.handleAll();
assertRollout(rollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
for (int i = 0; i < amountGroups; i++) {
assertGroup(groups.get(i), false, i == 0 ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.SCHEDULED, 3);
}
// execute groups (without on of the last)
assertThat(refresh(groups.get(0)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
for (int i = 0; i < amountGroups; i++) {
if (i + 1 != amountGroups) {
assertThat(refresh(groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED);
}
assertAndGetRunning(rollout, 3)
.stream()
.filter(action -> !(targetPrefix + (amountGroups * 3 - 1)).equals(action.getTarget().getControllerId()))
.forEach(this::finishAction);
rolloutHandler.handleAll();
assertThat(refresh(groups.get(i)).getStatus()).isEqualTo(i + 1 == amountGroups ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.FINISHED);
if (i + 1 != amountGroups) {
assertThat(refresh(groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
}
}
rolloutManagement.pauseRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, false, RolloutStatus.PAUSED, amountGroups, amountGroups * 3);
assertAndGetRunning(rollout, 1); // keep running
rolloutManagement.resumeRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
assertAndGetRunning(rollout, 1); // keep running
}
@Test
@Description("Verifies a simple dynamic rollout flow")
void dynamicRolloutFlow() {
final String rolloutName = "dynamic-rollout-std";
final int amountGroups = 5; // static only
final String targetPrefix = "controller-dynamic-rollout-std-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, true);
// rollout is READY
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 2 targets for the first dynamic group
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2);
// 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);
// execute statics (without on of the last) which start dynamic
assertThat(refresh(groups.get(0)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
for (int i = 0; i < amountGroups; i++) {
assertThat(refresh(groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED);
assertAndGetRunning(rollout, 3)
.stream()
.filter(action -> !(targetPrefix + (amountGroups * 3 - 1)).equals(action.getTarget().getControllerId()))
.forEach(this::finishAction);
rolloutHandler.handleAll();
assertThat(refresh(groups.get(i)).getStatus()).isEqualTo(i + 1 == amountGroups ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.FINISHED);
assertThat(refresh(i + 1 == amountGroups ? dynamic1 : groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); // on last round check dynamic
}
// partially fill the first dynamic (it is running and now create actions for 2 targets)
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 3 + 2);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 2);
// fill first and create second
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 2, 2);
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
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 + 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
assertAndGetRunning(rollout, 4); // one from the last static group and 3 from the first dynamic
assertScheduled(rollout, 0);
// executes last from static and dynamic1 without 1 target
assertAndGetRunning(rollout, 4)// one from the last static and 3 for 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 + 2)
.forEach(this::finishAction);
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0);
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);
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 1);
rolloutManagement.pauseRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.PAUSED, amountGroups + 2, amountGroups * 3 + 4);
assertAndGetRunning(rollout, 2);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 1); // no new assignment
rolloutManagement.resumeRollout(rollout.getId());
rolloutHandler.handleAll();
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 5);
assertAndGetRunning(rollout, 3);
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); // assign the target created when paused
}
}

View File

@@ -440,11 +440,6 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
private void finishAction(final Action action) {
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED));
}
@Test
@Description("Verifying that the error handling action of a group is executed to pause the current rollout")
void checkErrorHitOfGroupCallsErrorActionToPauseTheRollout() {
@@ -1432,7 +1427,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
final RolloutGroupCreate group1 = entityFactory.rolloutGroup().create().conditions(conditions).name("group1")
.targetPercentage(50.0F);
final RolloutGroupCreate group2 = entityFactory.rolloutGroup().create().conditions(conditions).name("group2")
.targetPercentage(100.0F);
.targetPercentage(50.0F);
// group1 exceeds the quota
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
@@ -1456,9 +1451,9 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
final RolloutGroupCreate group5 = entityFactory.rolloutGroup().create().conditions(conditions).name("group5")
.targetPercentage(33.3F);
final RolloutGroupCreate group6 = entityFactory.rolloutGroup().create().conditions(conditions).name("group6")
.targetPercentage(66.6F);
.targetPercentage(33.3F);
final RolloutGroupCreate group7 = entityFactory.rolloutGroup().create().conditions(conditions).name("group7")
.targetPercentage(100.0F);
.targetPercentage(33.3F);
// should work fine
assertThat(rolloutManagement.create(
@@ -1531,17 +1526,17 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
final int amountTargetsInGroup1 = 10;
final int percentTargetsInGroup1 = 100;
final int amountTargetsInGroup1and2 = 20;
final int amountTargetsInGroup2and3 = 20;
final int percentTargetsInGroup2 = 20;
final int percentTargetsInGroup3 = 100;
final int percentTargetsInGroup3 = 80;
final int countTargetsInGroup2 = (int) Math
.ceil((double) percentTargetsInGroup2 / 100 * amountTargetsInGroup1and2);
final int countTargetsInGroup3 = amountTargetsInGroup1and2 - countTargetsInGroup2;
.ceil((double) percentTargetsInGroup2 / 100 * amountTargetsInGroup2and3);
final int countTargetsInGroup3 = amountTargetsInGroup2and3 - countTargetsInGroup2;
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
// Generate Targets for group 2 and 3 and generate the Rollout
final RolloutCreate rolloutcreate = generateTargetsAndRollout(rolloutName, amountTargetsInGroup1and2);
final RolloutCreate rolloutcreate = generateTargetsAndRollout(rolloutName, amountTargetsInGroup2and3);
// Generate Targets for group 1
testdataFactory.createTargets(amountTargetsInGroup1, rolloutName + "-gr1-", rolloutName);
@@ -1568,7 +1563,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
myRollout = getRollout(myRollout.getId());
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1and2 + amountTargetsInGroup1);
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup2and3 + amountTargetsInGroup1);
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, myRollout.getId()).getContent();
@@ -1979,11 +1974,10 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Creating a rollout with a weight causes an error when multi assignment in disabled.")
void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50",
void weightAllowedWhenMultiAssignmentModeNotEnabled() {
testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50",
"80",
ActionType.FORCED, 66));
ActionType.FORCED, 66);
}
@Test
@@ -2028,7 +2022,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Rollout can be created without weight in single assignment and be started in multi assignment")
void createInSingleStartInMultiassigMode() {
void createInSingleStartInMultiassignMode() {
final int amountOfTargets = 5;
final Long rolloutId = testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2,
amountOfTargets,
@@ -2038,7 +2032,8 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.start(rolloutId);
rolloutHandler.handleAll();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions).hasSize(amountOfTargets).allMatch(action -> !action.getWeight().isPresent());
// wight replaced with default
assertThat(actions).hasSize(5).allMatch(action -> action.getWeight().isPresent());
}
@Test
@@ -2319,4 +2314,66 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> rolloutManagement.triggerNextGroup(createdRollout.getId()))
.withMessageContaining(errorMessage);
}
/**
* Tests static assignment aspects of the dynamic group assignment filters.
*/
@Test
@Description("Dynamic group doesn't override newer static group assignments")
public void dynamicGroupDoesntOverrideItsOrNewerStaticGroups() {
final int amountGroups = 1; // static only
final String targetPrefix = "controller-dynamic-rollout-";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("ds");
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);
rolloutManagement.start(dynamicRollout.getId());
rolloutHandler.handleAll();
assertRollout(dynamicRollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 2);
final List<RolloutGroup> dynamicGroups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
dynamicRollout.getId()).getContent();
for (int i = 0; i < dynamicGroups.size(); i++) {
final RolloutGroup group = dynamicGroups.get(i);
if (i + 1 == dynamicGroups.size()) {
assertGroup(group, true, RolloutGroupStatus.SCHEDULED, 0);
} else {
assertGroup(group, false, RolloutGroupStatus.RUNNING, 2);
}
}
assertAndGetRunning(dynamicRollout, 2).forEach(this::finishAction);
rolloutHandler.handleAll();
for (int i = 0; i < dynamicGroups.size(); i++) {
final RolloutGroup group = dynamicGroups.get(i);
if (i + 1 == dynamicGroups.size()) {
assertGroup(group, true, RolloutGroupStatus.RUNNING, 0);
} else {
assertGroup(group, false, RolloutGroupStatus.FINISHED, 2);
}
}
assertAndGetRunning(dynamicRollout, 0);
rolloutHandler.handleAll();
// NB: asserts that dynamic group doesn't get from its static groups (already finished action targets)
assertGroup(dynamicGroups.get(dynamicGroups.size() - 1), true, RolloutGroupStatus.RUNNING, 0);
assertAndGetRunning(dynamicRollout, 0);
rolloutManagement.pauseRollout(dynamicRollout.getId());
rolloutHandler.handleAll();
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);
rolloutManagement.start(staticRollout.getId());
rolloutHandler.handleAll();
assertRollout(staticRollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
final List<RolloutGroup> staticGroups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
staticRollout.getId()).getContent();
staticGroups.forEach(group -> assertGroup(group, false, RolloutGroupStatus.RUNNING, 3));
rolloutManagement.resumeRollout(dynamicRollout.getId());
rolloutHandler.handleAll(); // resume, do not get last devices (they are assigned to a newer group, nevertheless newer is with bigger weight
assertGroup(dynamicGroups.get(dynamicGroups.size() - 1), true, RolloutGroupStatus.RUNNING, 0);
assertAndGetRunning(dynamicRollout, 0);
}
}

View File

@@ -463,17 +463,15 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
@Test
@Description("Creating or updating a target filter query with autoassignment with a weight causes an error when multi assignment in disabled.")
public void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
public void weightAllowedWhenMultiAssignmentModeNotEnabled() {
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long filterId = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")).getId();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("b").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(342)));
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(343)));
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("b").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(342));
targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(343));
}
@Test

View File

@@ -53,13 +53,17 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
@@ -1328,6 +1332,65 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
"name==*")).isFalse();
}
/**
* Tests action based aspects of the dynamic group assignment filters.
*/
@Test
@Description("Target matches filter no active action with ge weight.")
void findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable() {
final String targetPrefix = "dyn_action_filter_";
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(targetPrefix, 9);
final Rollout rolloutOlder = testdataFactory.createRollout();
final Rollout rollout = testdataFactory.createRollout();
final Rollout rolloutNewer = testdataFactory.createRollout();
// old ro with less weight - match
createAction(targets.get(0), rolloutOlder, 0, Status.RUNNING, distributionSet);
// old ro with less weight - match
createAction(targets.get(1), rolloutOlder, 5, Status.SCHEDULED, distributionSet);
// old ro with equal weight - match
createAction(targets.get(2), rolloutOlder, 10, Status.RUNNING, distributionSet);
// old ro with BIGGER weight - doesn't match
createAction(targets.get(3), rolloutOlder, 20, Status.WAIT_FOR_CONFIRMATION, distributionSet);
// same ro - doesn't match
createAction(targets.get(4), rollout, 10, Status.RUNNING, distributionSet);
// new ro with less weight - match
createAction(targets.get(5), rolloutNewer, 0, Status.RUNNING, distributionSet);
// new ro with less weight - match
createAction(targets.get(6), rolloutNewer, 5, Status.WARNING, distributionSet);
// NEW ro with EQUAL weight - doesn't match
createAction(targets.get(7), rolloutNewer, 10, Status.RUNNING, distributionSet);
// new ro with BIGGER weight - doesn't match
createAction(targets.get(8), rolloutNewer, 20, Status.DOWNLOADED, distributionSet);
final Slice<Target> matching = targetManagement.findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable(
PAGE, rollout.getId(), 10, Long.MAX_VALUE,"controllerid==dyn_action_filter_*", distributionSet.getType());
assertThat(matching.getNumberOfElements()).isEqualTo(5);
assertThat(matching.stream()
.map(Target::getControllerId)
.map(s -> s.substring(targetPrefix.length()))
.map(Integer::parseInt)
.sorted()
.toList()).isEqualTo(List.of(0, 1, 2, 5, 6));
}
private void createAction(final Target target, final Rollout rollout, final Integer weight, final Action.Status status, final DistributionSet distributionSet) {
final JpaAction action = new JpaAction();
action.setActionType(Action.ActionType.FORCED);
action.setTarget(target);
action.setInitiatedBy("test");
if (rollout != null) {
action.setRollout(rollout);
}
if (weight != null) {
action.setWeight(weight);
}
action.setStatus(status);
action.setDistributionSet(distributionSet);
actionRepository.save(action);
}
@Test
@Description("Target matches filter for not existing DS.")
void matchesFilterDsNotExists() {