Tests for the new Dynamic Rollouts, including the new Dynamic group definition functionality (#1777)

* Added tests for the new Dynamic Rollouts, including the new Dynamic group definition functionality

Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com>

* Refactoring

Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com>

* Additional check before the dynamic group is triggered

Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com>

* Refactoring

Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com>

---------

Signed-off-by: Denislav Prinov <denislav.prinov@bosch.com>
This commit is contained in:
Denislav Prinov
2024-07-19 08:25:31 +03:00
committed by GitHub
parent 56ab399493
commit 119d1b5c50
2 changed files with 153 additions and 29 deletions

View File

@@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCond
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser;
@@ -222,6 +223,81 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
}
@Test
@Description("Testing dynamic rollouts with default dynamic group definition")
void createDynamicRolloutWithDefaultDynamicGroupDefinition() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("ro");
final int amountTargets = 10;
testdataFactory.createTargets(amountTargets, "rollout-dynamic", "rollout");
postRollout("rollout1", 1, dsA.getId(), "name==rollout*", 10, Action.ActionType.FORCED,
null, null, true, null, 1);
rolloutHandler.handleAll();
final Rollout rollout = rolloutManagement.findByRsql(PAGE, "name==rollout1", false).getContent().get(0);
rolloutManagement.start(rollout.getId());
rolloutHandler.handleAll();
mvc.perform(get("/rest/v1/rollouts?representation=full").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.content", hasSize(1))).andExpect(jsonPath("$.total", equalTo(1)))
.andExpect(jsonPath("content[0].id", equalTo(rollout.getId().intValue())))
.andExpect(jsonPath("content[0].name", equalTo("rollout1")))
.andExpect(jsonPath("content[0].status", equalTo("running")))
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==rollout*")))
.andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue())))
.andExpect(jsonPath("content[0].totalTargets", equalTo(10)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus").exists())
.andExpect(jsonPath("content[0].totalTargetsPerStatus.running", equalTo(10)))
.andExpect(jsonPath("content[0].totalGroups", equalTo(2)));
final int amountOfDynamicTargets = 2;
List<Target> additionalTargets = testdataFactory.createTargets(amountOfDynamicTargets, "rollout-dynamic-addition-", "rollout");
rolloutHandler.handleAll();
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent();
groups.forEach(group -> {
if (!group.getName().contains("-dynamic")) {
rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, group.getId()).forEach(target -> deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).forEach(action -> {
deploymentManagement.cancelAction(action.getId());
awaitActionStatus(action.getId(), Status.CANCELING);
deploymentManagement.forceQuitAction(action.getId());
awaitActionStatus(action.getId(), Status.CANCELED);
}));
}
});
awaitPendingDeviceInRollout(additionalTargets.get(0).getControllerId());
mvc.perform(get("/rest/v1/rollouts?representation=full").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.content", hasSize(1))).andExpect(jsonPath("$.total", equalTo(1)))
.andExpect(jsonPath("content[0].id", equalTo(rollout.getId().intValue())))
.andExpect(jsonPath("content[0].name", equalTo("rollout1")))
.andExpect(jsonPath("content[0].status", equalTo("running")))
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==rollout*")))
.andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue())))
.andExpect(jsonPath("content[0].totalTargets", equalTo(11)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus").exists())
.andExpect(jsonPath("content[0].totalTargetsPerStatus.running", equalTo(1)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.notstarted", equalTo(0)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.scheduled", equalTo(0)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.cancelled", equalTo(10)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.finished", equalTo(0)))
.andExpect(jsonPath("content[0].totalTargetsPerStatus.error", equalTo(0)))
.andExpect(jsonPath("content[0].deleted", equalTo(false)))
.andExpect(jsonPath("content[0].totalGroups", equalTo(3)))
.andExpect(jsonPath("content[0]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX)));
}
@Test
@Description("Testing that no rollout with groups that have illegal percentages can be created")
void createRolloutWithTooLowPercentage() throws Exception {
@@ -1239,6 +1315,30 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
.getStatus().equals(RolloutStatus.RUNNING));
}
private void awaitActionStatus(final Long actionId, final Status status) {
Awaitility.await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofMillis(100)).with()
.until(() -> SecurityContextSwitch
.runAsPrivileged(
() -> deploymentManagement.findAction(actionId).orElseThrow(NoSuchElementException::new))
.getStatus().equals(status));
}
private void awaitPendingDeviceInRollout(final String controllerId) {
Awaitility.await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofMillis(100)).with()
.until(() -> {
Optional<Target> maybeTarget = SecurityContextSwitch.runAsPrivileged(() -> {
rolloutHandler.handleAll();
List<Target> targets = targetManagement.findByRsql(PAGE, "controllerId==" + controllerId).getContent();
if (targets.size() == 1) {
return Optional.of(targets.get(0));
} else {
return Optional.empty();
}
});
return maybeTarget.isPresent() && maybeTarget.get().getUpdateStatus().equals(TargetUpdateStatus.PENDING);
});
}
@Test
@Description("Deletion of a rollout")
void deleteRollout() throws Exception {
@@ -1492,40 +1592,47 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
private void postRollout(final String name, final int groupSize, final Long distributionSetId,
final String targetFilterQuery, final int targets, final Action.ActionType type, final Long startTime,
final Long forceTime) throws Exception {
postRollout(name, groupSize, distributionSetId, targetFilterQuery, targets, type, startTime, forceTime, false, null, 0);
}
private void postRollout(final String name, final int groupSize, final Long distributionSetId,
final String targetFilterQuery, final int targets, final Action.ActionType type, final Long startTime,
final Long forceTime, boolean isDynamic, String dynamicGroupSuffix, int dynamicGroupTargetsCount) throws Exception {
final String actionType = MgmtRestModelMapper.convertActionType(type).getName();
final String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null, startTime, forceTime,
null);
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null, startTime, forceTime,
null, isDynamic, dynamicGroupSuffix, dynamicGroupTargetsCount);
mvc.perform(post("/rest/v1/rollouts").content(rollout).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(jsonPath("$.name", equalTo(name))).andExpect(jsonPath("$.status", equalTo("creating")))
.andExpect(jsonPath("$.type", equalTo(actionType)))
.andExpect(jsonPath("$.targetFilterQuery", equalTo(targetFilterQuery)))
.andExpect(jsonPath("$.description", equalTo("desc")))
.andExpect(jsonPath("$.distributionSetId", equalTo(distributionSetId.intValue())))
.andExpect(jsonPath("$.createdBy", equalTo("bumlux")))
.andExpect(jsonPath("$.createdAt", not(equalTo(0))))
.andExpect(jsonPath("$.lastModifiedBy", equalTo("bumlux")))
.andExpect(jsonPath("$.lastModifiedAt", not(equalTo(0))))
.andExpect(jsonPath("$.totalTargets", equalTo(targets)))
.andExpect(startTime != null ? jsonPath("$.startAt", equalTo(startTime.intValue()))
: jsonPath("$.startAt").doesNotExist())
.andExpect(forceTime != null ? jsonPath("$.forcetime", equalTo(forceTime.intValue()))
: jsonPath("$.forcetime", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.running", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.notstarted", equalTo(targets)))
.andExpect(jsonPath("$.totalTargetsPerStatus.scheduled", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.cancelled", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.finished", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)))
.andExpect(jsonPath("$._links.self.href", startsWith(HREF_ROLLOUT_PREFIX)))
.andExpect(jsonPath("$._links.start.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/start"))))
.andExpect(jsonPath("$._links.pause.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/pause"))))
.andExpect(
jsonPath("$._links.resume.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/resume"))))
.andExpect(jsonPath("$._links.groups.href",
allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups"))));
.andExpect(jsonPath("$.name", equalTo(name))).andExpect(jsonPath("$.status", equalTo("creating")))
.andExpect(jsonPath("$.type", equalTo(actionType)))
.andExpect(jsonPath("$.targetFilterQuery", equalTo(targetFilterQuery)))
.andExpect(jsonPath("$.description", equalTo("desc")))
.andExpect(jsonPath("$.distributionSetId", equalTo(distributionSetId.intValue())))
.andExpect(jsonPath("$.createdBy", equalTo("bumlux")))
.andExpect(jsonPath("$.createdAt", not(equalTo(0))))
.andExpect(jsonPath("$.lastModifiedBy", equalTo("bumlux")))
.andExpect(jsonPath("$.lastModifiedAt", not(equalTo(0))))
.andExpect(jsonPath("$.totalTargets", equalTo(targets)))
.andExpect(startTime != null ? jsonPath("$.startAt", equalTo(startTime.intValue()))
: jsonPath("$.startAt").doesNotExist())
.andExpect(forceTime != null ? jsonPath("$.forcetime", equalTo(forceTime.intValue()))
: jsonPath("$.forcetime", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.running", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.notstarted", equalTo(targets)))
.andExpect(jsonPath("$.totalTargetsPerStatus.scheduled", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.cancelled", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.finished", equalTo(0)))
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)))
.andExpect(jsonPath("$._links.self.href", startsWith(HREF_ROLLOUT_PREFIX)))
.andExpect(jsonPath("$._links.start.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/start"))))
.andExpect(jsonPath("$._links.pause.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/pause"))))
.andExpect(jsonPath("$.dynamic", equalTo(isDynamic)))
.andExpect(
jsonPath("$._links.resume.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/resume"))))
.andExpect(jsonPath("$._links.groups.href",
allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups"))));
}
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,

View File

@@ -514,6 +514,14 @@ public abstract class JsonBuilder {
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
final List<String> groupJsonList, final String type, final Integer weight, final Long startAt, final Long forceTime,
final Boolean confirmationRequired) {
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, groupJsonList, type,
weight, startAt, forceTime, confirmationRequired, false, null, 0);
}
public static String rollout(final String name, final String description, final Integer groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
final List<String> groupJsonList, final String type, final Integer weight, final Long startAt, final Long forceTime,
final Boolean confirmationRequired, final boolean isDynamic, final String dynamicGroupSuffix, final int dynamicGroupTargetsCount) {
final JSONObject json = new JSONObject();
try {
@@ -567,6 +575,15 @@ public abstract class JsonBuilder {
errorAction.put("expression", conditions.getErrorActionExp());
}
if (isDynamic) {
json.put("dynamic", isDynamic);
final JSONObject dynamicGroupTemplate = new JSONObject();
json.put("dynamicGroupTemplate", dynamicGroupTemplate);
dynamicGroupTemplate.put("nameSuffix", (dynamicGroupSuffix == null || dynamicGroupSuffix.isEmpty()) ? "-dynamic" : dynamicGroupSuffix);
dynamicGroupTemplate.put("targetCount", dynamicGroupTargetsCount < 0 ? 1 : dynamicGroupTargetsCount);
}
if (!CollectionUtils.isEmpty(groupJsonList)) {
final JSONArray jsonGroups = new JSONArray();
for (final String groupJson : groupJsonList) {