Introduce action status scoped custom code (#1277)
* Allow providing a custom code with an action status feedback to give more fine grained device specific details. * Add ddi rest docs for new optional status code value. * Provide new code value via mgmt api. Fix review findings. * Fix failing tests Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io> Co-authored-by: Stefan Behl <stefan.behl@bosch.io>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<Target> 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<ActionStatusCreate> 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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<String> message;
|
||||
|
||||
@@ -56,6 +61,15 @@ public class DmfActionUpdateStatus {
|
||||
return actionStatus;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Optional<Integer> getCode() {
|
||||
return Optional.ofNullable(code);
|
||||
}
|
||||
|
||||
public void setCode(final Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public List<String> getMessage() {
|
||||
if (message == null) {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -37,6 +37,8 @@ public interface ActionStatusCreate {
|
||||
*/
|
||||
ActionStatusCreate occurredAt(long occurredAt);
|
||||
|
||||
ActionStatusCreate code(int code);
|
||||
|
||||
/**
|
||||
* @param messages
|
||||
* for {@link ActionStatus#getMessages()}
|
||||
|
||||
@@ -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<Integer> getCode();
|
||||
|
||||
}
|
||||
|
||||
@@ -25,9 +25,13 @@ import org.springframework.util.StringUtils;
|
||||
* update or create builder interface
|
||||
*/
|
||||
public abstract class AbstractActionStatusCreate<T> {
|
||||
|
||||
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<T> {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public T code(final int code) {
|
||||
this.code = code;
|
||||
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public T messages(final Collection<String> messages) {
|
||||
if (this.messages == null) {
|
||||
this.messages = messages.stream().map(StringUtils::trimWhitespace).collect(Collectors.toList());
|
||||
|
||||
@@ -29,6 +29,9 @@ public class JpaActionStatusCreate extends AbstractActionStatusCreate<ActionStat
|
||||
if (messages != null) {
|
||||
messages.forEach(result::addMessage);
|
||||
}
|
||||
if (code != null) {
|
||||
result.setCode(code);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
@@ -85,6 +86,9 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
|
||||
@Column(name = "detail_message", length = MESSAGE_ENTRY_LENGTH, nullable = false, updatable = false)
|
||||
private List<String> 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<Integer> getCode() {
|
||||
return Optional.ofNullable(code);
|
||||
}
|
||||
|
||||
public void setCode(final Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String> 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<String> details) {
|
||||
@JsonProperty("result") final DdiResult result, @JsonProperty("code") final Integer code,
|
||||
@JsonProperty("details") final List<String> 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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Arguments> 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);
|
||||
|
||||
@@ -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<String> 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<String> 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<String> 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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -323,13 +323,18 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
|
||||
|
||||
protected String getJsonActionFeedback(final DdiStatus.ExecutionStatus executionStatus, final DdiResult ddiResult,
|
||||
final List<String> 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<String> 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<String> 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));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,10 @@ public class MgmtActionStatus {
|
||||
private List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<String> 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<DistributionSetType> 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<String, String> attributes) throws JSONException {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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']")))));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user