diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index fdeb16205..25e6e0a4d 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -355,6 +355,11 @@ public class AmqpMessageHandlerService extends BaseAmqpService { final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status) .messages(messages); + actionUpdateStatus.getCode().ifPresent(code -> { + actionStatus.code(code); + actionStatus.message("Device reported status code: " + code); + }); + final Action updatedAction = (Status.CANCELED == status) ? controllerManagement.addCancelActionStatus(actionStatus) : controllerManagement.addUpdateActionStatus(actionStatus); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 0688f685f..83e9a16aa 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -46,6 +46,8 @@ import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; +import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder; +import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -543,7 +545,7 @@ public class AmqpMessageHandlerServiceTest { } @Test - @Description("Tests TODO") + @Description("Test next update is provided on finished action") public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException { // Mock @@ -570,7 +572,7 @@ public class AmqpMessageHandlerServiceTest { final ArgumentCaptor targetCaptor = ArgumentCaptor.forClass(Target.class); verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(actionPropertiesCaptor.capture(), - targetCaptor.capture(), any(Map.class)); + targetCaptor.capture(), any(Map.class)); final ActionProperties actionProperties = actionPropertiesCaptor.getValue(); assertThat(actionProperties).isNotNull(); assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT"); @@ -578,6 +580,42 @@ public class AmqpMessageHandlerServiceTest { assertThat(actionProperties.getId()).as("event has wrong action id").isEqualTo(22L); } + @Test + @Description("Test feedback code is persisted in messages when provided with DmfActionUpdateStatus") + public void feedBackCodeIsPersistedInMessages() throws IllegalAccessException { + + // Mock + final Action action = createActionWithTarget(22L, Status.FINISHED); + when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action)); + when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action); + final ActionStatusBuilder builder = new JpaActionStatusBuilder(); + when(entityFactoryMock.actionStatus()).thenReturn(builder); + // for the test the same action can be used + when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.of(action)); + + final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT); + messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); + + final DmfActionUpdateStatus actionUpdateStatus = new DmfActionUpdateStatus(23L, DmfActionStatus.RUNNING); + actionUpdateStatus.setSoftwareModuleId(Long.valueOf(2)); + actionUpdateStatus.setCode(12); + + final Message message = createMessage(actionUpdateStatus, messageProperties); + + // test + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST); + + final ArgumentCaptor actionPropertiesCaptor = ArgumentCaptor + .forClass(ActionStatusCreate.class); + + verify(controllerManagementMock, times(1)).addUpdateActionStatus(actionPropertiesCaptor.capture()); + + final JpaActionStatus jpaActionStatus = (JpaActionStatus) actionPropertiesCaptor.getValue().build(); + assertThat(jpaActionStatus.getCode()).as("Action status for reported code is missing").contains(12); + assertThat(jpaActionStatus.getMessages()).as("Action status message for reported code is missing") + .contains("Device reported status code: 12"); + } + private DmfActionUpdateStatus createActionUpdateStatus(final DmfActionStatus status) { return createActionUpdateStatus(status, 2L); } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java index 748d4667f..b73b5c74e 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java @@ -105,6 +105,7 @@ public class BaseAmqpServiceTest { private DmfActionUpdateStatus createActionStatus() { final DmfActionUpdateStatus actionUpdateStatus = new DmfActionUpdateStatus(1L, DmfActionStatus.RUNNING); + actionUpdateStatus.setCode(2); actionUpdateStatus.setSoftwareModuleId(2L); actionUpdateStatus.addMessage("Message 1"); actionUpdateStatus.addMessage("Message 2"); diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionUpdateStatus.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionUpdateStatus.java index 1b6c681e8..9202ccaf6 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionUpdateStatus.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionUpdateStatus.java @@ -12,7 +12,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -31,6 +33,9 @@ public class DmfActionUpdateStatus { @JsonProperty private Long softwareModuleId; + @JsonProperty + private Integer code; + @JsonProperty private List message; @@ -56,6 +61,15 @@ public class DmfActionUpdateStatus { return actionStatus; } + @JsonIgnore + public Optional getCode() { + return Optional.ofNullable(code); + } + + public void setCode(final Integer code) { + this.code = code; + } + public List getMessage() { if (message == null) { return Collections.emptyList(); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java index ff328a0b3..b3973dd87 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java @@ -37,6 +37,8 @@ public interface ActionStatusCreate { */ ActionStatusCreate occurredAt(long occurredAt); + ActionStatusCreate code(int code); + /** * @param messages * for {@link ActionStatus#getMessages()} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java index 72a455648..c7f6416f0 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.repository.model; +import java.util.Optional; import java.util.concurrent.TimeUnit; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -39,4 +40,6 @@ public interface ActionStatus extends TenantAwareBaseEntity { */ Status getStatus(); + Optional getCode(); + } diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractActionStatusCreate.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractActionStatusCreate.java index d313a0a5c..7dd78c563 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractActionStatusCreate.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractActionStatusCreate.java @@ -25,9 +25,13 @@ import org.springframework.util.StringUtils; * update or create builder interface */ public abstract class AbstractActionStatusCreate { + protected Status status; + protected Long occurredAt; + protected Integer code; + protected List<@ValidString String> messages; protected Long actionId; @@ -48,6 +52,12 @@ public abstract class AbstractActionStatusCreate { return (T) this; } + public T code(final int code) { + this.code = code; + + return (T) this; + } + public T messages(final Collection messages) { if (this.messages == null) { this.messages = messages.stream().map(StringUtils::trimWhitespace).collect(Collectors.toList()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaActionStatusCreate.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaActionStatusCreate.java index 6132988b2..2af0b6479 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaActionStatusCreate.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaActionStatusCreate.java @@ -29,6 +29,9 @@ public class JpaActionStatusCreate extends AbstractActionStatusCreate messages; + @Column(name = "code", nullable = true, updatable = false) + private Integer code; + /** * Creates a new {@link ActionStatus} object. * @@ -184,4 +188,11 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements this.status = status; } + public Optional getCode() { + return Optional.ofNullable(code); + } + + public void setCode(final Integer code) { + this.code = code; + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_23__add_action_status_code___DB2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_23__add_action_status_code___DB2.sql new file mode 100644 index 000000000..c88711e47 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_23__add_action_status_code___DB2.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action_status ADD COLUMN code INTEGER; +CREATE INDEX sp_idx_action_status_03 ON sp_action_status (tenant, code); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_23__add_action_status_code___H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_23__add_action_status_code___H2.sql new file mode 100644 index 000000000..2e4caf07a --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_23__add_action_status_code___H2.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action_status ADD column code integer; +CREATE INDEX sp_idx_action_status_03 ON sp_action_status (tenant, code); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_23__add_action_status_code___MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_23__add_action_status_code___MYSQL.sql new file mode 100644 index 000000000..b5640ddec --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_23__add_action_status_code___MYSQL.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action_status ADD COLUMN code integer; +CREATE INDEX sp_idx_action_status_03 ON sp_action_status (tenant, code); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_23__add_action_status_code___SQL_SERVER.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_23__add_action_status_code___SQL_SERVER.sql new file mode 100644 index 000000000..38a6aeaab --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_23__add_action_status_code___SQL_SERVER.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action_status ADD code INT; +CREATE INDEX sp_idx_action_status_03 ON sp_action_status (tenant, code); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java index 4af1ea021..01a503791 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java @@ -47,7 +47,6 @@ import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; -import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; diff --git a/hawkbit-rest/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiStatus.java b/hawkbit-rest/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiStatus.java index c355d942e..e592e4507 100644 --- a/hawkbit-rest/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiStatus.java +++ b/hawkbit-rest/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiStatus.java @@ -10,6 +10,8 @@ package org.eclipse.hawkbit.ddi.json.model; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Optional; import javax.validation.Valid; import javax.validation.constraints.NotNull; @@ -33,6 +35,8 @@ public class DdiStatus { @Valid private final DdiResult result; + private final Integer code; + private final List details; /** @@ -42,14 +46,18 @@ public class DdiStatus { * status * @param result * information + * @param code + * as optional code (can be null) * @param details * as optional addition */ @JsonCreator public DdiStatus(@JsonProperty("execution") final ExecutionStatus execution, - @JsonProperty("result") final DdiResult result, @JsonProperty("details") final List details) { + @JsonProperty("result") final DdiResult result, @JsonProperty("code") final Integer code, + @JsonProperty("details") final List details) { this.execution = execution; this.result = result; + this.code = code; this.details = details; } @@ -69,6 +77,10 @@ public class DdiStatus { return Collections.unmodifiableList(details); } + public Integer getCode() { + return code; + } + /** * The element status contains information about the execution of the * operation. @@ -129,7 +141,8 @@ public class DdiStatus { @Override public String toString() { - return "Status [execution=" + execution + ", result=" + result + ", details=" + details + "]"; + return "Status [execution=" + execution + ", result=" + result + ", code=" + + code + ", details=" + details + "]"; } } diff --git a/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiActionFeedbackTest.java b/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiActionFeedbackTest.java index 0d67fa5ab..c6b208ec0 100644 --- a/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiActionFeedbackTest.java +++ b/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiActionFeedbackTest.java @@ -39,7 +39,7 @@ public class DdiActionFeedbackTest { public void shouldSerializeAndDeserializeObject() throws IOException { // Setup final String time = Instant.now().toString(); - final DdiStatus ddiStatus = new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, null, Lists.emptyList()); + final DdiStatus ddiStatus = new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, null, null, Lists.emptyList()); final DdiActionFeedback ddiActionFeedback = new DdiActionFeedback(time, ddiStatus); // Test diff --git a/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiStatusTest.java b/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiStatusTest.java index 1f9842f81..4c7be4b37 100644 --- a/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiStatusTest.java +++ b/hawkbit-rest/hawkbit-ddi-api/src/test/java/org/eclipse/hawkbit/ddi/json/model/DdiStatusTest.java @@ -16,6 +16,7 @@ import static org.eclipse.hawkbit.ddi.json.model.DdiStatus.ExecutionStatus.PROCE import java.io.IOException; import java.util.Collections; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -25,6 +26,9 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException; import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Story; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Test serializability of DDI api model 'DdiStatus' @@ -35,14 +39,10 @@ public class DdiStatusTest { private ObjectMapper mapper = new ObjectMapper(); - @Test + @ParameterizedTest + @MethodSource("ddiStatusPossibilities") @Description("Verify the correct serialization and deserialization of the model") - public void shouldSerializeAndDeserializeObject() throws IOException { - // Setup - DdiProgress ddiProgress = new DdiProgress(30, 100); - DdiResult ddiResult = new DdiResult(NONE, ddiProgress); - DdiStatus ddiStatus = new DdiStatus(PROCEEDING, ddiResult, Collections.emptyList()); - + public void shouldSerializeAndDeserializeObject(final DdiResult ddiResult, final DdiStatus ddiStatus) throws IOException { // Test String serializedDdiStatus = mapper.writeValueAsString(ddiStatus); DdiStatus deserializedDdiStatus = mapper.readValue(serializedDdiStatus, DdiStatus.class); @@ -55,19 +55,47 @@ public class DdiStatusTest { ddiStatus.getResult().getProgress().getCnt()); assertThat(deserializedDdiStatus.getResult().getProgress().getOf()).isEqualTo( ddiStatus.getResult().getProgress().getOf()); + assertThat(deserializedDdiStatus.getDetails()).isEqualTo(ddiStatus.getDetails()); + } + + private static Stream ddiStatusPossibilities(){ + final DdiProgress ddiProgress = new DdiProgress(30, 100); + final DdiResult ddiResult = new DdiResult(NONE, ddiProgress); + return Stream.of( + Arguments.of(ddiResult, new DdiStatus(PROCEEDING, ddiResult, null, Collections.emptyList())), + Arguments.of(ddiResult, new DdiStatus(PROCEEDING, ddiResult, null, Collections.singletonList("testMessage"))), + Arguments.of(ddiResult, new DdiStatus(PROCEEDING, ddiResult, 12, Collections.emptyList()))); } @Test @Description("Verify the correct deserialization of a model with a additional unknown property") public void shouldDeserializeObjectWithUnknownProperty() throws IOException { + // Setup + final String serializedDdiStatus = "{\"execution\":\"proceeding\",\"result\":{\"finished\":\"none\"," + + "\"progress\":{\"cnt\":30,\"of\":100}},\"details\":[],\"unknownProperty\":\"test\"}"; + + // Test + final DdiStatus ddiStatus = mapper.readValue(serializedDdiStatus, DdiStatus.class); + + assertThat(ddiStatus.getExecution()).isEqualTo(PROCEEDING); + assertThat(ddiStatus.getCode()).isNull(); + assertThat(ddiStatus.getResult().getFinished()).isEqualTo(NONE); + assertThat(ddiStatus.getResult().getProgress().getCnt()).isEqualTo(30); + assertThat(ddiStatus.getResult().getProgress().getOf()).isEqualTo(100); + } + + @Test + @Description("Verify the correct deserialization of a model with a provided code (optional)") + public void shouldDeserializeObjectWithOptionalCode() throws IOException { // Setup String serializedDdiStatus = "{\"execution\":\"proceeding\",\"result\":{\"finished\":\"none\"," - + "\"progress\":{\"cnt\":30,\"of\":100}},\"details\":[],\"unknownProperty\":\"test\"}"; + + "\"progress\":{\"cnt\":30,\"of\":100}},\"code\": 12,\"details\":[]}"; // Test DdiStatus ddiStatus = mapper.readValue(serializedDdiStatus, DdiStatus.class); assertThat(ddiStatus.getExecution()).isEqualTo(PROCEEDING); + assertThat(ddiStatus.getCode()).isEqualTo(12); assertThat(ddiStatus.getResult().getFinished()).isEqualTo(NONE); assertThat(ddiStatus.getResult().getProgress().getCnt()).isEqualTo(30); assertThat(ddiStatus.getResult().getProgress().getOf()).isEqualTo(100); diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 4c83e649e..aba695745 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -324,7 +324,6 @@ public class DdiRootController implements DdiRootControllerRestApi { final Target target = findTarget(controllerId); final Action action = findActionForTarget(actionId, target); - if (!action.isActive()) { LOG.warn("Updating action {} with feedback {} not possible since action not active anymore.", action.getId(), feedback.getStatus()); @@ -340,12 +339,20 @@ public class DdiRootController implements DdiRootControllerRestApi { private ActionStatusCreate generateUpdateStatus(final DdiActionFeedback feedback, final String controllerId, final Long actionId) { + final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId); + final List messages = new ArrayList<>(); if (!CollectionUtils.isEmpty(feedback.getStatus().getDetails())) { messages.addAll(feedback.getStatus().getDetails()); } + final Integer code = feedback.getStatus().getCode(); + if (code != null) { + actionStatusCreate.code(code); + messages.add("Device reported status code: " + code); + } + final Status status; switch (feedback.getStatus().getExecution()) { case CANCELED: @@ -380,7 +387,7 @@ public class DdiRootController implements DdiRootControllerRestApi { break; } - return entityFactory.actionStatus().create(actionId).status(status).messages(messages); + return actionStatusCreate.status(status).messages(messages); } private static void addMessageIfEmpty(final String text, final List messages) { @@ -431,7 +438,6 @@ public class DdiRootController implements DdiRootControllerRestApi { final Target target = findTarget(controllerId); final Action action = findActionForTarget(actionId, target); - if (action.isCancelingOrCanceled()) { final DdiCancel cancel = new DdiCancel(String.valueOf(action.getId()), new DdiCancelActionToStop(String.valueOf(action.getId()))); @@ -507,6 +513,8 @@ public class DdiRootController implements DdiRootControllerRestApi { private static ActionStatusCreate generateActionCancelStatus(final DdiActionFeedback feedback, final Target target, final Long actionId, final EntityFactory entityFactory) { + final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId); + final List messages = new ArrayList<>(); final Status status; switch (feedback.getStatus().getExecution()) { @@ -531,7 +539,13 @@ public class DdiRootController implements DdiRootControllerRestApi { messages.addAll(feedback.getStatus().getDetails()); } - return entityFactory.actionStatus().create(actionId).status(status).messages(messages); + final Integer code = feedback.getStatus().getCode(); + if (code != null) { + actionStatusCreate.code(code); + messages.add("Device reported status code: " + code); + } + + return actionStatusCreate.status(status).messages(messages); } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java index 7be63c876..ce025a865 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java @@ -323,13 +323,18 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat protected String getJsonActionFeedback(final DdiStatus.ExecutionStatus executionStatus, final DdiResult ddiResult, final List messages) throws JsonProcessingException { - final DdiStatus ddiStatus = new DdiStatus(executionStatus, ddiResult, messages); + final DdiStatus ddiStatus = new DdiStatus(executionStatus, ddiResult, null, messages); return objectMapper.writeValueAsString(new DdiActionFeedback(Instant.now().toString(), ddiStatus)); } protected String getJsonActionFeedback(final DdiStatus.ExecutionStatus executionStatus, final DdiResult.FinalResult finalResult, final List messages) throws JsonProcessingException { - final DdiStatus ddiStatus = new DdiStatus(executionStatus, new DdiResult(finalResult, new DdiProgress(2, 5)), + return getJsonActionFeedback(executionStatus, finalResult, null, messages); + } + + protected String getJsonActionFeedback(final DdiStatus.ExecutionStatus executionStatus, + final DdiResult.FinalResult finalResult, final Integer code, final List messages) throws JsonProcessingException { + final DdiStatus ddiStatus = new DdiStatus(executionStatus, new DdiResult(finalResult, new DdiProgress(2, 5)), code, messages); return objectMapper.writeValueAsString(new DdiActionFeedback(Instant.now().toString(), ddiStatus)); } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java index a4f5dc709..7db95d69c 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java @@ -492,7 +492,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } final String feedback = getJsonActionFeedback(DdiStatus.ExecutionStatus.PROCEEDING, DdiResult.FinalResult.NONE, - messages); + null, messages); postDeploymentFeedback(DEFAULT_CONTROLLER_ID, action.getId(), feedback, status().isForbidden()); } diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtActionStatus.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtActionStatus.java index b629ee180..ce40e877e 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtActionStatus.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/action/MgmtActionStatus.java @@ -33,7 +33,10 @@ public class MgmtActionStatus { private List messages; @JsonProperty - private Long reportedAt; + private Long reportedAt; + + @JsonProperty + private Integer code; /** * @return the statusId @@ -95,4 +98,18 @@ public class MgmtActionStatus { this.reportedAt = reportedAt; } + /** + * @return the code + */ + public Integer getCode() { + return code; + } + + /** + * @param code + * the reported code to set + */ + public void setCode(final Integer code) { + this.code = code; + } } 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 e2c1b9fec..41ec6d7c6 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 @@ -299,6 +299,7 @@ public final class MgmtTargetMapper { result.setReportedAt(actionStatus.getCreatedAt()); result.setStatusId(actionStatus.getId()); result.setType(actionStatus.getStatus().name().toLowerCase()); + actionStatus.getCode().ifPresent(result::setCode); return result; } 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 7db5d79b6..d53ce76d6 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 @@ -8,11 +8,13 @@ */ package org.eclipse.hawkbit.rest.util; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -183,6 +185,101 @@ public abstract class JsonBuilder { return builder.toString(); } + + /** + * builds a json string for the feedback for the execution "proceeding". + * + * @param id + * of the Action feedback refers to + * @return the built string + * @throws JSONException + */ + public static String deploymentActionInProgressFeedback(final String id) throws JSONException { + return deploymentActionFeedback(id, "proceeding"); + } + + /** + * builds a certain json string for a action feedback. + * + * @param id + * of the action the feedback refers to + * @param execution + * see ExecutionStatus + * @return the build json string + * @throws JSONException + */ + public static String deploymentActionFeedback(final String id, final String execution) throws JSONException { + return deploymentActionFeedback(id, execution, "none", null, + Arrays.asList(RandomStringUtils.randomAlphanumeric(1000))); + + } + + public static String deploymentActionFeedback(final String id, final String execution, final String message) + throws JSONException { + return deploymentActionFeedback(id, execution, "none", null, Arrays.asList(message)); + } + + public static String deploymentActionFeedback(final String id, final String execution, final String finished, + final String message) throws JSONException { + return deploymentActionFeedback(id, execution, finished, null, message); + } + + public static String deploymentActionFeedback(final String id, final String execution, final String finished, + final Integer code, final String message) throws JSONException { + return deploymentActionFeedback(id, execution, finished, code, Arrays.asList(message)); + } + + public static String deploymentActionFeedback(final String id, final String execution, final String finished, + final Integer code, final Collection messages) throws JSONException { + final JSONObject statusJson = new JSONObject().put("execution", execution).put("result", + new JSONObject().put("finished", finished).put("progress", new JSONObject().put("cnt", 2).put("of", 5))) + .put("details", new JSONArray(messages)); + if (code != null) { + statusJson.put("code", code); + } + return new JSONObject().put("id", id).put("status", statusJson).toString(); + } + + /** + * Build an invalid request body with missing result for feedback message. + * + * @param id + * id of the action + * @param execution + * the execution + * @param message + * the message + * @return a invalid request body + * @throws JSONException + */ + public static String missingResultInFeedback(final String id, final String execution, final String message) + throws JSONException { + return new JSONObject().put("id", id).put("time", "20140511T121314") + .put("status", + new JSONObject().put("execution", execution).put("details", new JSONArray().put(message))) + .toString(); + } + + /** + * Build an invalid request body with missing finished result for feedback + * message. + * + * @param id + * id of the action + * @param execution + * the execution + * @param message + * the message + * @return a invalid request body + * @throws JSONException + */ + public static String missingFinishedResultInFeedback(final String id, final String execution, final String message) + throws JSONException { + return new JSONObject().put("id", id).put("time", "20140511T121314") + .put("status", new JSONObject().put("execution", execution).put("result", new JSONObject()) + .put("details", new JSONArray().put(message))) + .toString(); + } public static String distributionSetTypes(final List types) throws JSONException { final JSONArray result = new JSONArray(); @@ -545,15 +642,27 @@ public abstract class JsonBuilder { return json.toString(); } + public static String cancelActionFeedback(final String id, final String execution) throws JSONException { + return cancelActionFeedback(id, execution, null, RandomStringUtils.randomAlphanumeric(1000)); + + } + public static String cancelActionFeedback(final String id, final String execution, final String message) throws JSONException { - return new JSONObject().put("id", id) - .put("status", - new JSONObject().put("execution", execution) - .put("result", new JSONObject().put("finished", "success")) - .put("details", new JSONArray().put(message))) - .toString(); + return cancelActionFeedback(id, execution, null, message); + } + public static String cancelActionFeedback(final String id, final String execution, final Integer code, + final String message) throws JSONException { + final JSONObject statusJson = new JSONObject().put("execution", execution) + .put("result", new JSONObject().put("finished", "success")) + .put("details", new JSONArray().put(message)); + + if (code != null) { + statusJson.put("code", code); + } + + return new JSONObject().put("id", id).put("status", statusJson).toString(); } public static JSONObject configData(final Map attributes) throws JSONException { diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/DdiApiModelProperties.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/DdiApiModelProperties.java index f94b9bbeb..9e7275257 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/DdiApiModelProperties.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/DdiApiModelProperties.java @@ -20,6 +20,8 @@ final class DdiApiModelProperties { static final String TARGET_EXEC_STATUS = "status of the action execution"; + static final String TARGET_EXEC_STATUS_CODE = "optional individual status code"; + static final String TARGET_RESULT_VALUE = "result of the action execution"; static final String TARGET_RESULT_DETAILS = "List of details message information"; diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/RootControllerDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/RootControllerDocumentationTest.java index e1faee561..33dc540e5 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/RootControllerDocumentationTest.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/ddi/documentation/RootControllerDocumentationTest.java @@ -185,17 +185,17 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio final Action cancelAction = deploymentManagement.cancelAction(actionId); final DdiStatus ddiStatus = new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, - new DdiResult(DdiResult.FinalResult.SUCCESS, new DdiProgress(2, 5)), List.of("Some feedback")); + new DdiResult(DdiResult.FinalResult.SUCCESS, new DdiProgress(2, 5)), null, List.of("Some feedback")); final DdiActionFeedback feedback = new DdiActionFeedback(Instant.now().toString(), ddiStatus); mockMvc.perform(post( DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CANCEL_ACTION + "/{actionId}/feedback", - tenantAware.getCurrentTenant(), target.getControllerId(), cancelAction.getId()).content( - objectMapper.writeValueAsString(feedback)) + tenantAware.getCurrentTenant(), target.getControllerId(), cancelAction.getId()) + .content(objectMapper.writeValueAsString(feedback)) .contentType(MediaType.APPLICATION_JSON_UTF8)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andDo(this.document.document( + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()).andDo(this.document + .document( pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT), parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID), parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID_CANCELED)), @@ -206,6 +206,8 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio optionalRequestFieldWithPath("time") .description(DdiApiModelProperties.FEEDBACK_ACTION_TIME), requestFieldWithPath("status").description(DdiApiModelProperties.TARGET_STATUS), + requestFieldWithPath("status.code") + .description(DdiApiModelProperties.TARGET_EXEC_STATUS_CODE), requestFieldWithPath("status.execution") .description(DdiApiModelProperties.TARGET_EXEC_STATUS).type("enum") .attributes(key("value").value( @@ -396,17 +398,14 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio final Long actionId = getFirstAssignedActionId(assignDistributionSet(set.getId(), target.getControllerId())); final DdiStatus ddiStatus = new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, - new DdiResult(DdiResult.FinalResult.SUCCESS, new DdiProgress(2, 5)), List.of("Feedback message")); + new DdiResult(DdiResult.FinalResult.SUCCESS, new DdiProgress(2, 5)), 200, List.of("Feedback message")); final DdiActionFeedback feedback = new DdiActionFeedback(Instant.now().toString(), ddiStatus); - mockMvc.perform(post( - DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION - + "/{actionId}/feedback", - tenantAware.getCurrentTenant(), target.getControllerId(), actionId) - .content(objectMapper.writeValueAsString(feedback)) + mockMvc.perform(post(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + + DdiRestConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/feedback", tenantAware.getCurrentTenant(), + target.getControllerId(), actionId).content(objectMapper.writeValueAsString(feedback)) .contentType(MediaType.APPLICATION_JSON_UTF8).accept(MediaTypes.HAL_JSON_VALUE)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andDo(this.document.document( + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()).andDo(this.document.document( pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT), parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID), parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID)), @@ -418,6 +417,8 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio optionalRequestFieldWithPath("time") .description(DdiApiModelProperties.FEEDBACK_ACTION_TIME), requestFieldWithPath("status").description(DdiApiModelProperties.TARGET_STATUS), + requestFieldWithPath("status.code") + .description(DdiApiModelProperties.TARGET_EXEC_STATUS_CODE), requestFieldWithPath("status.execution") .description(DdiApiModelProperties.TARGET_EXEC_STATUS).type("enum") .attributes(key("value").value( 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 4eace3217..1abc5f9d3 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 @@ -251,6 +251,11 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati .addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED)); } + protected void provideCodeFeedback(final Action action, final int code) { + controllerManagement.addUpdateActionStatus( + entityFactory.actionStatus().create(action.getId()).code(code).status(Status.RUNNING)); + } + protected Target createTargetByGivenNameWithAttributes(final String name, final DistributionSet distributionSet) { return createTargetByGivenNameWithAttributes(name, true, false, 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 89d9e2991..e2d83df4f 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 @@ -150,6 +150,8 @@ public final class MgmtApiModelProperties { public static final String ACTION_STATUS_REPORTED_AT = "Time at which the status was reported (server time)."; + public static final String ACTION_STATUS_CODE = "(Optional) Code provided by the device related to the status."; + public static final String ACTION_STATUS_LIST = "List of action status."; public static final String ACTION_EXECUTION_STATUS = "Status of action."; 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 c20a78edc..b4dcc6fc1 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 @@ -462,7 +462,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio @Test @Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.") public void getStatusFromAction() throws Exception { - final Action action = generateActionForTarget(targetId); + final Action action = generateActionForTarget(targetId, false); + provideCodeFeedback(action, 200); mockMvc.perform( get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + MgmtRestConstants.TARGET_V1_ACTIONS @@ -481,7 +482,10 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio .description(MgmtApiModelProperties.ACTION_STATUS_MESSAGES).type("String"), fieldWithPath("content[].reportedAt") .description(MgmtApiModelProperties.ACTION_STATUS_REPORTED_AT).type("String"), - fieldWithPath("content[].type").description(MgmtApiModelProperties.ACTION_STATUS_TYPE) + optionalRequestFieldWithPath("content[].code") + .description(MgmtApiModelProperties.ACTION_STATUS_CODE).type("Integer"), + fieldWithPath( + "content[].type").description(MgmtApiModelProperties.ACTION_STATUS_TYPE) .attributes(key("value").value( "['finished', 'error', 'warning', 'pending', 'running', 'canceled', 'retrieved', 'canceling']"))))); }