From 119d1b5c50929a694b13256054af312a87ed4374 Mon Sep 17 00:00:00 2001 From: Denislav Prinov Date: Fri, 19 Jul 2024 08:25:31 +0300 Subject: [PATCH] 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 * Refactoring Signed-off-by: Denislav Prinov * Additional check before the dynamic group is triggered Signed-off-by: Denislav Prinov * Refactoring Signed-off-by: Denislav Prinov --------- Signed-off-by: Denislav Prinov --- .../resource/MgmtRolloutResourceTest.java | 165 +++++++++++++++--- .../hawkbit/rest/util/JsonBuilder.java | 17 ++ 2 files changed, 153 insertions(+), 29 deletions(-) diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java index 74c9d96bc..2ffec13e3 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java @@ -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 additionalTargets = testdataFactory.createTargets(amountOfDynamicTargets, "rollout-dynamic-addition-", "rollout"); + + rolloutHandler.handleAll(); + + final List 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 maybeTarget = SecurityContextSwitch.runAsPrivileged(() -> { + rolloutHandler.handleAll(); + List 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, diff --git a/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java b/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java index 261e3fc75..ba6afff25 100644 --- a/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java +++ b/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java @@ -514,6 +514,14 @@ public abstract class JsonBuilder { final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions, final List 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 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) {