Management API: Expose lastStatusCode property of action entities (#1313)

* Enhance Mgmt REST API to expose lastStatusCode property of actions
* Add unit test
This commit is contained in:
Stefan Behl
2023-01-30 11:10:48 +01:00
committed by GitHub
parent 21f1569881
commit 4bf0f878be
6 changed files with 124 additions and 41 deletions

View File

@@ -75,6 +75,9 @@ public class MgmtAction extends MgmtBaseEntity {
@JsonProperty
private String rolloutName;
@JsonProperty
private Integer lastStatusCode;
public MgmtMaintenanceWindow getMaintenanceWindow() {
return maintenanceWindow;
}
@@ -155,4 +158,12 @@ public class MgmtAction extends MgmtBaseEntity {
this.detailStatus = detailStatus;
}
public Integer getLastStatusCode() {
return lastStatusCode;
}
public void setLastStatusCode(final Integer lastStatusCode) {
this.lastStatusCode = lastStatusCode;
}
}

View File

@@ -40,8 +40,8 @@ import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -257,6 +257,10 @@ public final class MgmtTargetMapper {
result.setDetailStatus(action.getStatus().toString().toLowerCase());
action.getLastActionStatusCode().ifPresent(statusCode -> {
result.setLastStatusCode(statusCode);
});
final Rollout rollout = action.getRollout();
if (rollout != null) {
result.setRollout(rollout.getId());

View File

@@ -50,6 +50,7 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action;
@@ -141,8 +142,8 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
final int limitSize = 2;
final String knownTargetId = "targetId";
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(actions.get(0).getId()).status(Status.FINISHED).message("test"));
assertThat(actions).hasSize(2);
updateActionStatus(actions.get(0), Status.FINISHED, null, "test");
final PageRequest pageRequest = PageRequest.of(0, 1000, Direction.ASC, ActionFields.ID.getFieldName());
final Action action = deploymentManagement.findActionsByTarget(knownTargetId, pageRequest).getContent().get(0);
@@ -1353,7 +1354,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
if (confirmationFlowActive) {
enableConfirmationFlow();
}
final JSONObject jsonPayload = new JSONObject();
jsonPayload.put("id", set.getId());
if (confirmationRequired != null) {
@@ -2050,9 +2051,9 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, 98);
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
}
@Test
@@ -2207,7 +2208,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Description("Ensures that a post request for creating target with target type works.")
void createTargetWithExistingTargetType() throws Exception {
// create target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 1);
final List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 1);
assertThat(targetTypes).hasSize(1);
final Target target = entityFactory.target().create().controllerId("targetcontroller").name("testtarget")
@@ -2229,11 +2230,11 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Description("Ensures that a put request for updating targets with target type works.")
void updateTargetTypeInTarget() throws Exception {
// create target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 2);
final List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 2);
assertThat(targetTypes).hasSize(2);
String controllerId = "targetcontroller";
Target target = testdataFactory.createTarget(controllerId, "testtarget", targetTypes.get(0).getId());
final String controllerId = "targetcontroller";
final Target target = testdataFactory.createTarget(controllerId, "testtarget", targetTypes.get(0).getId());
assertThat(target).isNotNull();
assertThat(target.getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
@@ -2250,13 +2251,14 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test
@Description("Ensures that a post request for creating targets with unknown target type fails.")
void addingNonExistingTargetTypeInTargetShouldFail() throws Exception {
long unknownTargetTypeId = 999;
String errorMsg = String.format("TargetType with given identifier {%s} does not exist.", unknownTargetTypeId);
final long unknownTargetTypeId = 999;
final String errorMsg = String.format("TargetType with given identifier {%s} does not exist.",
unknownTargetTypeId);
Optional<TargetType> targetType = targetTypeManagement.get(unknownTargetTypeId);
final Optional<TargetType> targetType = targetTypeManagement.get(unknownTargetTypeId);
assertThat(targetType).isNotPresent();
String controllerId = "targetcontroller";
final String controllerId = "targetcontroller";
final Target target = entityFactory.target().create().controllerId(controllerId).name("testtarget").build();
final String targetList = JsonBuilder.targets(Collections.singletonList(target), false, unknownTargetTypeId);
@@ -2271,12 +2273,12 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Description("Ensures that a post request for assign target type to target works.")
void assignTargetTypeToTarget() throws Exception {
// create target type
TargetType targetType = testdataFactory.findOrCreateTargetType("targettype");
final TargetType targetType = testdataFactory.findOrCreateTargetType("targettype");
assertThat(targetType).isNotNull();
// create target
String targetControllerId = "targetcontroller";
Target target = testdataFactory.createTarget(targetControllerId, "testtarget");
final String targetControllerId = "targetcontroller";
final Target target = testdataFactory.createTarget(targetControllerId, "testtarget");
assertThat(target).isNotNull();
// assign target type over rest resource
@@ -2292,11 +2294,11 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Description("Ensures that a post request for assign a invalid target type to target fails.")
void assignInvalidTargetTypeToTargetFails() throws Exception {
// Invalid target type ID
long invalidTargetTypeId = 999;
final long invalidTargetTypeId = 999;
// create target
String targetControllerId = "targetcontroller";
Target target = testdataFactory.createTarget(targetControllerId, "testtarget");
final String targetControllerId = "targetcontroller";
final Target target = testdataFactory.createTarget(targetControllerId, "testtarget");
assertThat(target).isNotNull();
// assign invalid target type over rest resource
@@ -2304,7 +2306,8 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.content("{\"id\":" + invalidTargetTypeId + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// verify response json exception message if body does not include id field
// verify response json exception message if body does not include id
// field
final MvcResult mvcResult = mvc
.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
.content("{\"unknownfield\":" + invalidTargetTypeId + "}")
@@ -2321,11 +2324,12 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Description("Ensures that a delete request for unassign target type from target works.")
void unassignTargetTypeFromTarget() throws Exception {
// create target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 1);
final List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 1);
assertThat(targetTypes).hasSize(1);
String targetControllerId = "targetcontroller";
Target target = testdataFactory.createTarget(targetControllerId, "testtarget", targetTypes.get(0).getId());
final String targetControllerId = "targetcontroller";
final Target target = testdataFactory.createTarget(targetControllerId, "testtarget",
targetTypes.get(0).getId());
assertThat(target).isNotNull();
assertThat(target.getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
@@ -2384,16 +2388,15 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
// GET with all possible responses
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM,
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("active", equalTo(Boolean.TRUE)))
.andExpect(initiator == null ? jsonPath("initiator").doesNotExist()
: jsonPath("initiator", equalTo(initiator)))
.andExpect(remark == null ? jsonPath("remark").doesNotExist() : jsonPath("remark", equalTo(remark)))
.andExpect(jsonPath("_links.deactivate").exists())
.andExpect(jsonPath("_links.activate").doesNotExist());
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("active", equalTo(Boolean.TRUE)))
.andExpect(initiator == null ? jsonPath("initiator").doesNotExist()
: jsonPath("initiator", equalTo(initiator)))
.andExpect(remark == null ? jsonPath("remark").doesNotExist() : jsonPath("remark", equalTo(remark)))
.andExpect(jsonPath("_links.deactivate").exists())
.andExpect(jsonPath("_links.activate").doesNotExist());
}
@Test
void getAutoConfirmStateFromTargetsEndpoint() throws Exception {
final String knownTargetId = "targetId";
@@ -2453,6 +2456,64 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(jsonPath("autoConfirmActive").exists()).andExpect(jsonPath("_links.autoConfirm").exists());
}
@Test
@Description("Verifies that the status code that was reported in the last action status update is correctly exposed via the action.")
void lastActionStatusCode() throws Exception {
// prepare test
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final Target target = testdataFactory.createTarget("knownTargetId");
final Action action = getFirstAssignedAction(assignDistributionSet(dsA, Collections.singletonList(target)));
// no status update yet -> no status code
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/actions/{actionId}",
target.getControllerId(), action.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("lastStatusCode").doesNotExist());
// update action status with status code
updateActionStatus(action, Status.RUNNING, 100);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/actions/{actionId}",
target.getControllerId(), action.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("lastStatusCode", equalTo(100)))
.andExpect(jsonPath("detailStatus", equalTo("running")));
// update action status without a status code
updateActionStatus(action, Status.RUNNING, null);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/actions/{actionId}",
target.getControllerId(), action.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("lastStatusCode").doesNotExist())
.andExpect(jsonPath("detailStatus", equalTo("running")));
// update action status with status code
updateActionStatus(action, Status.ERROR, 432);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/actions/{actionId}",
target.getControllerId(), action.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("lastStatusCode", equalTo(432)))
.andExpect(jsonPath("detailStatus", equalTo("error")));
}
private Action updateActionStatus(final Action action, final Status status, final Integer statusCode) {
return updateActionStatus(action, status, statusCode, null);
}
private Action updateActionStatus(final Action action, final Status status, final Integer statusCode,
final String message) {
assertThat(action).isNotNull();
assertThat(status).isNotNull();
final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId());
actionStatus.status(status);
if (statusCode != null) {
actionStatus.code(statusCode);
}
if (message != null) {
actionStatus.message(message);
}
return controllerManagement.addUpdateActionStatus(actionStatus);
}
private static Stream<Arguments> possibleActiveStates() {
return Stream.of(Arguments.of("someInitiator", "someRemark"), Arguments.of(null, "someRemark"),
Arguments.of("someInitiator", null), Arguments.of(null, null));

View File

@@ -162,6 +162,8 @@ public final class MgmtApiModelProperties {
public static final String ACTION_STATUS_CODE = "(Optional) Code provided by the device related to the status.";
public static final String ACTION_LAST_STATUS_CODE = "(Optional) Code provided as part of the last status update that was sent by the device.";
public static final String ACTION_STATUS_LIST = "List of action status.";
public static final String ACTION_EXECUTION_STATUS = "Status of action.";

View File

@@ -52,7 +52,8 @@ public class ActionResourceDocumentationTest extends AbstractApiRestDocumentatio
@Description("Handles the GET request of retrieving all actions. Required Permission: READ_TARGET.")
public void getActions() throws Exception {
enableMultiAssignments();
generateRolloutActionForTarget(targetId);
final Action action = generateRolloutActionForTarget(targetId);
provideCodeFeedback(action, 200);
mockMvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING)).andExpect(status().isOk())
.andDo(MockMvcResultPrinter.print())
@@ -74,6 +75,8 @@ public class ActionResourceDocumentationTest extends AbstractApiRestDocumentatio
fieldWithPath("content[].detailStatus").description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
.attributes(key("value").value(
"['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
optionalRequestFieldWithPath("content[].lastStatusCode")
.description(MgmtApiModelProperties.ACTION_LAST_STATUS_CODE).type("Integer"),
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT),

View File

@@ -107,8 +107,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
fieldWithPath("content[].autoConfirmActive")
.description(MgmtApiModelProperties.AUTO_CONFIRM_ACTIVE),
fieldWithPath("content[].installedAt").description(MgmtApiModelProperties.INSTALLED_AT),
fieldWithPath("content[].lastModifiedAt").description(
ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
fieldWithPath("content[].lastModifiedAt")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
fieldWithPath("content[].lastModifiedBy")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY).type("String"),
fieldWithPath("content[].ipAddress").description(MgmtApiModelProperties.IP_ADDRESS)
@@ -362,6 +362,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
public void getActionFromTarget() throws Exception {
enableMultiAssignments();
final Action action = generateRolloutActionForTarget(targetId, true, true);
provideCodeFeedback(action, 200);
assertThat(deploymentManagement.findAction(action.getId()).get().getActionType())
.isEqualTo(ActionType.TIMEFORCED);
@@ -390,6 +392,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
fieldWithPath("detailStatus").description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
.attributes(key("value").value(
"['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
optionalRequestFieldWithPath("lastStatusCode")
.description(MgmtApiModelProperties.ACTION_LAST_STATUS_CODE).type("Integer"),
fieldWithPath("rollout").description(MgmtApiModelProperties.ACTION_ROLLOUT),
fieldWithPath("rolloutName").description(MgmtApiModelProperties.ACTION_ROLLOUT_NAME),
fieldWithPath("_links.self").ignored(),
@@ -406,6 +410,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
enableMultiAssignments();
final Action action = generateActionForTarget(targetId, true, true, getTestSchedule(2), getTestDuration(1),
getTestTimeZone());
provideCodeFeedback(action, 200);
mockMvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/{actionId}", targetId, action.getId()))
@@ -432,6 +437,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
fieldWithPath("detailStatus").description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
.attributes(key("value").value(
"['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
optionalRequestFieldWithPath("lastStatusCode")
.description(MgmtApiModelProperties.ACTION_LAST_STATUS_CODE).type("Integer"),
fieldWithPath("maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
fieldWithPath("maintenanceWindow.schedule")
@@ -1005,11 +1012,6 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
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);
}
private Action generateActionForTarget(final String knownControllerId, final boolean inSync,
final boolean timeforced, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) throws Exception {