Trigger next rollout group - backend and management API implementatio… (#1294)

* Trigger next rollout group - backend and management API implementations. Backend and management API tests.
* Trigger next rollout group - Fixed resource documentation test.
* Trigger next rollout group - Fixed resource documentation test.
* add rest docs
* Trigger next rollout group - UI changes. New button for trigger next rollout group in rollout view.
* add error test for rest api
* Trigger next rollout group - Added test for triggering next group for all rollout states.
* add confirm
* fix test
* replace DB calls
* fix translation
* fix error message

Signed-off-by: Dimitar Shterev <dimitar.shterev@bosch.io>
Signed-off-by: Stefan Klotz <stefan.klotz@bosch.io>
Co-authored-by: Stefan Klotz <stefan.klotz@bosch.io>
This commit is contained in:
Dimitar Shterev
2023-01-12 14:22:09 +02:00
committed by GitHub
parent ed1e7d8da2
commit 2db45a4cc5
15 changed files with 370 additions and 5 deletions

View File

@@ -236,4 +236,16 @@ public interface MgmtRolloutRestApi {
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam);
/**
* Handles the POST request to force trigger processing next group of a rollout even success threshold isn't yet met
*
* @param rolloutId
* the ID of the rollout to trigger next group.
* @return OK response (200). In case of any
* exception the corresponding errors occur.
*/
@PostMapping(value = "/{rolloutId}/triggerNextGroup", produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> triggerNextGroup(@PathVariable("rolloutId") Long rolloutId);
}

View File

@@ -95,6 +95,7 @@ final class MgmtRolloutMapper {
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId())).withRel("start"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).resume(rollout.getId())).withRel("resume"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).triggerNextGroup(rollout.getId())).withRel("triggerNextGroup"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).approve(rollout.getId(), null)).withRel("approve"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).deny(rollout.getId(), null)).withRel("deny"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroups(rollout.getId(),

View File

@@ -254,4 +254,10 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent());
return ResponseEntity.ok(new PagedList<>(rest, rolloutGroupTargets.getTotalElements()));
}
@Override
public ResponseEntity<Void> triggerNextGroup(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.triggerNextGroup(rolloutId);
return ResponseEntity.ok().build();
}
}

View File

@@ -27,6 +27,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;
import org.awaitility.Awaitility;
import org.awaitility.Duration;
@@ -36,13 +37,16 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
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.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.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
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.test.util.WithSpringAuthorityRule;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
@@ -52,6 +56,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.ResultMatcher;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
@@ -386,6 +391,8 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
.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.triggerNextGroup.href",
allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/triggerNextGroup"))))
.andExpect(jsonPath("$._links.groups.href",
allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups"))))
.andExpect(jsonPath("$.deleted", equalTo(false)));
@@ -1033,14 +1040,95 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
final String targetFilterQuery) {
final Rollout rollout = rolloutManagement.create(
entityFactory.rollout().create().name(name).set(distributionSetId).targetFilterQuery(targetFilterQuery),
amountGroups, new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
final Rollout rollout = createRolloutInCreatingSatate(name, amountGroups, distributionSetId, targetFilterQuery);
// Run here, because Scheduler is disabled during tests
rolloutManagement.handleRollouts();
return rolloutManagement.get(rollout.getId()).orElseThrow(NoSuchElementException::new);
}
private Rollout createRolloutInCreatingSatate(final String name, final int amountGroups,
final long distributionSetId, final String targetFilterQuery) {
return rolloutManagement.create(
entityFactory.rollout().create().name(name).set(distributionSetId).targetFilterQuery(targetFilterQuery),
amountGroups, new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
}
@Test
@Description("Trigger next rollout group")
void triggeringNextGroupRollout() throws Exception {
// setup
final int amountTargets = 20;
testdataFactory.createTargets(amountTargets, "rollout", "rollout");
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
rolloutManagement.start(rollout.getId());
rolloutManagement.handleRollouts();
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/triggerNextGroup", rollout.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
final List<RolloutGroupStatus> groupStatus = rolloutGroupManagement.findByRollout(PAGE, rollout.getId())
.getContent().stream().map(RolloutGroup::getStatus).collect(Collectors.toList());
assertThat(groupStatus).containsExactly(RolloutGroupStatus.RUNNING, RolloutGroupStatus.RUNNING,
RolloutGroupStatus.SCHEDULED, RolloutGroupStatus.SCHEDULED);
}
@Test
@Description("Trigger next rollout group if rollout is in wrong state")
void triggeringNextGroupRolloutWrongState() throws Exception {
final int amountTargets = 2;
final List<Target> targets = testdataFactory.createTargets(amountTargets, "rollout");
final DistributionSet dsA = testdataFactory.createDistributionSet("");
// CREATING state
final Rollout rollout = createRolloutInCreatingSatate("rollout1", 3, dsA.getId(), "controllerId==rollout*");
triggerNextGroupAndExpect(rollout, status().isBadRequest());
// READY state
rolloutManagement.handleRollouts();
triggerNextGroupAndExpect(rollout, status().isBadRequest());
// STARTING state
rolloutManagement.start(rollout.getId());
triggerNextGroupAndExpect(rollout, status().isBadRequest());
// RUNNING state
rolloutManagement.handleRollouts();
triggerNextGroupAndExpect(rollout, status().isOk());
// PAUSED state
rolloutManagement.pauseRollout(rollout.getId());
triggerNextGroupAndExpect(rollout, status().isBadRequest());
rolloutManagement.resumeRollout(rollout.getId());
triggerNextGroupAndExpect(rollout, status().isOk());
// last group already running
triggerNextGroupAndExpect(rollout, status().isBadRequest());
// FINISHED state
setTargetsStatus(targets, Status.FINISHED);
rolloutManagement.handleRollouts();
triggerNextGroupAndExpect(rollout, status().isBadRequest());
}
private void triggerNextGroupAndExpect(final Rollout rollout, final ResultMatcher expect) throws Exception {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/triggerNextGroup", rollout.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(expect);
}
private void setTargetsStatus(final List<Target> targets, final Status status) {
for (final Target target : targets) {
final Long action = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).toList().get(0)
.getId();
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action).status(status).message("test"));
}
}
}

View File

@@ -356,6 +356,39 @@ include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
== POST /rest/v1/rollouts/{rolloutId}/triggerNextGroup
=== Implementation Notes
Handles the POST request of triggering the next group of a rollout within Hawkbit. Required Permission: UPDATE_ROLLOUT
=== Trigger next group
==== CURL
include::{snippets}/rollouts/trigger-next-group/curl-request.adoc[]
==== Request URL
include::{snippets}/rollouts/trigger-next-group/http-request.adoc[]
==== Response example
include::{snippets}/rollouts/trigger-next-group/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
== DELETE /rest/v1/rollouts/{rolloutId}
=== Implementation Notes

View File

@@ -118,6 +118,7 @@ public final class MgmtApiModelProperties {
public static final String ROLLOUT_LINKS_START_SYNC = "Link to start the rollout in sync mode";
public static final String ROLLOUT_LINKS_START_ASYNC = "Link to start the rollout in async mode";
public static final String ROLLOUT_LINKS_PAUSE = "Link to pause a running rollout";
public static final String ROLLOUT_LINKS_TRIGGER_NEXT_GROUP = "Link for triggering next rollout group on a running rollout";
public static final String ROLLOUT_LINKS_RESUME = "Link to resume a paused rollout";
public static final String ROLLOUT_LINKS_APPROVE = "Link to approve a rollout";
public static final String ROLLOUT_LINKS_DENY = "Link to deny a rollout";

View File

@@ -147,6 +147,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
.description(MgmtApiModelProperties.ROLLOUT_LINKS_START_SYNC));
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.pause")
.description(MgmtApiModelProperties.ROLLOUT_LINKS_PAUSE));
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.triggerNextGroup")
.description(MgmtApiModelProperties.ROLLOUT_LINKS_TRIGGER_NEXT_GROUP));
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.resume")
.description(MgmtApiModelProperties.ROLLOUT_LINKS_RESUME));
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.groups")
@@ -444,6 +446,23 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
pathParameters(parameterWithName("rolloutId").description(ApiModelPropertiesGeneric.ITEM_ID))));
}
@Test
@Description("Handles the POST request of triggering the next group of a rollout. Required Permission: "
+ SpPermission.UPDATE_ROLLOUT)
public void triggerNextGroup() throws Exception {
final Rollout rollout = createRolloutEntity();
rolloutManagement.start(rollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.handleRollouts();
mockMvc.perform(
post(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}/triggerNextGroup", rollout.getId())
.accept(MediaTypes.HAL_JSON_VALUE))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()).andDo(this.document.document(
pathParameters(parameterWithName("rolloutId").description(ApiModelPropertiesGeneric.ITEM_ID))));
}
@Test
@Description("Handles the GET request of retrieving the deploy groups of a rollout. Required Permission: "
+ SpPermission.READ_ROLLOUT)