diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtAction.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtAction.java index 0941758fc..e37ddbe9a 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtAction.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtAction.java @@ -65,6 +65,12 @@ public class MgmtAction extends MgmtBaseEntity { @JsonProperty private MgmtMaintenanceWindow maintenanceWindow; + + @JsonProperty + private Long rollout; + + @JsonProperty + private String rolloutName; public MgmtMaintenanceWindow getMaintenanceWindow() { return maintenanceWindow; @@ -121,4 +127,21 @@ public class MgmtAction extends MgmtBaseEntity { public void setType(final String type) { this.type = type; } + + public Long getRollout() { + return rollout; + } + + public void setRollout(Long rollout) { + this.rollout = rollout; + } + + public String getRolloutName() { + return rolloutName; + } + + public void setRolloutName(String rolloutName) { + this.rolloutName = rolloutName; + } + } diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java index 5893eaba0..52a1a30ff 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java @@ -84,6 +84,10 @@ public final class MgmtRestConstants { * The target URL mapping, href link for canceled actions. */ public static final String TARGET_V1_ACTION_STATUS = "status"; + /** + * The target URL mapping, href link for a rollout. + */ + public static final String TARGET_V1_ROLLOUT = "rollout"; /** * The target URL mapping rest resource. diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java index 1409816c1..bc6162a14 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java @@ -28,6 +28,7 @@ import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi; import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionStatusFields; @@ -39,6 +40,7 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.PollStatus; +import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.rest.data.ResponseList; @@ -215,6 +217,12 @@ public final class MgmtTargetMapper { result.setStatus(MgmtAction.ACTION_FINISHED); } + Rollout rollout = action.getRollout(); + if (rollout != null) { + result.setRollout(rollout.getId()); + result.setRolloutName(rollout.getName()); + } + if (action.hasMaintenanceSchedule()) { final MgmtMaintenanceWindow maintenanceWindow = new MgmtMaintenanceWindow(); maintenanceWindow.setSchedule(action.getMaintenanceWindowSchedule()); @@ -248,6 +256,12 @@ public final class MgmtTargetMapper { ActionStatusFields.ID.getFieldName() + ":" + SortDirection.DESC)) .withRel(MgmtRestConstants.TARGET_V1_ACTION_STATUS)); + final Rollout rollout = action.getRollout(); + if (rollout != null) { + result.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRollout(rollout.getId())) + .withRel(MgmtRestConstants.TARGET_V1_ROLLOUT)); + } + return result; } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index bb4ec6cc0..d3aad4cae 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasItem; @@ -51,6 +52,7 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; 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.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetMetadata; @@ -2039,5 +2041,38 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest } } + + @Test + @Description("An action provides information of the rollout it was created for (if any).") + public void getActionWithRolloutInfo() throws Exception { + + // setup + final int amountTargets = 10; + final List targets = testdataFactory.createTargets(amountTargets, "trg", "trg"); + final DistributionSet ds = testdataFactory.createDistributionSet(""); + + final Rollout rollout = testdataFactory.createRolloutByVariables("My Rollout", "My Rollout Description", 1, + "name==trg*", ds, "50", "5"); + rolloutManagement.start(rollout.getId()); + rolloutManagement.handleRollouts(); + + // get all actions for the first target + final Target target = targets.get(0); + mvc.perform(get("/rest/v1/targets/{targetId}/actions", target.getControllerId())).andExpect(status().isOk()) + .andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath("content.[0].rollout", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("content.[0].rolloutName", equalTo(rollout.getName()))); + + // get the first action for the first target; + // verify that also the rollout link is present + final Slice action = deploymentManagement.findActionsByTarget(target.getControllerId(), + PageRequest.of(0, 100)); + assertThat(action.getContent()).hasSize(1); + mvc.perform(get("/rest/v1/targets/{targetId}/actions/{actionId}", target.getControllerId(), + action.getContent().get(0).getId())).andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath("$.rollout", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("$.rolloutName", equalTo(rollout.getName()))).andExpect(jsonPath( + "$._links.rollout.href", containsString("/rest/v1/rollouts/" + rollout.getId().intValue()))); + } } diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java index 7a44961e6..51d690623 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java @@ -19,6 +19,7 @@ import static org.springframework.restdocs.snippet.Attributes.key; import java.io.ByteArrayInputStream; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -33,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.test.TestConfiguration; @@ -161,28 +163,52 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync, final boolean timeforced, final DistributionSet distributionSet) { - return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null); + return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null, false); + } + + protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync, + final boolean timeforced, final DistributionSet distributionSet, final boolean createRollout) { + return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null, createRollout); } protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync, final boolean timeforced, final DistributionSet distributionSet, final String maintenanceWindowSchedule, - final String maintenanceWindowDuration, final String maintenanceWindowTimeZone) { + final String maintenanceWindowDuration, final String maintenanceWindowTimeZone, final boolean createRollout) { final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name) .status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name) .lastTargetQuery(System.currentTimeMillis())); - final DeploymentRequestBuilder deploymentRequestBuilder = DeploymentManagement - .deploymentRequest(savedTarget.getControllerId(), distributionSet.getId()) - .setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone); - if (timeforced) { - deploymentRequestBuilder.setActionType(ActionType.TIMEFORCED); - } - if (isMultiAssignmentsEnabled()) { - deploymentRequestBuilder.setWeight(600); - } - final List updatedTargets = makeAssignment(deploymentRequestBuilder.build()).getAssignedEntity() - .stream().map(Action::getTarget).collect(Collectors.toList()); + + final List updatedTargets; + if (createRollout) { + final Rollout rollout = testdataFactory.createRolloutByVariables("rollout", "rollout desc", 1, + "name==" + name, distributionSet, "50", "5", timeforced ? ActionType.TIMEFORCED : ActionType.FORCED, + isMultiAssignmentsEnabled() ? 600 : null); + + // start the rollout and handle it + rolloutManagement.start(rollout.getId()); + rolloutManagement.handleRollouts(); + + updatedTargets = Collections.singletonList(savedTarget); + + } else { + final DeploymentRequestBuilder deploymentRequestBuilder = DeploymentManagement + .deploymentRequest(savedTarget.getControllerId(), distributionSet.getId()) + .setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone); + + if (timeforced) { + deploymentRequestBuilder.setActionType(ActionType.TIMEFORCED); + } + + if (isMultiAssignmentsEnabled()) { + deploymentRequestBuilder.setWeight(600); + } + + updatedTargets = makeAssignment(deploymentRequestBuilder.build()).getAssignedEntity().stream() + .map(Action::getTarget).collect(Collectors.toList()); + } + if (inSync) { feedbackToByInSync(distributionSet); } 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 4bd414350..43e1a2abf 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 @@ -31,6 +31,7 @@ public final class MgmtApiModelProperties { public static final String LINK_TO_METADATA = "The link to the metadata."; public static final String LINK_TO_MANDATORY_SMT = "Link to mandatory software modules types in this distribution set type."; public static final String LINK_TO_OPTIONAL_SMT = "Link to optional software modules types in this distribution set type."; + public static final String LINK_TO_ROLLOUT = "The link to the rollout."; // software module types public static final String SMT_TYPE = "The type of the software module identified by its key."; @@ -143,6 +144,10 @@ public final class MgmtApiModelProperties { public static final String ACTION_WEIGHT = "Weight of the action showing the importance of the update."; + public static final String ACTION_ROLLOUT = "The ID of the rollout this action was created for."; + + public static final String ACTION_ROLLOUT_NAME = "The name of the rollout this action was created for."; + public static final String IP_ADDRESS = "Last known IP address of the target. Only presented if IP address of the target itself is known (connected directly through DDI API)."; public static final String ADDRESS = "The last known address URI of the target. Includes information of the target is connected either directly (DDI) through HTTP or indirectly (DMF) through amqp."; diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java index 3519f4397..69c60f4c8 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java @@ -201,7 +201,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio @Description("Handles the GET request of retrieving the full action history of a specific target. Required Permission: READ_TARGET.") public void getActionsFromTarget() throws Exception { enableMultiAssignments(); - generateActionForTarget(targetId); + generateRolloutActionForTarget(targetId); mockMvc.perform( get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + MgmtRestConstants.TARGET_V1_ACTIONS, @@ -228,7 +228,10 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio .attributes(key("value").value("['finished', 'pending']")), fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION), fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID), - fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT)))); + fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT), + fieldWithPath("content[].rollout").description(MgmtApiModelProperties.ACTION_ROLLOUT), + fieldWithPath("content[].rolloutName") + .description(MgmtApiModelProperties.ACTION_ROLLOUT_NAME)))); } @Test @@ -323,7 +326,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio @Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.") public void getActionFromTarget() throws Exception { enableMultiAssignments(); - final Action action = generateActionForTarget(targetId, true, true); + final Action action = generateRolloutActionForTarget(targetId, true, true); assertThat(deploymentManagement.findAction(action.getId()).get().getActionType()) .isEqualTo(ActionType.TIMEFORCED); @@ -349,10 +352,15 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio .type("String"), fieldWithPath("status").description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS) .attributes(key("value").value("['finished', 'pending']")), + fieldWithPath("rollout").description(MgmtApiModelProperties.ACTION_ROLLOUT), + fieldWithPath("rolloutName") + .description(MgmtApiModelProperties.ACTION_ROLLOUT_NAME), fieldWithPath("_links.self").ignored(), fieldWithPath("_links.distributionset").description(MgmtApiModelProperties.LINK_TO_DS), fieldWithPath("_links.status") - .description(MgmtApiModelProperties.LINKS_ACTION_STATUSES)))); + .description(MgmtApiModelProperties.LINKS_ACTION_STATUSES), + fieldWithPath("_links.rollout") + .description(MgmtApiModelProperties.LINK_TO_ROLLOUT)))); } @Test @@ -820,10 +828,19 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio return generateActionForTarget(knownControllerId, true, false, null, null, null); } + private Action generateRolloutActionForTarget(final String knownControllerId) throws Exception { + return generateActionForTarget(knownControllerId, true, false, null, null, null, true); + } + private Action generateActionForTarget(final String knownControllerId, final boolean inSync) throws Exception { return generateActionForTarget(knownControllerId, inSync, false, null, null, null); } + private Action generateRolloutActionForTarget(final String knownControllerId, final boolean inSync, + final boolean timeforced) throws Exception { + return generateActionForTarget(knownControllerId, inSync, timeforced, null, null, null, true); + } + private Action generateActionForTarget(final String knownControllerId, final boolean inSync, final boolean timeforced) throws Exception { return generateActionForTarget(knownControllerId, inSync, timeforced, null, null, null); @@ -832,14 +849,22 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio private Action generateActionForTarget(final String knownControllerId, final boolean inSync, final boolean timeforced, final String maintenanceWindowSchedule, final String maintenanceWindowDuration, final String maintenanceWindowTimeZone) throws Exception { + return generateActionForTarget(knownControllerId, inSync, timeforced, maintenanceWindowSchedule, + maintenanceWindowDuration, maintenanceWindowTimeZone, false); + } + + private Action generateActionForTarget(final String knownControllerId, final boolean inSync, + final boolean timeforced, final String maintenanceWindowSchedule, final String maintenanceWindowDuration, + final String maintenanceWindowTimeZone, final boolean createRollout) throws Exception { final PageRequest pageRequest = PageRequest.of(0, 1, Direction.ASC, ActionStatusFields.ID.getFieldName()); createTargetByGivenNameWithAttributes(knownControllerId, inSync, timeforced, createDistributionSet(), - maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone); + maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone, createRollout); final List actions = deploymentManagement.findActionsAll(pageRequest).getContent(); assertThat(actions).hasSize(1); return actions.get(0); } + }