diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/rollout/MgmtRolloutResponseBody.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/rollout/MgmtRolloutResponseBody.java index 5f0b5ed6e..b86871229 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/rollout/MgmtRolloutResponseBody.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/rollout/MgmtRolloutResponseBody.java @@ -44,6 +44,12 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity { @JsonProperty private Integer totalGroups; + @JsonProperty + private Long startAt; + + @JsonProperty + private Long forcetime; + @JsonProperty private boolean deleted; @@ -101,6 +107,22 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity { return totalTargets; } + public void setStartAt(final Long startAt) { + this.startAt = startAt; + } + + public Long getStartAt() { + return startAt; + } + + public void setForcetime(final Long forcetime) { + this.forcetime = forcetime; + } + + public Long getForcetime() { + return forcetime; + } + public Map getTotalTargetsPerStatus() { return totalTargetsPerStatus; } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutMapper.java index b30207586..bc1c1ec5e 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutMapper.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutMapper.java @@ -84,6 +84,7 @@ final class MgmtRolloutMapper { body.setTotalTargets(rollout.getTotalTargets()); body.setDeleted(rollout.isDeleted()); body.setType(MgmtRestModelMapper.convertActionType(rollout.getActionType())); + body.setForcetime(rollout.getForcedTime()); rollout.getWeight().ifPresent(body::setWeight); if (withDetails) { @@ -92,6 +93,7 @@ final class MgmtRolloutMapper { rollout.getTotalTargetCountStatus().getTotalTargetCountByStatus(status)); } body.setTotalGroups(rollout.getRolloutGroupsCreated()); + body.setStartAt(rollout.getStartAt()); body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId())).withRel("start")); body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause")); 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 737accef9..ad0c58cc3 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 @@ -397,7 +397,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { mvc.perform(post("/rest/v1/rollouts") .content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*", - rolloutGroupConditions, rolloutGroups, null, null, false)) + rolloutGroupConditions, rolloutGroups, null, null, null, null, false)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn(); @@ -438,7 +438,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { mvc.perform(post("/rest/v1/rollouts") .content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*", - rolloutGroupConditions, rolloutGroups, null, null, null)) + rolloutGroupConditions, rolloutGroups, null, null, null, null, null)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn(); @@ -585,6 +585,25 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { .andExpect(jsonPath("content[1]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX))); } + @Test + void retrieveRolloutWithStartAtAndForcedTimeResponseFields() throws Exception { + final DistributionSet dsA = testdataFactory.createDistributionSet(""); + final Long startAt = 21L; + final Long forcetime = 45L; + + testdataFactory.createTargets(20, "target", "rollout"); + postRollout("rollout1", 5, dsA.getId(), "id==target*", 20, Action.ActionType.TIMEFORCED, + startAt, forcetime); + postRollout("rollout2", 5, dsA.getId(), "id==target-0001*", 10, Action.ActionType.TIMEFORCED, + startAt, forcetime); + + // Run here, because Scheduler is disabled during tests + rolloutManagement.handleRollouts(); + + retrieveAndCompareRolloutsContent(dsA, "/rest/v1/rollouts", false, true, startAt, forcetime); + retrieveAndCompareRolloutsContent(dsA, "/rest/v1/rollouts?representation=full", true, true, startAt, forcetime); + } + @Test @Description("Testing representation mode of rollout paged list") void rolloutPagedListRepresentationMode() throws Exception { @@ -1180,9 +1199,9 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { final int weight = 66; final String invalideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*", - new RolloutGroupConditionBuilder().withDefaults().build(), null, null, Action.WEIGHT_MIN - 1, null); + new RolloutGroupConditionBuilder().withDefaults().build(), null, null, Action.WEIGHT_MIN - 1, null, null, null); final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*", - new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null); + new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null); mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) @@ -1202,11 +1221,18 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { assertThat(rollouts.get(0).getWeight()).get().isEqualTo(weight); } + private void postRollout(final String name, final int groupSize, final Long distributionSetId, - final String targetFilterQuery, final int targets, final Action.ActionType type) throws Exception { + final String targetFilterQuery, final int targets, final Action.ActionType type) throws Exception { + postRollout(name, groupSize, distributionSetId, targetFilterQuery, targets, type, null, null); + } + + 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 { final String actionType = MgmtRestModelMapper.convertActionType(type).getName(); final String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, - new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null, null); + new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null, startTime, + forceTime, null); mvc.perform(post("/rest/v1/rollouts").content(rollout).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) @@ -1220,6 +1246,12 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { .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))) @@ -1330,87 +1362,98 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest { private void retrieveAndCompareRolloutsContent(final DistributionSet dsA, final String urlTemplate, final boolean isFullRepresentation) throws Exception { + retrieveAndCompareRolloutsContent(dsA, urlTemplate, isFullRepresentation, false, null, null); + } + + private void retrieveAndCompareRolloutsContent(final DistributionSet dsA, final String urlTemplate, + final boolean isFullRepresentation, final boolean isStartTypeScheduled, final Long startAt, final Long forcetime) throws Exception { mvc.perform(get(urlTemplate).accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) - .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$.content", hasSize(2))).andExpect(jsonPath("$.total", equalTo(2))) - .andExpect(jsonPath("content[0].name", equalTo("rollout1"))) - .andExpect(jsonPath("content[0].status", equalTo("ready"))) - .andExpect(jsonPath("content[0].targetFilterQuery", equalTo("id==target*"))) - .andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue()))) - .andExpect(jsonPath("content[0].createdBy", equalTo("bumlux"))) - .andExpect(jsonPath("content[0].createdAt", not(equalTo(0)))) - .andExpect(jsonPath("content[0].lastModifiedBy", equalTo("bumlux"))) - .andExpect(jsonPath("content[0].lastModifiedAt", not(equalTo(0)))) - .andExpect(jsonPath("content[0].totalTargets", equalTo(20))) - .andExpect(isFullRepresentation ? jsonPath("$.content[0].totalTargetsPerStatus").exists() - : jsonPath("content[0].totalTargetsPerStatus").doesNotExist()) - .andExpect(isFullRepresentation ? jsonPath("$.content[0].totalGroups", equalTo(5)) - : jsonPath("content[0].totalGroups").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.start.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.start.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.pause.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.pause.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.resume.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.resume.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.triggerNextGroup.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.triggerNextGroup.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.approve.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.approve.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.deny.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.deny.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.groups.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[0]._links.groups.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[0]._links.distributionset.href", - startsWith("http://localhost/rest/v1/distributionsets/")) - : jsonPath("content[0]._links.distributionset.href").doesNotExist()) - .andExpect(jsonPath("content[0]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX))) - .andExpect(jsonPath("content[1].name", equalTo("rollout2"))) - .andExpect(jsonPath("content[1].status", equalTo("ready"))) - .andExpect(jsonPath("content[1].targetFilterQuery", equalTo("id==target-0001*"))) - .andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue()))) - .andExpect(jsonPath("content[1].createdBy", equalTo("bumlux"))) - .andExpect(jsonPath("content[1].createdAt", not(equalTo(0)))) - .andExpect(jsonPath("content[1].lastModifiedBy", equalTo("bumlux"))) - .andExpect(jsonPath("content[1].lastModifiedAt", not(equalTo(0)))) - .andExpect(jsonPath("content[1].totalTargets", equalTo(10))) - .andExpect(isFullRepresentation ? jsonPath("$.content[1].totalTargetsPerStatus").exists() - : jsonPath("content[1].totalTargetsPerStatus").doesNotExist()) - .andExpect(isFullRepresentation ? jsonPath("$.content[0].totalGroups", equalTo(5)) - : jsonPath("content[0].totalGroups").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.start.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.start.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.pause.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.pause.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.resume.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.resume.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.triggerNextGroup.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.triggerNextGroup.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.approve.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.approve.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.deny.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.deny.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.groups.href", startsWith(HREF_ROLLOUT_PREFIX)) - : jsonPath("content[1]._links.groups.href").doesNotExist()) - .andExpect(isFullRepresentation - ? jsonPath("$.content[1]._links.distributionset.href", - startsWith("http://localhost/rest/v1/distributionsets/")) - : jsonPath("content[1]._links.distributionset.href").doesNotExist()) - .andExpect(jsonPath("content[1]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX))); + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.content", hasSize(2))).andExpect(jsonPath("$.total", equalTo(2))) + .andExpect(jsonPath("content[0].name", equalTo("rollout1"))) + .andExpect(jsonPath("content[0].status", equalTo("ready"))) + .andExpect(jsonPath("content[0].targetFilterQuery", equalTo("id==target*"))) + .andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue()))) + .andExpect(jsonPath("content[0].createdBy", equalTo("bumlux"))) + .andExpect(jsonPath("content[0].createdAt", not(equalTo(0)))) + .andExpect(jsonPath("content[0].lastModifiedBy", equalTo("bumlux"))) + .andExpect(jsonPath("content[0].lastModifiedAt", not(equalTo(0)))) + .andExpect(jsonPath("content[0].totalTargets", equalTo(20))) + .andExpect(jsonPath("content[0].forcetime", equalTo(isStartTypeScheduled ? forcetime.intValue() : 0))) + .andExpect(isFullRepresentation ? jsonPath("$.content[0].totalTargetsPerStatus").exists() + : jsonPath("content[0].totalTargetsPerStatus").doesNotExist()) + .andExpect(isFullRepresentation ? jsonPath("$.content[0].totalGroups", equalTo(5)) + : jsonPath("content[0].totalGroups").doesNotExist()) + .andExpect(isFullRepresentation && isStartTypeScheduled ? jsonPath("$.content[0].startAt", equalTo(startAt.intValue())) + : jsonPath("$.content[0].startAt").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.start.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.start.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.pause.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.pause.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.resume.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.resume.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.triggerNextGroup.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.triggerNextGroup.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.approve.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.approve.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.deny.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.deny.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.groups.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[0]._links.groups.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[0]._links.distributionset.href", + startsWith("http://localhost/rest/v1/distributionsets/")) + : jsonPath("content[0]._links.distributionset.href").doesNotExist()) + .andExpect(jsonPath("content[0]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX))) + .andExpect(jsonPath("content[1].name", equalTo("rollout2"))) + .andExpect(jsonPath("content[1].status", equalTo("ready"))) + .andExpect(jsonPath("content[1].targetFilterQuery", equalTo("id==target-0001*"))) + .andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue()))) + .andExpect(jsonPath("content[1].createdBy", equalTo("bumlux"))) + .andExpect(jsonPath("content[1].createdAt", not(equalTo(0)))) + .andExpect(jsonPath("content[1].lastModifiedBy", equalTo("bumlux"))) + .andExpect(jsonPath("content[1].lastModifiedAt", not(equalTo(0)))) + .andExpect(jsonPath("content[1].totalTargets", equalTo(10))) + .andExpect(jsonPath("content[1].forcetime", equalTo(isStartTypeScheduled ? forcetime.intValue() : 0))) + .andExpect(isFullRepresentation ? jsonPath("$.content[1].totalTargetsPerStatus").exists() + : jsonPath("content[1].totalTargetsPerStatus").doesNotExist()) + .andExpect(isFullRepresentation ? jsonPath("$.content[1].totalGroups", equalTo(5)) + : jsonPath("content[1].totalGroups").doesNotExist()) + .andExpect(isFullRepresentation && isStartTypeScheduled ? jsonPath("content[1].startAt", equalTo(startAt.intValue())) + : jsonPath("content[1].startAt").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.start.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.start.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.pause.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.pause.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.resume.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.resume.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.triggerNextGroup.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.triggerNextGroup.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.approve.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.approve.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.deny.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.deny.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.groups.href", startsWith(HREF_ROLLOUT_PREFIX)) + : jsonPath("content[1]._links.groups.href").doesNotExist()) + .andExpect(isFullRepresentation + ? jsonPath("$.content[1]._links.distributionset.href", + startsWith("http://localhost/rest/v1/distributionsets/")) + : jsonPath("content[1]._links.distributionset.href").doesNotExist()) + .andExpect(jsonPath("content[1]._links.self.href", startsWith(HREF_ROLLOUT_PREFIX))); } } 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 20ed1855d..1ec29635a 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 @@ -481,15 +481,15 @@ public abstract class JsonBuilder { public static String rollout(final String name, final String description, final int groupSize, final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) { - return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, null, - null, null); + return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, null, null, + null, null, null); } public static String rollout(final String name, final String description, final Integer groupSize, final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions, final String type) { - return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, type, null, - null); + return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, type, + null, null, null, null); } public static String rolloutWithGroups(final String name, final String description, final Integer groupSize, @@ -506,12 +506,13 @@ public abstract class JsonBuilder { final List rolloutGroupsJson = groups.stream().map(JsonBuilder::rolloutGroup) .collect(Collectors.toList()); return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, - rolloutGroupsJson, type, weight, confirmationRequired); + rolloutGroupsJson, type, weight, System.currentTimeMillis(), null, confirmationRequired); } 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 Boolean confirmationRequired) { + final List groupJsonList, final String type, final Integer weight, final Long startAt, final Long forceTime, + final Boolean confirmationRequired) { final JSONObject json = new JSONObject(); try { @@ -529,6 +530,14 @@ public abstract class JsonBuilder { json.put("weight", weight); } + if (startAt != null) { + json.put("startAt", startAt); + } + + if (forceTime != null) { + json.put("forcetime", forceTime); + } + if (confirmationRequired != null) { json.put("confirmationRequired", confirmationRequired); } diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java index ebf06972e..97d6ed458 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java @@ -130,6 +130,8 @@ public final class MgmtApiModelProperties { public static final String ROLLOUT_LINKS_DENY = "Link to deny a rollout"; public static final String ROLLOUT_LINKS_GROUPS = "Link to retrieve the groups a rollout"; public static final String ROLLOUT_START_ASYNC = "Start the rollout asynchronous"; + public static final String ROLLOUT_START_AT = "Start at timestamp of Rollout."; + public static final String RESULTING_ACTIONS_WEIGHT = "Weight of the resulting Actions"; diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/RolloutResourceDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/RolloutResourceDocumentationTest.java index 3d262695c..d5096edce 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/RolloutResourceDocumentationTest.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/RolloutResourceDocumentationTest.java @@ -137,12 +137,15 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati .attributes(key("value").value("['forced','soft','timeforced','downloadonly']"))); allFieldDescriptor.add( fieldWithPath(arrayPrefix + "totalTargets").description(MgmtApiModelProperties.ROLLOUT_TOTAL_TARGETS)); + allFieldDescriptor.add(fieldWithPath(arrayPrefix + "forcetime").description(MgmtApiModelProperties.FORCETIME)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.self").ignored()); if (withDetails) { allFieldDescriptor.add(fieldWithPath(arrayPrefix + "totalTargetsPerStatus") .description(MgmtApiModelProperties.ROLLOUT_TOTAL_TARGETS_PER_STATUS)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "totalGroups") .description(MgmtApiModelProperties.ROLLOUT_TOTAL_GROUPS)); + allFieldDescriptor.add(fieldWithPath(arrayPrefix + "startAt") + .description(MgmtApiModelProperties.ROLLOUT_START_AT)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.start") .description(MgmtApiModelProperties.ROLLOUT_LINKS_START_SYNC)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.pause") @@ -200,8 +203,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati mockMvc.perform( post(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING) - .content(JsonBuilder.rollout(name, description, groupSize, dsId, targetFilter, - rolloutGroupConditions, type)) + .content(JsonBuilder.rollout(name, description, groupSize, dsId, targetFilter, rolloutGroupConditions, null, type, + null, System.currentTimeMillis() + 2000, System.currentTimeMillis() + 3000, false)) .contentType(MediaTypes.HAL_JSON).accept(MediaTypes.HAL_JSON_VALUE)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andExpect(content().contentType(MediaTypes.HAL_JSON)) @@ -211,8 +214,11 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati .description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT) .attributes(key("value").value("0 - 1000")), requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME), + optionalRequestFieldWithPath("forcetime").description(MgmtApiModelProperties.FORCETIME), optionalRequestFieldWithPath("type").description(MgmtApiModelProperties.ROLLOUT_TYPE) .attributes(key("value").value("['soft', 'forced', 'timeforced', 'downloadonly']")), + optionalRequestFieldWithPath("startAt").description(MgmtApiModelProperties.ROLLOUT_START_AT) + .type(JsonFieldType.NUMBER), optionalRequestFieldWithPath("confirmationRequired") .description(MgmtApiModelProperties.ROLLOUT_CONFIRMATION_REQUIRED) .type(JsonFieldType.BOOLEAN.toString()), @@ -311,6 +317,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati .description(MgmtApiModelProperties.ROLLOUT_CONFIRMATION_REQUIRED), optionalRequestFieldWithPath("description") .description(ApiModelPropertiesGeneric.DESCRPTION), + optionalRequestFieldWithPath("startAt").description(MgmtApiModelProperties.ROLLOUT_START_AT) + .type(JsonFieldType.NUMBER), optionalRequestFieldWithPath("successCondition") .description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION), optionalRequestFieldWithPath("successCondition.condition") @@ -679,6 +687,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati if (isMultiAssignmentsEnabled()) { rolloutCreate.weight(400); } + rolloutCreate.forcedTime(System.currentTimeMillis() + 12345); final Rollout rollout = rolloutManagement.create(rolloutCreate, 5, false, new RolloutGroupConditionBuilder() .withDefaults().successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build()); @@ -686,7 +695,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati rolloutManagement.handleRollouts(); return rolloutManagement - .update(entityFactory.rollout().update(rollout.getId()).description("exampleDescription")); + .update(entityFactory.rollout().update(rollout.getId()).startAt(System.currentTimeMillis() + 1000).description("exampleDescription")); } }