Skip to content

Add new endpoint for single action #1316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

Expand Down Expand Up @@ -54,4 +55,14 @@ ResponseEntity<PagedList<MgmtAction>> getActions(
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE_DEFAULT) String representationModeParam);

/**
* Handles the GET request of retrieving a specific Action by <code>actionId</code>
*
* @param actionId
*
* @return the action
*/
@GetMapping(value = "/{actionId}", produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtAction> getAction(
@PathVariable("actionId") Long actionId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -55,16 +56,29 @@ public ResponseEntity<PagedList<MgmtAction>> getActions(final int pagingOffsetPa
totalActionCount = this.deploymentManagement.countActionsAll();
}

final MgmtRepresentationMode repMode = MgmtRepresentationMode.fromValue(representationModeParam)
.orElseGet(() -> {
// no need for a 400, just apply a safe fallback
LOG.warn("Received an invalid representation mode: {}", representationModeParam);
return MgmtRepresentationMode.COMPACT;
});
final MgmtRepresentationMode repMode = getRepresentationModeFromString(representationModeParam);

return ResponseEntity
.ok(new PagedList<>(MgmtActionMapper.toResponse(actions.getContent(), repMode), totalActionCount));

}

@Override
public ResponseEntity<MgmtAction> getAction(final Long actionId) {

final Action action = deploymentManagement.findAction(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));

return ResponseEntity.ok(MgmtActionMapper.toResponse(action, MgmtRepresentationMode.FULL));
}

private MgmtRepresentationMode getRepresentationModeFromString(final String representationModeParam) {
return MgmtRepresentationMode.fromValue(representationModeParam)
.orElseGet(() -> {
// no need for a 400, just apply a safe fallback
LOG.warn("Received an invalid representation mode: {}", representationModeParam);
return MgmtRepresentationMode.COMPACT;
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ class MgmtActionResourceTest extends AbstractManagementApiIntegrationTest {
private static final String JSON_PATH_FIELD_SIZE = ".size";
private static final String JSON_PATH_FIELD_TOTAL = ".total";

private static final String JSON_PATH_FIELD_ID = ".id";

private static final String JSON_PATH_PAGED_LIST_CONTENT = JSON_PATH_ROOT + JSON_PATH_FIELD_CONTENT;
private static final String JSON_PATH_PAGED_LIST_SIZE = JSON_PATH_ROOT + JSON_PATH_FIELD_SIZE;
private static final String JSON_PATH_PAGED_LIST_TOTAL = JSON_PATH_ROOT + JSON_PATH_FIELD_TOTAL;

private static final String JSON_PATH_ACTION_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID;

@Test
@Description("Verifies that actions can be filtered based on action status.")
void filterActionsByStatus() throws Exception {
Expand Down Expand Up @@ -381,10 +385,6 @@ void invalidRequestsOnActionResource() throws Exception {
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
final Long actionId = actions.get(0).getId();

// ensure specific action cannot be accessed via the actions resource
mvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/" + actionId))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());

// not allowed methods
mvc.perform(post(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
Expand All @@ -394,6 +394,28 @@ void invalidRequestsOnActionResource() throws Exception {
.andExpect(status().isMethodNotAllowed());
}

@Test
@Description("Verifies that the correct action is returned")
void shouldRetrieveCorrectActionById() throws Exception {
final String knownTargetId = "targetId";

final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
final Long actionId = actions.get(0).getId();

mvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/" + actionId))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath(JSON_PATH_ACTION_ID, equalTo(actionId.intValue())));
}

@Test
@Description("Verifies that NOT_FOUND is returned when there is no such action.")
void requestActionThatDoesNotExistsLeadsToNotFound() throws Exception {
mvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/" + 101))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
}

private List<Action> generateTargetWithTwoUpdatesWithOneOverride(final String knownTargetId) {
return generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(knownTargetId, null, null, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,44 @@ include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===

== GET /rest/v1/actions/{actionId}

=== Implementation notes

Handles the GET request of retrieving a single action within Hawkbit by actionId.

=== Get single action

==== CURL

include::{snippets}/actions/get-action/curl-request.adoc[]

==== Request URL

A `GET` request is used to access a single action

include::{snippets}/actions/get-action/http-request.adoc[]

=== Response (Status 200)

==== Response fields

include::{snippets}/actions/get-action/response-fields.adoc[]

==== Response example

include::{snippets}/actions/get-action/http-response.adoc[]

|===
| HTTP Status Code | Reason | Response Model

include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/404.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===

== Additional content

[[error-body]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

Expand Down Expand Up @@ -103,6 +102,47 @@ public void getActionsWithParameters() throws Exception {
parameterWithName("representation").description(MgmtApiModelProperties.REPRESENTATION_MODE))));
}

@Test
@Description("Handles the GET request of retrieving a specific action.")
public void getAction() throws Exception {
final Action action = generateRolloutActionForTarget(targetId);
provideCodeFeedback(action, 200);

assertThat(deploymentManagement.findAction(action.getId()).get().getActionType())
.isEqualTo(Action.ActionType.FORCED);

mockMvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/{actionId}", action.getId()))
.andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
.andDo(this.document.document(
pathParameters(parameterWithName("actionId").description(ApiModelPropertiesGeneric.ITEM_ID)),
responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
.type("String"),
fieldWithPath("type").description(MgmtApiModelProperties.ACTION_TYPE)
.attributes(key("value").value("['update', 'cancel']")),
fieldWithPath("forceType").description(MgmtApiModelProperties.ACTION_FORCE_TYPE)
.attributes(key("value").value("['forced', 'soft', 'timeforced']")),
fieldWithPath("status").description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
.attributes(key("value").value("['finished', 'pending']")),
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(),
fieldWithPath("_links.distributionset").description(MgmtApiModelProperties.LINK_TO_DS),
fieldWithPath("_links.status")
.description(MgmtApiModelProperties.LINKS_ACTION_STATUSES),
fieldWithPath("_links.rollout").description(MgmtApiModelProperties.LINK_TO_ROLLOUT),
fieldWithPath("_links.target").description(MgmtApiModelProperties.LINK_TO_TARGET))));
}

private Action generateRolloutActionForTarget(final String knownControllerId) throws Exception {
return generateActionForTarget(knownControllerId, true, false, null, null, null, true);
}
Expand Down