Support user consent flow (#1293)
* Introduce user consent flow * Add permissions to confirmation management * rename from consent to confirmation * Reformat code. Remove unused imports. Change and add permission checks when configuring auto-confirmation. * Do not include null values for DDI confirmation base endpoint * fix confirmation required checkbox id * Remove unused import. Fix consume/produce type of new API's. * Change term processing to proceeding when activating user consent flow * Align formatting and extend integration test cases for DMF and DDI. * Extend DMF test cases to consider auto-confirmation * Refactor action management to fix problem of handling action status updates on closed actions. * remove unsupported validation * use new confirmation api for DMF. Extend test cases., * Remove unnecessary fields. * Extend API documentation for DDI and MGMT API. * adapt ddi api docs adoc file * Fixed the duplicate migration version for db files * fix method to support confirmation * Fixed PR comments * Addressed PR comments * Fixed after merge compilation issue * Fixed after merge compilation issue * Fix failing tests in MgmtRolloutResourceTest * Fixed the permissions issue reflected by integration tests * Added back the missing line of code lost during merge * Fix the failing test on Jenkins Signed-off-by: Stanislav Trailov <stanislav.trailov@bosch.io> Signed-off-by: Dimitar Shterev <dimitar.shterev@bosch.io> Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io> Signed-off-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com> Co-authored-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com>
This commit is contained in:
@@ -19,8 +19,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
|
||||
/**
|
||||
* Provide action history information to the controller as part of response to
|
||||
* {@link DdiRootControllerRestApi#getControllerBasedeploymentAction}: 1.
|
||||
* Current action status at the server; 2. List of messages from action history
|
||||
* {@link DdiRootControllerRestApi#getControllerBasedeploymentAction} and
|
||||
* {@link DdiRootControllerRestApi#getControllerBaseconfirmationAction}:
|
||||
* 1. Current action status at the server; 2. List of messages from action history
|
||||
* that were sent to server earlier by the controller using
|
||||
* {@link DdiActionFeedback}.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ddi.json.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DdiActivateAutoConfirmation {
|
||||
|
||||
@JsonProperty(required = false)
|
||||
private final String initiator;
|
||||
|
||||
@JsonProperty(required = false)
|
||||
private final String remark;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param initiator
|
||||
* can be null
|
||||
* @param remark
|
||||
* can be null
|
||||
*/
|
||||
@JsonCreator
|
||||
public DdiActivateAutoConfirmation(@JsonProperty(value = "initiator") final String initiator,
|
||||
@JsonProperty(value = "remark") final String remark) {
|
||||
this.initiator = initiator;
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DdiActivateAutoConfirmation [initiator=" + initiator + ", remark=" + remark + ", toString()="
|
||||
+ super.toString() + "]";
|
||||
}
|
||||
|
||||
public String getInitiator() {
|
||||
return initiator;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ddi.json.model;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder({ "active", "initiator", "remark", "activatedAt" })
|
||||
public class DdiAutoConfirmationState extends RepresentationModel<DdiAutoConfirmationState> {
|
||||
|
||||
@NotNull
|
||||
private boolean active;
|
||||
private String initiator;
|
||||
private String remark;
|
||||
|
||||
private Long activatedAt;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public DdiAutoConfirmationState() {
|
||||
// needed for json create.
|
||||
}
|
||||
|
||||
public static DdiAutoConfirmationState active(final long activatedAt) {
|
||||
final DdiAutoConfirmationState state = new DdiAutoConfirmationState();
|
||||
state.setActive(true);
|
||||
state.setActivatedAt(activatedAt);
|
||||
return state;
|
||||
}
|
||||
|
||||
public static DdiAutoConfirmationState disabled() {
|
||||
return new DdiAutoConfirmationState();
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(final boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public Long getActivatedAt() {
|
||||
return activatedAt;
|
||||
}
|
||||
|
||||
public void setActivatedAt(final long activatedAt) {
|
||||
this.activatedAt = activatedAt;
|
||||
}
|
||||
|
||||
public String getInitiator() {
|
||||
return initiator;
|
||||
}
|
||||
|
||||
public void setInitiator(final String initiator) {
|
||||
this.initiator = initiator;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(final String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ddi.json.model;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Confirmation base response.
|
||||
* Set order to place links at last.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder({ "autoConfirm" })
|
||||
public class DdiConfirmationBase extends RepresentationModel<DdiConfirmationBase> {
|
||||
|
||||
@JsonProperty("autoConfirm")
|
||||
@NotNull
|
||||
private DdiAutoConfirmationState autoConfirm;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public DdiConfirmationBase() {
|
||||
// needed for json create.
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param autoConfirmState
|
||||
*/
|
||||
public DdiConfirmationBase(final DdiAutoConfirmationState autoConfirmState) {
|
||||
this.autoConfirm = autoConfirmState;
|
||||
}
|
||||
|
||||
public DdiAutoConfirmationState getAutoConfirm() {
|
||||
return autoConfirm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ddi.json.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Update action resource.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder({ "id", "confirmation", "actionHistory" })
|
||||
public class DdiConfirmationBaseAction extends RepresentationModel<DdiConfirmationBaseAction> {
|
||||
|
||||
@JsonProperty("id")
|
||||
@NotNull
|
||||
private String id;
|
||||
|
||||
@JsonProperty("confirmation")
|
||||
@NotNull
|
||||
private DdiDeployment confirmation;
|
||||
|
||||
/**
|
||||
* Action history containing current action status and a list of feedback
|
||||
* messages received earlier from the controller.
|
||||
*/
|
||||
@JsonProperty("actionHistory")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private DdiActionHistory actionHistory;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public DdiConfirmationBaseAction() {
|
||||
// needed for json create.
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param id
|
||||
* of the update action
|
||||
* @param confirmation
|
||||
* chunk details
|
||||
* @param actionHistory
|
||||
* containing current action status and a list of feedback messages
|
||||
* received earlier from the controller.
|
||||
*/
|
||||
public DdiConfirmationBaseAction(final String id, final DdiDeployment confirmation,
|
||||
final DdiActionHistory actionHistory) {
|
||||
this.id = id;
|
||||
this.confirmation = confirmation;
|
||||
this.actionHistory = actionHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the confirmation data containing chunks needed to confirm a action.
|
||||
*
|
||||
* @return {@link DdiDeployment}
|
||||
*/
|
||||
public DdiDeployment getConfirmation() {
|
||||
return confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action history containing current action status and a list of
|
||||
* feedback messages received earlier from the controller.
|
||||
*
|
||||
* @return {@link DdiActionHistory}
|
||||
*/
|
||||
public DdiActionHistory getActionHistory() {
|
||||
return actionHistory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfirmationBase [id=" + id + ", confirmation=" + confirmation + " actionHistory=" + actionHistory
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DdiConfirmationBaseAction oBase = (DdiConfirmationBaseAction) obj;
|
||||
return Objects.equals(this.id, oBase.id) && Objects.equals(this.confirmation, oBase.confirmation)
|
||||
&& Objects.equals(this.actionHistory, oBase.actionHistory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ddi.json.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* New update actions require confirmation when confirmation flow is switched on. This is the feedback channel for
|
||||
* confirmation messages for DDI API. The confirmation message has a mandatory field confirmation with possible values:
|
||||
* "confirmed" and "denied".
|
||||
*
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DdiConfirmationFeedback {
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private final Confirmation confirmation;
|
||||
|
||||
private final Integer code;
|
||||
|
||||
private final List<String> details;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an confirmation-feedback
|
||||
*
|
||||
* @param confirmation confirmation value for the action. Valid values are "Confirmed" and "Denied
|
||||
* @param code code for confirmation
|
||||
* @param details messages
|
||||
*/
|
||||
@JsonCreator public DdiConfirmationFeedback(
|
||||
@JsonProperty(value = "confirmation", required = true) final Confirmation confirmation,
|
||||
@JsonProperty(value = "code") final Integer code,
|
||||
@JsonProperty(value = "details") final List<String> details) {
|
||||
this.confirmation = confirmation;
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public Confirmation getConfirmation() {
|
||||
return confirmation;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public List<String> getDetails() {
|
||||
if (details == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(details);
|
||||
}
|
||||
|
||||
public enum Confirmation {
|
||||
/**
|
||||
* Confirm the action.
|
||||
*/
|
||||
CONFIRMED("confirmed"),
|
||||
|
||||
/**
|
||||
* Deny the action.
|
||||
*/
|
||||
DENIED("denied");
|
||||
|
||||
|
||||
private final String name;
|
||||
|
||||
Confirmation(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DdiConfirmationFeedback [confirmation=" + confirmation + ", code=" + code + ", details=" + details +"]";
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,21 @@ public final class DdiRestConstants {
|
||||
*/
|
||||
public static final String DEPLOYMENT_BASE_ACTION = "deploymentBase";
|
||||
|
||||
/**
|
||||
* Confirmation base resource.
|
||||
*/
|
||||
public static final String CONFIRMATION_BASE = "confirmationBase";
|
||||
|
||||
/**
|
||||
* Activate auto-confirm
|
||||
*/
|
||||
public static final String AUTO_CONFIRM_ACTIVATE = "activateAutoConfirm";
|
||||
|
||||
/**
|
||||
* Deactivate auto-confirm
|
||||
*/
|
||||
public static final String AUTO_CONFIRM_DEACTIVATE = "deactivateAutoConfirm";
|
||||
|
||||
/**
|
||||
* Installed action resources.
|
||||
*/
|
||||
@@ -52,6 +67,7 @@ public final class DdiRestConstants {
|
||||
* Default value specifying that no action history to be sent as part of
|
||||
* response to deploymentBase
|
||||
* {@link DdiRootControllerRestApi#getControllerBasedeploymentAction}.
|
||||
* {@link DdiRootControllerRestApi#getConfirmationBaseAction}.
|
||||
*/
|
||||
public static final String NO_ACTION_HISTORY = "0";
|
||||
|
||||
|
||||
@@ -16,9 +16,14 @@ import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActivateAutoConfirmation;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiAutoConfirmationState;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiCancel;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfigData;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationBaseAction;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiDeploymentBase;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
@@ -29,6 +34,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@@ -123,17 +129,16 @@ public interface DdiRootControllerRestApi {
|
||||
* @param controllerId
|
||||
* of the target
|
||||
* @param actionId
|
||||
* of the {@link DdiDeploymentBase} that matches to active
|
||||
* actions.
|
||||
* of the {@link DdiDeploymentBase} that matches to active actions.
|
||||
* @param resource
|
||||
* an hashcode of the resource which indicates if the action has
|
||||
* been changed, e.g. from 'soft' to 'force' and the eTag needs
|
||||
* to be re-generated
|
||||
* an hashcode of the resource which indicates if the action has been
|
||||
* changed, e.g. from 'soft' to 'force' and the eTag needs to be
|
||||
* re-generated
|
||||
* @param actionHistoryMessageCount
|
||||
* specifies the number of messages to be returned from action
|
||||
* history. Regardless of the passed value, in order to restrict
|
||||
* resource utilization by controllers, maximum number of
|
||||
* messages that are retrieved from database is limited by
|
||||
* resource utilization by controllers, maximum number of messages
|
||||
* that are retrieved from database is limited by
|
||||
* {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}.
|
||||
*
|
||||
* actionHistoryMessageCount less than zero: retrieves the maximum
|
||||
@@ -212,8 +217,8 @@ public interface DdiRootControllerRestApi {
|
||||
@PathVariable("actionId") @NotEmpty final Long actionId);
|
||||
|
||||
/**
|
||||
* RequestMethod.POST method receiving the {@link DdiActionFeedback} from
|
||||
* the target.
|
||||
* RequestMethod.POST method receiving the {@link DdiActionFeedback} from the
|
||||
* target.
|
||||
*
|
||||
* @param feedback
|
||||
* the {@link DdiActionFeedback} from the target.
|
||||
@@ -270,4 +275,119 @@ public interface DdiRootControllerRestApi {
|
||||
@PathVariable("actionId") @NotEmpty final Long actionId,
|
||||
@RequestParam(value = "actionHistory", defaultValue = DdiRestConstants.NO_ACTION_HISTORY) final Integer actionHistoryMessageCount);
|
||||
|
||||
/**
|
||||
* Returns the confirmation base with the current auto-confirmation state for a
|
||||
* given controllerId and toggle links. In case there are actions present where
|
||||
* the confirmation is required, a reference to it will be returned as well.
|
||||
*
|
||||
* @param tenant
|
||||
* the controllerId is corresponding too
|
||||
* @param controllerId
|
||||
* to check the state for
|
||||
* @return the state as {@link DdiAutoConfirmationState}
|
||||
*/
|
||||
@GetMapping(value = "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE, produces = {
|
||||
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE, DdiRestConstants.MEDIA_TYPE_CBOR })
|
||||
ResponseEntity<DdiConfirmationBase> getConfirmationBase(@PathVariable("tenant") final String tenant,
|
||||
@PathVariable("controllerId") @NotEmpty final String controllerId);
|
||||
|
||||
/**
|
||||
* Resource for confirmation of an action.
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* @param controllerId
|
||||
* of the target
|
||||
* @param actionId
|
||||
* of the {@link DdiConfirmationBaseAction} that matches to active
|
||||
* actions in WAITING_FOR_CONFIRMATION status.
|
||||
* @param resource
|
||||
* an hashcode of the resource which indicates if the action has been
|
||||
* changed, e.g. from 'soft' to 'force' and the eTag needs to be
|
||||
* re-generated
|
||||
* @param actionHistoryMessageCount
|
||||
* specifies the number of messages to be returned from action
|
||||
* history. Regardless of the passed value, in order to restrict
|
||||
* resource utilization by controllers, maximum number of messages
|
||||
* that are retrieved from database is limited by
|
||||
* {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}.
|
||||
*
|
||||
* actionHistoryMessageCount less than zero: retrieves the maximum
|
||||
* allowed number of action status messages from history;
|
||||
*
|
||||
* actionHistoryMessageCount equal to zero: does not retrieve any
|
||||
* message;
|
||||
*
|
||||
* actionHistoryMessageCount greater than zero: retrieves the
|
||||
* specified number of messages, limited by maximum allowed number.
|
||||
*
|
||||
* @return the response
|
||||
*/
|
||||
@GetMapping(value = "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE + "/{actionId}", produces = {
|
||||
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE, DdiRestConstants.MEDIA_TYPE_CBOR })
|
||||
ResponseEntity<DdiConfirmationBaseAction> getConfirmationBaseAction(@PathVariable("tenant") final String tenant,
|
||||
@PathVariable("controllerId") @NotEmpty final String controllerId,
|
||||
@PathVariable("actionId") @NotEmpty final Long actionId,
|
||||
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource,
|
||||
@RequestParam(value = "actionHistory", defaultValue = DdiRestConstants.NO_ACTION_HISTORY) final Integer actionHistoryMessageCount);
|
||||
|
||||
/**
|
||||
* This is the feedback channel for the {@link DdiConfirmationBaseAction}
|
||||
* action.
|
||||
*
|
||||
* @param tenant
|
||||
* of the client
|
||||
* @param feedback
|
||||
* to provide
|
||||
* @param controllerId
|
||||
* of the target that matches to controller id
|
||||
* @param actionId
|
||||
* of the action we have feedback for
|
||||
*
|
||||
* @return the response
|
||||
*/
|
||||
@PostMapping(value = "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE + "/{actionId}/"
|
||||
+ DdiRestConstants.FEEDBACK, consumes = { MediaType.APPLICATION_JSON_VALUE,
|
||||
DdiRestConstants.MEDIA_TYPE_CBOR })
|
||||
ResponseEntity<Void> postConfirmationActionFeedback(@Valid final DdiConfirmationFeedback feedback,
|
||||
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
|
||||
@PathVariable("actionId") @NotEmpty final Long actionId);
|
||||
|
||||
/**
|
||||
* Activate auto confirmation for a given controllerId. Will use the provided
|
||||
* initiator and remark field from the provided
|
||||
* {@link DdiActivateAutoConfirmation}. If not present, the values will be
|
||||
* prefilled with a default remark and the CONTROLLER as initiator.
|
||||
*
|
||||
* @param tenant
|
||||
* the controllerId is corresponding too
|
||||
* @param controllerId
|
||||
* to activate auto-confirmation for
|
||||
* @param body
|
||||
* as {@link DdiActivateAutoConfirmation}
|
||||
* @return {@link org.springframework.http.HttpStatus#OK} if successful or
|
||||
* {@link org.springframework.http.HttpStatus#CONFLICT} in case
|
||||
* auto-confirmation was active already.
|
||||
*/
|
||||
@PostMapping(value = "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE + "/"
|
||||
+ DdiRestConstants.AUTO_CONFIRM_ACTIVATE, consumes = { MediaType.APPLICATION_JSON_VALUE,
|
||||
DdiRestConstants.MEDIA_TYPE_CBOR })
|
||||
ResponseEntity<Void> activateAutoConfirmation(@PathVariable("tenant") final String tenant,
|
||||
@PathVariable("controllerId") @NotEmpty final String controllerId,
|
||||
@Valid @RequestBody(required = false) final DdiActivateAutoConfirmation body);
|
||||
|
||||
/**
|
||||
* Deactivate auto confirmation for a given controller id.
|
||||
*
|
||||
* @param tenant
|
||||
* the controllerId is corresponding too
|
||||
* @param controllerId
|
||||
* to disable auto-confirmation for
|
||||
* @return {@link org.springframework.http.HttpStatus#OK} if successfully
|
||||
* executed
|
||||
*/
|
||||
@PostMapping(value = "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE + "/"
|
||||
+ DdiRestConstants.AUTO_CONFIRM_DEACTIVATE)
|
||||
ResponseEntity<Void> deactivateAutoConfirmation(@PathVariable("tenant") final String tenant,
|
||||
@PathVariable("controllerId") @NotEmpty final String controllerId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package org.eclipse.hawkbit.ddi.json.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.eclipse.hawkbit.ddi.json.model.DdiDeployment.DdiMaintenanceWindowStatus.AVAILABLE;
|
||||
import static org.eclipse.hawkbit.ddi.json.model.DdiDeployment.HandlingType.ATTEMPT;
|
||||
import static org.eclipse.hawkbit.ddi.json.model.DdiDeployment.HandlingType.FORCED;
|
||||
|
||||
/**
|
||||
* Test serializability of DDI api model 'DdiConfirmationBase'
|
||||
*/
|
||||
@Feature("Unit Tests - Direct Device Integration API")
|
||||
@Story("CHeck JSON serialization of DDI api confirmation models")
|
||||
class DdiConfirmationBaseTest {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
@Description("Verify the correct serialization and deserialization of the model")
|
||||
void shouldSerializeAndDeserializeObject() throws IOException {
|
||||
// Setup
|
||||
final String id = "1234";
|
||||
final DdiDeployment ddiDeployment = new DdiDeployment(FORCED, ATTEMPT, Collections.emptyList(), AVAILABLE);
|
||||
final String actionStatus = "TestAction";
|
||||
final DdiActionHistory ddiActionHistory = new DdiActionHistory(actionStatus,
|
||||
Arrays.asList("Action status message 1", "Action status message 2"));
|
||||
final DdiConfirmationBaseAction ddiConfirmationBaseAction = new DdiConfirmationBaseAction(id, ddiDeployment,
|
||||
ddiActionHistory);
|
||||
|
||||
// Test
|
||||
String serializedDdiConfirmationBase = mapper.writeValueAsString(ddiConfirmationBaseAction);
|
||||
final DdiConfirmationBaseAction deserializedDdiConfigurationBase = mapper
|
||||
.readValue(serializedDdiConfirmationBase, DdiConfirmationBaseAction.class);
|
||||
|
||||
assertThat(serializedDdiConfirmationBase).contains(id, FORCED.getName(), ATTEMPT.getName(),
|
||||
AVAILABLE.getStatus(), actionStatus);
|
||||
assertThat(deserializedDdiConfigurationBase.getConfirmation().getDownload())
|
||||
.isEqualTo(ddiDeployment.getDownload());
|
||||
assertThat(deserializedDdiConfigurationBase.getConfirmation().getUpdate()).isEqualTo(ddiDeployment.getUpdate());
|
||||
assertThat(deserializedDdiConfigurationBase.getConfirmation().getMaintenanceWindow())
|
||||
.isEqualTo(ddiDeployment.getMaintenanceWindow());
|
||||
assertThat(deserializedDdiConfigurationBase.getActionHistory().toString())
|
||||
.isEqualTo(ddiActionHistory.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify the correct deserialization of a model with a additional unknown property")
|
||||
void shouldDeserializeObjectWithUnknownProperty() throws IOException {
|
||||
// Setup
|
||||
String serializedDdiConfirmationBase = "{\"id\":\"1234\",\"confirmation\":{\"download\":\"forced\","
|
||||
+ "\"update\":\"attempt\",\"maintenanceWindow\":\"available\",\"chunks\":[]},"
|
||||
+ "\"actionHistory\":{\"status\":\"TestAction\",\"messages\":[\"Action status message 1\","
|
||||
+ "\"Action status message 2\"]},\"links\":[],\"unknownProperty\":\"test\"}";
|
||||
|
||||
// Test
|
||||
DdiConfirmationBaseAction ddiConfirmationBaseAction = mapper.readValue(serializedDdiConfirmationBase,
|
||||
DdiConfirmationBaseAction.class);
|
||||
|
||||
assertThat(ddiConfirmationBaseAction.getConfirmation().getDownload().getName()).isEqualTo(FORCED.getName());
|
||||
assertThat(ddiConfirmationBaseAction.getConfirmation().getUpdate().getName()).isEqualTo(ATTEMPT.getName());
|
||||
assertThat(ddiConfirmationBaseAction.getConfirmation().getMaintenanceWindow().getStatus())
|
||||
.isEqualTo(AVAILABLE.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that deserialization fails for known properties with a wrong datatype")
|
||||
void shouldFailForObjectWithWrongDataTypes() throws IOException {
|
||||
// Setup
|
||||
String serializedDdiConfirmationBase = "{\"id\":[\"1234\"],\"confirmation\":{\"download\":\"forced\","
|
||||
+ "\"update\":\"attempt\",\"maintenanceWindow\":\"available\",\"chunks\":[]},"
|
||||
+ "\"actionHistory\":{\"status\":\"TestAction\",\"messages\":[\"Action status message 1\","
|
||||
+ "\"Action status message 2\"]},\"links\":[]}";
|
||||
|
||||
// Test
|
||||
assertThatExceptionOfType(MismatchedInputException.class)
|
||||
.isThrownBy(() -> mapper.readValue(serializedDdiConfirmationBase, DdiConfirmationBaseAction.class));
|
||||
}
|
||||
}
|
||||
@@ -31,20 +31,23 @@ import io.qameta.allure.Story;
|
||||
public class JsonIgnorePropertiesAnnotationTest {
|
||||
|
||||
@Test
|
||||
@Description(
|
||||
"This test verifies that all model classes within the 'org.eclipse.hawkbit.ddi.json.model' package are annotated with '@JsonIgnoreProperties(ignoreUnknown = true)'")
|
||||
@Description("This test verifies that all model classes within the 'org.eclipse.hawkbit.ddi.json.model' package are annotated with '@JsonIgnoreProperties(ignoreUnknown = true)'")
|
||||
public void shouldCheckAnnotationsForAllModelClasses() throws IOException {
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final String packageName = this.getClass().getPackage().getName();
|
||||
|
||||
final ImmutableSet<ClassPath.ClassInfo> topLevelClasses = ClassPath.from(loader).getTopLevelClasses(packageName);
|
||||
final ImmutableSet<ClassPath.ClassInfo> topLevelClasses = ClassPath.from(loader)
|
||||
.getTopLevelClasses(packageName);
|
||||
for (final ClassPath.ClassInfo classInfo : topLevelClasses) {
|
||||
final Class<?> modelClass = classInfo.load();
|
||||
if (modelClass.getSimpleName().contains("Test") || modelClass.isEnum()) {
|
||||
continue;
|
||||
}
|
||||
final JsonIgnoreProperties annotation = modelClass.getAnnotation(JsonIgnoreProperties.class);
|
||||
assertThat(annotation).isNotNull();
|
||||
assertThat(annotation)
|
||||
.describedAs(
|
||||
"Annotation 'JsonIgnoreProperties' is missing for class: " + modelClass.getSimpleName())
|
||||
.isNotNull();
|
||||
assertThat(annotation.ignoreUnknown()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ import org.eclipse.hawkbit.api.URLPlaceholder;
|
||||
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiArtifactHash;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiAutoConfirmationState;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiChunk;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfig;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiMetadata;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiPolling;
|
||||
@@ -108,13 +110,46 @@ public final class DataConversionHelper {
|
||||
|
||||
}
|
||||
|
||||
public static DdiConfirmationBase createConfirmationBase(final Target target, final Action activeAction,
|
||||
final DdiAutoConfirmationState autoConfirmationState, final TenantAware tenantAware) {
|
||||
final String controllerId = target.getControllerId();
|
||||
final DdiConfirmationBase confirmationBase = new DdiConfirmationBase(autoConfirmationState);
|
||||
if (autoConfirmationState.isActive()) {
|
||||
confirmationBase.add(WebMvcLinkBuilder
|
||||
.linkTo(WebMvcLinkBuilder.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.deactivateAutoConfirmation(tenantAware.getCurrentTenant(), controllerId))
|
||||
.withRel(DdiRestConstants.AUTO_CONFIRM_DEACTIVATE));
|
||||
} else {
|
||||
confirmationBase.add(WebMvcLinkBuilder
|
||||
.linkTo(WebMvcLinkBuilder.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.activateAutoConfirmation(tenantAware.getCurrentTenant(), controllerId, null))
|
||||
.withRel(DdiRestConstants.AUTO_CONFIRM_ACTIVATE));
|
||||
}
|
||||
if (activeAction != null && activeAction.isWaitingConfirmation()) {
|
||||
confirmationBase.add(WebMvcLinkBuilder
|
||||
.linkTo(WebMvcLinkBuilder.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.getConfirmationBaseAction(tenantAware.getCurrentTenant(), controllerId,
|
||||
activeAction.getId(), calculateEtag(activeAction), null))
|
||||
.withRel(DdiRestConstants.CONFIRMATION_BASE));
|
||||
}
|
||||
|
||||
return confirmationBase;
|
||||
}
|
||||
|
||||
public static DdiControllerBase fromTarget(final Target target, final Action installedAction,
|
||||
final Action activeAction, final String defaultControllerPollTime, final TenantAware tenantAware) {
|
||||
final DdiControllerBase result = new DdiControllerBase(
|
||||
new DdiConfig(new DdiPolling(defaultControllerPollTime)));
|
||||
|
||||
if (activeAction != null) {
|
||||
if (activeAction.isCancelingOrCanceled()) {
|
||||
if (activeAction.isWaitingConfirmation()) {
|
||||
result.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder
|
||||
.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.getConfirmationBaseAction(tenantAware.getCurrentTenant(), target.getControllerId(),
|
||||
activeAction.getId(), calculateEtag(activeAction), null))
|
||||
.withRel(DdiRestConstants.CONFIRMATION_BASE));
|
||||
|
||||
} else if (activeAction.isCancelingOrCanceled()) {
|
||||
result.add(WebMvcLinkBuilder
|
||||
.linkTo(WebMvcLinkBuilder.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.getControllerCancelAction(tenantAware.getCurrentTenant(), target.getControllerId(),
|
||||
@@ -126,10 +161,10 @@ public final class DataConversionHelper {
|
||||
// have changed from 'soft' to 'forced' type and we need to
|
||||
// change the payload of the
|
||||
// response because of eTags.
|
||||
result.add(WebMvcLinkBuilder
|
||||
.linkTo(WebMvcLinkBuilder.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.getControllerBasedeploymentAction(tenantAware.getCurrentTenant(),
|
||||
target.getControllerId(), activeAction.getId(), calculateEtag(activeAction), null))
|
||||
result.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder
|
||||
.methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
|
||||
.getControllerBasedeploymentAction(tenantAware.getCurrentTenant(), target.getControllerId(),
|
||||
activeAction.getId(), calculateEtag(activeAction), null))
|
||||
.withRel(DdiRestConstants.DEPLOYMENT_BASE_ACTION));
|
||||
}
|
||||
}
|
||||
@@ -149,6 +184,7 @@ public final class DataConversionHelper {
|
||||
.putConfigData(null, tenantAware.getCurrentTenant(), target.getControllerId()))
|
||||
.withRel(DdiRestConstants.CONFIG_DATA_ACTION));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
@@ -22,11 +23,16 @@ import org.eclipse.hawkbit.api.ArtifactUrlHandler;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionHistory;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActivateAutoConfirmation;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiAutoConfirmationState;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiCancel;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiCancelActionToStop;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiChunk;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfigData;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationBaseAction;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiDeployment;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiDeployment.DdiMaintenanceWindowStatus;
|
||||
@@ -37,6 +43,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiUpdateMode;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRootControllerRestApi;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ConfirmationManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
@@ -47,6 +54,7 @@ import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidConfirmationFeedbackException;
|
||||
import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
@@ -91,6 +99,10 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DdiRootController.class);
|
||||
private static final String GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET = "given action ({}) is not assigned to given target ({}).";
|
||||
private static final String FALLBACK_REMARK = "Initiated using the Device Direct Integration API without providing a remark.";
|
||||
|
||||
@Autowired
|
||||
private ConfirmationManagement confirmationManagement;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
@@ -155,12 +167,11 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
|
||||
checkAndCancelExpiredAction(activeAction);
|
||||
|
||||
return new ResponseEntity<>(
|
||||
DataConversionHelper.fromTarget(target, installedAction, activeAction,
|
||||
activeAction == null ? controllerManagement.getPollingTime()
|
||||
: controllerManagement.getPollingTimeForAction(activeAction.getId()),
|
||||
tenantAware),
|
||||
HttpStatus.OK);
|
||||
// activeAction
|
||||
return new ResponseEntity<>(DataConversionHelper.fromTarget(target, installedAction, activeAction,
|
||||
activeAction == null ? controllerManagement.getPollingTime()
|
||||
: controllerManagement.getPollingTimeForAction(activeAction.getId()),
|
||||
tenantAware), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -276,7 +287,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
|
||||
checkAndCancelExpiredAction(action);
|
||||
|
||||
if (!action.isCancelingOrCanceled()) {
|
||||
if (!action.isCancelingOrCanceled() && !action.isWaitingConfirmation()) {
|
||||
|
||||
final DdiDeploymentBase base = generateDdiDeploymentBase(target, action, actionHistoryMessageCount);
|
||||
|
||||
@@ -324,6 +335,10 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
final Target target = findTarget(controllerId);
|
||||
final Action action = findActionForTarget(actionId, target);
|
||||
|
||||
if (action.isWaitingConfirmation()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
if (!action.isActive()) {
|
||||
LOG.warn("Updating action {} with feedback {} not possible since action not active anymore.",
|
||||
action.getId(), feedback.getStatus());
|
||||
@@ -488,28 +503,6 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
|
||||
}
|
||||
|
||||
private DdiDeploymentBase generateDdiDeploymentBase(final Target target, final Action action,
|
||||
final Integer actionHistoryMessageCount) {
|
||||
final List<DdiChunk> chunks = DataConversionHelper.createChunks(target, action, artifactUrlHandler,
|
||||
systemManagement, new ServletServerHttpRequest(requestResponseContextHolder.getHttpServletRequest()),
|
||||
controllerManagement);
|
||||
|
||||
final List<String> actionHistoryMsgs = controllerManagement.getActionHistoryMessages(action.getId(),
|
||||
actionHistoryMessageCount == null ? Integer.parseInt(DdiRestConstants.NO_ACTION_HISTORY)
|
||||
: actionHistoryMessageCount);
|
||||
|
||||
final DdiActionHistory actionHistory = actionHistoryMsgs.isEmpty() ? null
|
||||
: new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs);
|
||||
|
||||
final HandlingType downloadType = calculateDownloadType(action);
|
||||
final HandlingType updateType = calculateUpdateType(action, downloadType);
|
||||
|
||||
final DdiMaintenanceWindowStatus maintenanceWindow = calculateMaintenanceWindow(action);
|
||||
|
||||
return new DdiDeploymentBase(Long.toString(action.getId()),
|
||||
new DdiDeployment(downloadType, updateType, chunks, maintenanceWindow), actionHistory);
|
||||
}
|
||||
|
||||
private static ActionStatusCreate generateActionCancelStatus(final DdiActionFeedback feedback, final Target target,
|
||||
final Long actionId, final EntityFactory entityFactory) {
|
||||
|
||||
@@ -621,4 +614,150 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResponseEntity<DdiConfirmationBaseAction> getConfirmationBaseAction(
|
||||
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
|
||||
@PathVariable("actionId") final Long actionId,
|
||||
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource,
|
||||
@RequestParam(value = "actionHistory", defaultValue = DdiRestConstants.NO_ACTION_HISTORY) final Integer actionHistoryMessageCount) {
|
||||
LOG.debug("getConfirmationBaseAction({},{})", controllerId, resource);
|
||||
|
||||
final Target target = findTarget(controllerId);
|
||||
final Action action = findActionForTarget(actionId, target);
|
||||
|
||||
checkAndCancelExpiredAction(action);
|
||||
|
||||
if (!action.isCancelingOrCanceled() && action.isWaitingConfirmation()) {
|
||||
|
||||
final DdiConfirmationBaseAction base = generateDdiConfirmationBase(target, action,
|
||||
actionHistoryMessageCount);
|
||||
|
||||
LOG.debug("Found an active UpdateAction for target {}. Returning confirmation: {}", controllerId, base);
|
||||
|
||||
return new ResponseEntity<>(base, HttpStatus.OK);
|
||||
}
|
||||
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
private DdiDeploymentBase generateDdiDeploymentBase(final Target target, final Action action,
|
||||
final Integer actionHistoryMessageCount) {
|
||||
final DdiActionHistory actionHistory = generateDdiActionHistory(action, actionHistoryMessageCount).orElse(null);
|
||||
final DdiDeployment ddiDeployment = generateDdiDeployment(target, action);
|
||||
return new DdiDeploymentBase(Long.toString(action.getId()), ddiDeployment, actionHistory);
|
||||
}
|
||||
|
||||
private DdiConfirmationBaseAction generateDdiConfirmationBase(final Target target, final Action action,
|
||||
final Integer actionHistoryMessageCount) {
|
||||
final DdiActionHistory actionHistory = generateDdiActionHistory(action, actionHistoryMessageCount).orElse(null);
|
||||
final DdiDeployment ddiDeployment = generateDdiDeployment(target, action);
|
||||
return new DdiConfirmationBaseAction(Long.toString(action.getId()), ddiDeployment, actionHistory);
|
||||
}
|
||||
|
||||
private DdiDeployment generateDdiDeployment(final Target target, final Action action) {
|
||||
final List<DdiChunk> chunks = DataConversionHelper.createChunks(target, action, artifactUrlHandler,
|
||||
systemManagement, new ServletServerHttpRequest(requestResponseContextHolder.getHttpServletRequest()),
|
||||
controllerManagement);
|
||||
final HandlingType downloadType = calculateDownloadType(action);
|
||||
final HandlingType updateType = calculateUpdateType(action, downloadType);
|
||||
final DdiMaintenanceWindowStatus maintenanceWindow = calculateMaintenanceWindow(action);
|
||||
return new DdiDeployment(downloadType, updateType, chunks, maintenanceWindow);
|
||||
}
|
||||
|
||||
private Optional<DdiActionHistory> generateDdiActionHistory(final Action action,
|
||||
final Integer actionHistoryMessageCount) {
|
||||
final List<String> actionHistoryMsgs = controllerManagement.getActionHistoryMessages(action.getId(),
|
||||
actionHistoryMessageCount == null ? Integer.parseInt(DdiRestConstants.NO_ACTION_HISTORY)
|
||||
: actionHistoryMessageCount);
|
||||
return actionHistoryMsgs.isEmpty() ? Optional.empty()
|
||||
: Optional.of(new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> postConfirmationActionFeedback(
|
||||
@Valid @RequestBody final DdiConfirmationFeedback feedback, @PathVariable("tenant") final String tenant,
|
||||
@PathVariable("controllerId") final String controllerId,
|
||||
@PathVariable("actionId") @NotEmpty final Long actionId) {
|
||||
LOG.debug("provideConfirmationActionFeedback with feedback [controllerId={}, actionId={}]: {}", controllerId,
|
||||
actionId, feedback);
|
||||
|
||||
final Target target = findTarget(controllerId);
|
||||
final Action action = findActionForTarget(actionId, target);
|
||||
|
||||
try {
|
||||
|
||||
switch (feedback.getConfirmation()) {
|
||||
case CONFIRMED:
|
||||
LOG.info("Controller confirmed the action (actionId: {}, controllerId: {}) as we got {} report.",
|
||||
actionId, controllerId, feedback.getConfirmation());
|
||||
confirmationManagement.confirmAction(actionId, feedback.getCode(), feedback.getDetails());
|
||||
break;
|
||||
case DENIED:
|
||||
default:
|
||||
LOG.debug("Controller denied the action (actionId: {}, controllerId: {}) as we got {} report.",
|
||||
actionId, controllerId, feedback.getConfirmation());
|
||||
confirmationManagement.denyAction(actionId, feedback.getCode(), feedback.getDetails());
|
||||
break;
|
||||
}
|
||||
} catch (final InvalidConfirmationFeedbackException e) {
|
||||
if (e.getReason() == InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED) {
|
||||
LOG.warn("Updating action {} with confirmation {} not possible since action not active anymore.",
|
||||
action.getId(), feedback.getConfirmation());
|
||||
return new ResponseEntity<>(HttpStatus.GONE);
|
||||
} else if (e.getReason() == InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION) {
|
||||
LOG.debug("Action is not waiting for confirmation, deny request.");
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<DdiConfirmationBase> getConfirmationBase(final String tenant, final String controllerId) {
|
||||
LOG.debug("getConfirmationBase is called [controllerId={}].", controllerId);
|
||||
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil
|
||||
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
|
||||
final Action activeAction = controllerManagement.findActiveActionWithHighestWeight(controllerId).orElse(null);
|
||||
|
||||
final DdiAutoConfirmationState autoConfirmationState = getAutoConfirmationState(controllerId);
|
||||
|
||||
final DdiConfirmationBase confirmationBase = DataConversionHelper.createConfirmationBase(target, activeAction,
|
||||
autoConfirmationState, tenantAware);
|
||||
return new ResponseEntity<>(confirmationBase, HttpStatus.OK);
|
||||
}
|
||||
|
||||
private DdiAutoConfirmationState getAutoConfirmationState(final String controllerId) {
|
||||
return confirmationManagement.getStatus(controllerId).map(status -> {
|
||||
final DdiAutoConfirmationState state = DdiAutoConfirmationState.active(status.getActivatedAt());
|
||||
state.setInitiator(status.getInitiator());
|
||||
state.setRemark(status.getRemark());
|
||||
LOG.trace("Returning state auto-conf state active [initiator='{}' | activatedAt={}] for device {}",
|
||||
controllerId, status.getInitiator(), status.getActivatedAt());
|
||||
return state;
|
||||
}).orElseGet(() -> {
|
||||
LOG.trace("Returning state auto-conf state disabled for device {}", controllerId);
|
||||
return DdiAutoConfirmationState.disabled();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> activateAutoConfirmation(final String tenant, final String controllerId,
|
||||
final DdiActivateAutoConfirmation body) {
|
||||
final String initiator = body == null ? null : body.getInitiator();
|
||||
final String remark = body == null ? FALLBACK_REMARK : body.getRemark();
|
||||
LOG.debug("Activate auto-confirmation request for device '{}' with payload: [initiator='{}' | remark='{}'",
|
||||
controllerId, initiator, remark);
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, initiator, remark);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> deactivateAutoConfirmation(final String tenant, final String controllerId) {
|
||||
LOG.debug("Deactivate auto-confirmation request for device ‘{}‘", controllerId);
|
||||
confirmationManagement.deactivateAutoConfirmation(controllerId);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiProgress;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
||||
@@ -68,6 +69,13 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
|
||||
protected static final String DEPLOYMENT_FEEDBACK = DEPLOYMENT_BASE + "/feedback";
|
||||
protected static final String CANCEL_FEEDBACK = CANCEL_ACTION + "/feedback";
|
||||
|
||||
protected static final String CONFIRMATION_BASE = CONTROLLER_BASE + "/confirmationBase";
|
||||
protected static final String ACTIVATE_AUTO_CONFIRM = CONFIRMATION_BASE + "/activateAutoConfirm";
|
||||
protected static final String DEACTIVATE_AUTO_CONFIRM = CONFIRMATION_BASE + "/deactivateAutoConfirm";
|
||||
protected static final String CONFIRMATION_BASE_ACTION = CONTROLLER_BASE + "/confirmationBase/{actionId}";
|
||||
|
||||
protected static final String CONFIRMATION_FEEDBACK = CONFIRMATION_BASE_ACTION + "/feedback";
|
||||
|
||||
protected static final int ARTIFACT_SIZE = 5 * 1024;
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@@ -155,7 +163,7 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
|
||||
final Long osModuleId, final String downloadType, final String updateType) throws Exception {
|
||||
final ResultActions resultActions = performGet(DEPLOYMENT_BASE, mediaType, status().isOk(),
|
||||
tenantAware.getCurrentTenant(), controllerId, actionId.toString());
|
||||
return verifyBasePayload(resultActions, controllerId, ds, artifact, artifactSignature, actionId, osModuleId,
|
||||
return verifyBasePayload("$.deployment", resultActions, controllerId, ds, artifact, artifactSignature, actionId, osModuleId,
|
||||
downloadType, updateType);
|
||||
}
|
||||
|
||||
@@ -171,60 +179,60 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
|
||||
final Long osModuleId, final Action.ActionType actionType) throws Exception {
|
||||
final ResultActions resultActions = performGet(INSTALLED_BASE, mediaType, status().isOk(),
|
||||
tenantAware.getCurrentTenant(), controllerId, actionId.toString());
|
||||
return verifyBasePayload(resultActions, controllerId, ds, artifact, artifactSignature, actionId, osModuleId,
|
||||
return verifyBasePayload("$.deployment", resultActions, controllerId, ds, artifact, artifactSignature, actionId, osModuleId,
|
||||
getDownloadAndUploadType(actionType), getDownloadAndUploadType(actionType));
|
||||
}
|
||||
|
||||
private ResultActions verifyBasePayload(final ResultActions resultActions, final String controllerId,
|
||||
private ResultActions verifyBasePayload(final String prefix, final ResultActions resultActions, final String controllerId,
|
||||
final DistributionSet ds, final Artifact artifact, final Artifact artifactSignature, final Long actionId,
|
||||
final Long osModuleId, final String downloadType, final String updateType) throws Exception {
|
||||
return resultActions.andExpect(jsonPath("$.id", equalTo(String.valueOf(actionId))))
|
||||
.andExpect(jsonPath("$.deployment.download", equalTo(downloadType)))
|
||||
.andExpect(jsonPath("$.deployment.update", equalTo(updateType)))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='jvm')].name",
|
||||
.andExpect(jsonPath(prefix + ".download", equalTo(downloadType)))
|
||||
.andExpect(jsonPath(prefix + ".update", equalTo(updateType)))
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].name",
|
||||
contains(ds.findFirstModuleByType(runtimeType).get().getName())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='jvm')].version",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].version",
|
||||
contains(ds.findFirstModuleByType(runtimeType).get().getVersion())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].name",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].name",
|
||||
contains(ds.findFirstModuleByType(osType).get().getName())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].version",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].version",
|
||||
contains(ds.findFirstModuleByType(osType).get().getVersion())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].size", contains(ARTIFACT_SIZE)))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].filename",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].size", contains(ARTIFACT_SIZE)))
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].filename",
|
||||
contains(artifact.getFilename())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].hashes.md5",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.md5",
|
||||
contains(artifact.getMd5Hash())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].hashes.sha1",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.sha1",
|
||||
contains(artifact.getSha1Hash())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0].hashes.sha256",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].hashes.sha256",
|
||||
contains(artifact.getSha256Hash())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0]._links.download-http.href",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.download-http.href",
|
||||
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
|
||||
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href",
|
||||
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
|
||||
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename()
|
||||
+ ".MD5SUM")))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].size", contains(ARTIFACT_SIZE)))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].filename",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].size", contains(ARTIFACT_SIZE)))
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].filename",
|
||||
contains(artifactSignature.getFilename())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].hashes.md5",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.md5",
|
||||
contains(artifactSignature.getMd5Hash())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].hashes.sha1",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.sha1",
|
||||
contains(artifactSignature.getSha1Hash())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1].hashes.sha256",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].hashes.sha256",
|
||||
contains(artifactSignature.getSha256Hash())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1]._links.download-http.href",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.download-http.href",
|
||||
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
|
||||
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href",
|
||||
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
|
||||
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename()
|
||||
+ ".MD5SUM")))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].version",
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].version",
|
||||
contains(ds.findFirstModuleByType(appType).get().getVersion())))
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].metadata").doesNotExist())
|
||||
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].name")
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].metadata").doesNotExist())
|
||||
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].name")
|
||||
.value(ds.findFirstModuleByType(appType).get().getName()));
|
||||
}
|
||||
|
||||
@@ -338,4 +346,24 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
|
||||
messages);
|
||||
return objectMapper.writeValueAsString(new DdiActionFeedback(Instant.now().toString(), ddiStatus));
|
||||
}
|
||||
|
||||
protected String getJsonConfirmationFeedback(final DdiConfirmationFeedback.Confirmation confirmation,
|
||||
final Integer code, final List<String> messages) throws JsonProcessingException {
|
||||
return objectMapper.writeValueAsString(new DdiConfirmationFeedback(confirmation, code, messages));
|
||||
}
|
||||
|
||||
protected static ObjectMapper getMapper(){
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
protected ResultActions getAndVerifyConfirmationBasePayload(final String controllerId, final MediaType mediaType,
|
||||
final DistributionSet ds, final Artifact artifact, final Artifact artifactSignature, final Long actionId,
|
||||
final Long osModuleId, final String downloadType, final String updateType) throws Exception {
|
||||
final ResultActions resultActions = performGet(CONFIRMATION_BASE_ACTION, mediaType, status().isOk(),
|
||||
tenantAware.getCurrentTenant(), controllerId, actionId.toString());
|
||||
return verifyBasePayload("$.confirmation", resultActions, controllerId, ds, artifact, artifactSignature, actionId, osModuleId,
|
||||
downloadType, updateType);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ddi.rest.resource;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Step;
|
||||
import io.qameta.allure.Story;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.RandomUtils;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActivateAutoConfirmation;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.eclipse.hawkbit.repository.jpa.JpaConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Test confirmation base from the controller.
|
||||
*/
|
||||
@Feature("Component Tests - Direct Device Integration API")
|
||||
@Story("Confirmation Action Resource")
|
||||
public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
private static final String DEFAULT_CONTROLLER_ID = "4747";
|
||||
|
||||
@Test
|
||||
@Description("Forced deployment to a controller. Checks if the confirmation resource response payload for a given"
|
||||
+ " deployment is as expected.")
|
||||
public void verifyConfirmationReferencesInControllerBase() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
// Prepare test data
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("", true);
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true);
|
||||
final Artifact artifact = testdataFactory.createArtifact(RandomUtils.nextBytes(ARTIFACT_SIZE), getOsModule(ds),
|
||||
"test1", ARTIFACT_SIZE);
|
||||
final Artifact artifactSignature = testdataFactory.createArtifact(RandomUtils.nextBytes(ARTIFACT_SIZE),
|
||||
getOsModule(ds), "test1.signature", ARTIFACT_SIZE);
|
||||
|
||||
final Target savedTarget = testdataFactory.createTarget(DdiConfirmationBaseTest.DEFAULT_CONTROLLER_ID);
|
||||
assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty();
|
||||
|
||||
final List<Target> targetsAssignedToDs = assignDistributionSet(ds.getId(), savedTarget.getControllerId(),
|
||||
Action.ActionType.FORCED).getAssignedEntity().stream().map(Action::getTarget)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1);
|
||||
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
|
||||
.getContent().get(0);
|
||||
assertThat(deploymentManagement.countActionsAll()).isEqualTo(1);
|
||||
|
||||
assignDistributionSet(ds2, targetsAssignedToDs).getAssignedEntity();
|
||||
assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2);
|
||||
assertThat(deploymentManagement.countActionsAll()).isEqualTo(2);
|
||||
|
||||
final Action uaction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
|
||||
.getContent().get(0);
|
||||
assertThat(uaction.getDistributionSet()).isEqualTo(ds);
|
||||
assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2);
|
||||
|
||||
// Run test
|
||||
final long current = System.currentTimeMillis();
|
||||
final String expectedConfirmationBaseLink = String.format("/%s/controller/v1/%s/confirmationBase/%d",
|
||||
tenantAware.getCurrentTenant(), DEFAULT_CONTROLLER_ID, uaction.getId());
|
||||
|
||||
performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(),
|
||||
DEFAULT_CONTROLLER_ID)
|
||||
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")))
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href",
|
||||
containsString(expectedConfirmationBaseLink)))
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist());
|
||||
|
||||
assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery())
|
||||
.isGreaterThanOrEqualTo(current);
|
||||
assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery())
|
||||
.isLessThanOrEqualTo(System.currentTimeMillis());
|
||||
assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2);
|
||||
|
||||
final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get();
|
||||
|
||||
getAndVerifyConfirmationBasePayload(DEFAULT_CONTROLLER_ID, MediaType.APPLICATION_JSON, ds, artifact,
|
||||
artifactSignature, action.getId(),
|
||||
findDistributionSetByAction.findFirstModuleByType(osType).get().getId(), "forced", "forced");
|
||||
|
||||
// Retrieved is reported
|
||||
final Iterable<ActionStatus> actionStatus = deploymentManagement
|
||||
.findActionStatusByAction(PageRequest.of(0, 100, Sort.Direction.DESC, "id"), uaction.getId());
|
||||
assertThat(actionStatus).hasSize(1)
|
||||
.allMatch(status -> status.getStatus() == Action.Status.WAIT_FOR_CONFIRMATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensure that the deployment resource is available as CBOR")
|
||||
public void confirmationResourceCbor() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
final Target target = testdataFactory.createTarget();
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet("");
|
||||
|
||||
assignDistributionSet(distributionSet.getId(), target.getName());
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
|
||||
.getContent().get(0);
|
||||
|
||||
// get confirmation base
|
||||
performGet(CONFIRMATION_BASE_ACTION, MediaType.parseMediaType(DdiRestConstants.MEDIA_TYPE_CBOR),
|
||||
status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId(), action.getId().toString());
|
||||
|
||||
final Long softwareModuleId = distributionSet.getModules().stream().findAny().get().getId();
|
||||
testdataFactory.createArtifacts(softwareModuleId);
|
||||
// get artifacts
|
||||
performGet(SOFTWARE_MODULE_ARTIFACTS, MediaType.parseMediaType(DdiRestConstants.MEDIA_TYPE_CBOR),
|
||||
status().isOk(), tenantAware.getCurrentTenant(), target.getControllerId(),
|
||||
String.valueOf(softwareModuleId));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensure that the confirmation endpoint is not available.")
|
||||
public void confirmationEndpointNotExposed() throws Exception {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("988");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
|
||||
final String controllerId = savedTarget.getControllerId();
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent()
|
||||
.get(0);
|
||||
|
||||
mvc.perform(
|
||||
get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href").doesNotExist());
|
||||
|
||||
mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), controllerId, savedAction.getId())
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensure that the deploymentBase endpoint is not available for action ins WFC state.")
|
||||
public void deploymentEndpointNotAccessibleForActionsWFC() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("988");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
|
||||
final String controllerId = savedTarget.getControllerId();
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent()
|
||||
.get(0);
|
||||
|
||||
mvc.perform(
|
||||
get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href").exists())
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist());
|
||||
|
||||
mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), controllerId, savedAction.getId())
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, savedAction.getId())
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensure that the confirmation endpoints are still available after deactivating the confirmation flow.")
|
||||
public void verifyConfirmationBaseEndpointsArePresentAfterDisablingConfirmationFlow() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("988");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
|
||||
final String controllerId = savedTarget.getControllerId();
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent()
|
||||
.get(0);
|
||||
|
||||
// disable confirmation flow
|
||||
disableConfirmationFlow();
|
||||
|
||||
// confirmation base should still be exposed
|
||||
verifyActionInConfirmationBaseState(savedTarget.getControllerId(), savedAction.getId());
|
||||
|
||||
// verify confirmation endpoint is still accessible
|
||||
sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.DENIED, 20,
|
||||
"Action denied message.").andExpect(status().isOk());
|
||||
|
||||
// confirmation base should still be exposed
|
||||
verifyActionInConfirmationBaseState(savedTarget.getControllerId(), savedAction.getId());
|
||||
|
||||
// verify confirmation endpoint is still accessible
|
||||
sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.CONFIRMED, 10,
|
||||
"Action confirmed message.").andExpect(status().isOk());
|
||||
|
||||
// assert deployment link is exposed to the target
|
||||
verifyActionInDeploymentBaseState(controllerId, savedAction.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Controller sends a confirmed action state.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1),
|
||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||
void sendConfirmedActionStateFeedbackTest() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("988");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
|
||||
String controllerId = savedTarget.getControllerId();
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent()
|
||||
.get(0);
|
||||
|
||||
sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.CONFIRMED, 10,
|
||||
"Action confirmed message.").andExpect(status().isOk());
|
||||
|
||||
// assert deployment link is exposed to the target
|
||||
verifyActionInDeploymentBaseState(controllerId, savedAction.getId());
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyActionInDeploymentBaseState(final String controllerId, final long actionId) throws Exception {
|
||||
final String expectedDeploymentBaseLink = String.format("/%s/controller/v1/%s/deploymentBase/%d",
|
||||
tenantAware.getCurrentTenant(), controllerId, actionId);
|
||||
|
||||
mvc.perform(
|
||||
get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink)))
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href").doesNotExist());
|
||||
|
||||
// assert that deployment endpoint is working
|
||||
mvc.perform(get(expectedDeploymentBaseLink).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyActionInConfirmationBaseState(final String controllerId, final long actionId) throws Exception {
|
||||
mvc.perform(
|
||||
get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href").exists())
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist());
|
||||
|
||||
mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), controllerId, actionId)
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, actionId)
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Confirmation base provides right values if auto-confirm not active.")
|
||||
void getConfirmationBaseProvidesAutoConfirmStatusNotActive() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget("989").getControllerId();
|
||||
assignDistributionSet(testdataFactory.createDistributionSet("").getId(), controllerId);
|
||||
final long actionId = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent().get(0)
|
||||
.getId();
|
||||
|
||||
final String confirmationBaseActionLink = String.format("/%s/controller/v1/%s/confirmationBase/%d",
|
||||
tenantAware.getCurrentTenant(), controllerId, actionId);
|
||||
|
||||
final String activateAutoConfLink = String.format("/%s/controller/v1/%s/confirmationBase/activateAutoConfirm",
|
||||
tenantAware.getCurrentTenant(), controllerId);
|
||||
|
||||
mvc.perform(
|
||||
get(CONFIRMATION_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("autoConfirm.active", equalTo(Boolean.FALSE)))
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href", containsString(confirmationBaseActionLink)))
|
||||
.andExpect(jsonPath("$._links.activateAutoConfirm.href", containsString(activateAutoConfLink)))
|
||||
.andExpect(jsonPath("$._links.deactivateAutoConfirm").doesNotExist());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("possibleActiveStates")
|
||||
@Description("Confirmation base provides right values if auto-confirm is active.")
|
||||
void getConfirmationBaseProvidesAutoConfirmStatusActive(final String initiator, final String remark)
|
||||
throws Exception {
|
||||
final String controllerId = testdataFactory.createTarget("988").getControllerId();
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, initiator, remark);
|
||||
|
||||
final String deactivateAutoConfLink = String.format(
|
||||
"/%s/controller/v1/%s/confirmationBase/deactivateAutoConfirm", tenantAware.getCurrentTenant(),
|
||||
controllerId);
|
||||
|
||||
mvc.perform(
|
||||
get(CONFIRMATION_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("autoConfirm.active", equalTo(Boolean.TRUE)))
|
||||
.andExpect(initiator == null ? jsonPath("autoConfirm.initiator").doesNotExist()
|
||||
: jsonPath("autoConfirm.initiator", equalTo(initiator)))
|
||||
.andExpect(remark == null ? jsonPath("autoConfirm.remark").doesNotExist()
|
||||
: jsonPath("autoConfirm.remark", equalTo(remark)))
|
||||
.andExpect(jsonPath("$._links.deactivateAutoConfirm.href", containsString(deactivateAutoConfLink)))
|
||||
.andExpect(jsonPath("$._links.activateAutoConfirm").doesNotExist());
|
||||
}
|
||||
|
||||
private static Stream<Arguments> possibleActiveStates() {
|
||||
return Stream.of(Arguments.of("someInitiator", "someRemark"), Arguments.of(null, "someRemark"),
|
||||
Arguments.of("someInitiator", null), Arguments.of(null, null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("possibleActiveStates")
|
||||
@Description("Verify auto-confirm activation is handled correctly.")
|
||||
void activateAutoConfirmation(final String initiator, final String remark) throws Exception {
|
||||
final String controllerId = testdataFactory.createTarget("988").getControllerId();
|
||||
|
||||
final DdiActivateAutoConfirmation body = new DdiActivateAutoConfirmation(initiator, remark);
|
||||
|
||||
mvc.perform(post(ACTIVATE_AUTO_CONFIRM, tenantAware.getCurrentTenant(), controllerId)
|
||||
.content(getMapper().writeValueAsString(body)).contentType(MediaType.APPLICATION_JSON_UTF8))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
assertThat(confirmationManagement.getStatus(controllerId)).hasValueSatisfying(status -> {
|
||||
assertThat(status.getInitiator()).isEqualTo(initiator);
|
||||
assertThat(status.getRemark()).isEqualTo(remark);
|
||||
assertThat(status.getCreatedBy()).isEqualTo("bumlux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify auto-confirm deactivation is handled correctly.")
|
||||
void deactivateAutoConfirmation() throws Exception {
|
||||
final String controllerId = testdataFactory.createTarget("988").getControllerId();
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
|
||||
|
||||
mvc.perform(post(DEACTIVATE_AUTO_CONFIRM, tenantAware.getCurrentTenant(), controllerId))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
assertThat(confirmationManagement.getStatus(controllerId)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Controller sends a denied action state.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1),
|
||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||
void sendDeniedActionStateFeedbackTest() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("989");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
String controllerId = savedTarget.getControllerId();
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent()
|
||||
.get(0);
|
||||
|
||||
sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.DENIED, 10,
|
||||
"Action denied message.").andExpect(status().isOk());
|
||||
|
||||
// asserts that deployment link is not available
|
||||
final String expectedConfirmationBaseLink = String.format("/%s/controller/v1/%s/confirmationBase/%d",
|
||||
tenantAware.getCurrentTenant(), controllerId, savedAction.getId());
|
||||
|
||||
mvc.perform(
|
||||
get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), controllerId).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist())
|
||||
.andExpect(jsonPath("$._links.confirmationBase.href", containsString(expectedConfirmationBaseLink)));
|
||||
|
||||
mvc.perform(get(DEPLOYMENT_BASE, tenantAware.getCurrentTenant(), controllerId, savedAction.getId())
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
private ResultActions sendConfirmationFeedback(final Target target, final Action action,
|
||||
final DdiConfirmationFeedback.Confirmation confirmation, Integer code, String message) throws Exception {
|
||||
|
||||
if (message == null) {
|
||||
message = RandomStringUtils.randomAlphanumeric(1000);
|
||||
}
|
||||
|
||||
final String feedback = getJsonConfirmationFeedback(confirmation, code, Collections.singletonList(message));
|
||||
return mvc.perform(
|
||||
post(CONFIRMATION_FEEDBACK, tenantAware.getCurrentTenant(), target.getControllerId(), action.getId())
|
||||
.content(feedback).contentType(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
|
||||
void testActionHistoryCount() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
Target savedTarget = testdataFactory.createTarget("990");
|
||||
savedTarget = getFirstAssignedTarget(assignDistributionSet(ds.getId(), savedTarget.getControllerId()));
|
||||
|
||||
String controllerId = savedTarget.getControllerId();
|
||||
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, controllerId).getContent()
|
||||
.get(0);
|
||||
final String CONFIRMED_MESSAGE = "Action confirmed message.";
|
||||
final Integer CONFIRMED_CODE = 10;
|
||||
sendConfirmationFeedback(savedTarget, savedAction, DdiConfirmationFeedback.Confirmation.CONFIRMED,
|
||||
CONFIRMED_CODE, CONFIRMED_MESSAGE).andExpect(status().isOk());
|
||||
|
||||
// confirmationBase not available in RUNNING state anymore
|
||||
mvc.perform(get(CONFIRMATION_BASE_ACTION, tenantAware.getCurrentTenant(), savedTarget.getControllerId(),
|
||||
savedAction.getId()).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
|
||||
|
||||
// assert confirmed message against deploymentBase endpoint
|
||||
// this call will update the action due to retrieved action status update
|
||||
mvc.perform(
|
||||
get(DEPLOYMENT_BASE + "?actionHistory=2", tenantAware.getCurrentTenant(), savedTarget.getControllerId(),
|
||||
savedAction.getId()).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.actionHistory.messages", hasItem(containsString(CONFIRMED_MESSAGE))))
|
||||
.andExpect(jsonPath("$.actionHistory.messages",
|
||||
hasItem(containsString(String.format(CONFIRMATION_CODE_MSG_PREFIX, CONFIRMED_CODE)))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public class MgmtTargetAssignmentRequestBody {
|
||||
private MgmtActionType type;
|
||||
private MgmtMaintenanceWindowRequestBody maintenanceWindow;
|
||||
private Integer weight;
|
||||
private Boolean confirmationRequired;
|
||||
|
||||
/**
|
||||
* JsonCreator Constructor
|
||||
@@ -77,4 +78,12 @@ public class MgmtTargetAssignmentRequestBody {
|
||||
public void setMaintenanceWindow(final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
|
||||
this.maintenanceWindow = maintenanceWindow;
|
||||
}
|
||||
|
||||
public Boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.mgmt.json.model.rollout;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
|
||||
@@ -38,6 +39,9 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
|
||||
@JsonProperty(required = false)
|
||||
private Integer weight;
|
||||
|
||||
@JsonProperty(required = false)
|
||||
private Boolean confirmationRequired;
|
||||
|
||||
private MgmtActionType type;
|
||||
|
||||
private List<MgmtRolloutGroup> groups;
|
||||
@@ -161,4 +165,21 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
|
||||
public void setWeight(final Integer weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only considered if confirmation flow active
|
||||
*
|
||||
* @return if the confirmation is required for this rollout
|
||||
*/
|
||||
public Boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param confirmationRequired
|
||||
* if the confirmation is required for this rollout
|
||||
*/
|
||||
public void setConfirmationRequired(final boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public class MgmtRolloutGroup extends AbstractMgmtRolloutConditionsEntity {
|
||||
|
||||
private String targetFilterQuery;
|
||||
private Float targetPercentage;
|
||||
private Boolean confirmationRequired;
|
||||
|
||||
public String getTargetFilterQuery() {
|
||||
return targetFilterQuery;
|
||||
@@ -38,4 +39,12 @@ public class MgmtRolloutGroup extends AbstractMgmtRolloutConditionsEntity {
|
||||
public void setTargetPercentage(Float targetPercentage) {
|
||||
this.targetPercentage = targetPercentage;
|
||||
}
|
||||
|
||||
public Boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final Boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ public class MgmtDistributionSetAssignment extends MgmtId {
|
||||
private long forcetime;
|
||||
@JsonProperty(required = false)
|
||||
private Integer weight;
|
||||
@JsonProperty(required = false)
|
||||
private Boolean confirmationRequired;
|
||||
private MgmtActionType type;
|
||||
private MgmtMaintenanceWindowRequestBody maintenanceWindow;
|
||||
|
||||
@@ -77,4 +79,12 @@ public class MgmtDistributionSetAssignment extends MgmtId {
|
||||
public void setMaintenanceWindow(final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
|
||||
this.maintenanceWindow = maintenanceWindow;
|
||||
}
|
||||
|
||||
public Boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final Boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ public class MgmtTarget extends MgmtNamedEntity {
|
||||
@JsonProperty
|
||||
private String targetTypeName;
|
||||
|
||||
@JsonProperty
|
||||
private Boolean autoConfirmActive;
|
||||
|
||||
/**
|
||||
* @return Target type ID
|
||||
*/
|
||||
@@ -221,4 +224,13 @@ public class MgmtTarget extends MgmtNamedEntity {
|
||||
public void setRequestAttributes(final boolean requestAttributes) {
|
||||
this.requestAttributes = requestAttributes;
|
||||
}
|
||||
|
||||
public Boolean getAutoConfirmActive() {
|
||||
return autoConfirmActive;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setAutoConfirmActive(final boolean autoConfirmActive) {
|
||||
this.autoConfirmActive = autoConfirmActive;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.json.model.target;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Response representing the current state of auto-confirmation for a specific target
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder({ "active", "initiator", "remark", "activatedAt" })
|
||||
public class MgmtTargetAutoConfirm extends RepresentationModel<MgmtTargetAutoConfirm> {
|
||||
@NotNull
|
||||
private boolean active;
|
||||
private String initiator;
|
||||
private String remark;
|
||||
private Long activatedAt;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public MgmtTargetAutoConfirm() {
|
||||
// needed for json create.
|
||||
}
|
||||
|
||||
public static MgmtTargetAutoConfirm active(final long activatedAt) {
|
||||
final MgmtTargetAutoConfirm state = new MgmtTargetAutoConfirm();
|
||||
state.setActive(true);
|
||||
state.setActivatedAt(activatedAt);
|
||||
return state;
|
||||
}
|
||||
|
||||
public static MgmtTargetAutoConfirm disabled() {
|
||||
return new MgmtTargetAutoConfirm();
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(final boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public Long getActivatedAt() {
|
||||
return activatedAt;
|
||||
}
|
||||
|
||||
public void setActivatedAt(final long activatedAt) {
|
||||
this.activatedAt = activatedAt;
|
||||
}
|
||||
|
||||
public String getInitiator() {
|
||||
return initiator;
|
||||
}
|
||||
|
||||
public void setInitiator(final String initiator) {
|
||||
this.initiator = initiator;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(final String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.json.model.target;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Payload to activate the auto-confirmation by given initiator and remark.
|
||||
*/
|
||||
public class MgmtTargetAutoConfirmUpdate {
|
||||
@JsonProperty(required = false)
|
||||
private final String initiator;
|
||||
|
||||
@JsonProperty(required = false)
|
||||
private final String remark;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param initiator
|
||||
* can be null
|
||||
* @param remark
|
||||
* can be null
|
||||
*/
|
||||
@JsonCreator
|
||||
public MgmtTargetAutoConfirmUpdate(@JsonProperty(value = "initiator") final String initiator,
|
||||
@JsonProperty(value = "remark") final String remark) {
|
||||
this.initiator = initiator;
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getInitiator() {
|
||||
return initiator;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MgmtTargetAutoConfirm [initiator=" + initiator + ", remark=" + remark + ", toString()="
|
||||
+ super.toString() + "]";
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@ public class MgmtDistributionSetAutoAssignment extends MgmtId {
|
||||
@JsonProperty(required = false)
|
||||
private Integer weight;
|
||||
|
||||
@JsonProperty(required = false)
|
||||
private Boolean confirmationRequired;
|
||||
|
||||
public MgmtActionType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -40,4 +43,12 @@ public class MgmtDistributionSetAutoAssignment extends MgmtId {
|
||||
public void setWeight(final Integer weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public Boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final Boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity {
|
||||
@JsonProperty
|
||||
private Integer autoAssignWeight;
|
||||
|
||||
@JsonProperty
|
||||
private Boolean confirmationRequired;
|
||||
|
||||
public Long getFilterId() {
|
||||
return filterId;
|
||||
}
|
||||
@@ -90,4 +93,12 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity {
|
||||
public void setAutoAssignWeight(final Integer autoAssignWeight) {
|
||||
this.autoAssignWeight = autoAssignWeight;
|
||||
}
|
||||
|
||||
public Boolean getConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final Boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,21 @@ public final class MgmtRestConstants {
|
||||
*/
|
||||
public static final String TARGET_V1_ASSIGNED_TARGET_TYPE = "targetType";
|
||||
|
||||
/**
|
||||
* The target URL mapping, href link for autoConfirm state of a target.
|
||||
*/
|
||||
public static final String TARGET_V1_AUTO_CONFIRM = "autoConfirm";
|
||||
|
||||
/**
|
||||
* The target URL mapping, href link activate auto-confirm on a target.
|
||||
*/
|
||||
public static final String TARGET_V1_ACTIVATE_AUTO_CONFIRM = "activate";
|
||||
|
||||
/**
|
||||
* The target URL mapping, href link deactivate auto-confirm on a target.
|
||||
*/
|
||||
public static final String TARGET_V1_DEACTIVATE_AUTO_CONFIRM = "deactivate";
|
||||
|
||||
/**
|
||||
* The target URL mapping, href link for assigned distribution set.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignments;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirm;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirmUpdate;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -31,6 +33,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@@ -396,12 +399,47 @@ public interface MgmtTargetRestApi {
|
||||
* the ID of the targetId to create meta data for
|
||||
* @param metadataRest
|
||||
* the list of meta data entries to create
|
||||
* @return status created if post request is successful with the value of
|
||||
* the created meta data
|
||||
* @return status created if post request is successful with the value of the
|
||||
* created meta data
|
||||
*/
|
||||
@PostMapping(value = "/{targetId}/metadata", consumes = { MediaType.APPLICATION_JSON_VALUE,
|
||||
MediaTypes.HAL_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<List<MgmtMetadata>> createMetadata(@PathVariable("targetId") String targetId,
|
||||
List<MgmtMetadata> metadataRest);
|
||||
|
||||
/**
|
||||
* Get the current auto-confirm state for a specific target.
|
||||
*
|
||||
* @param targetId
|
||||
* to check the state for
|
||||
* @return the current state as {@link MgmtTargetAutoConfirm}
|
||||
*/
|
||||
@GetMapping(value = "/{targetId}/autoConfirm", produces = { MediaTypes.HAL_JSON_VALUE,
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<MgmtTargetAutoConfirm> getAutoConfirmStatus(@PathVariable("targetId") String targetId);
|
||||
|
||||
/**
|
||||
* Activate auto-confirm on a specific target.
|
||||
*
|
||||
* @param targetId
|
||||
* to activate auto-confirm on
|
||||
* @param update
|
||||
* properties to update
|
||||
* @return {@link org.springframework.http.HttpStatus#OK} in case of a success
|
||||
*/
|
||||
@PostMapping(value = "/{targetId}/autoConfirm/activate")
|
||||
ResponseEntity<Void> activateAutoConfirm(@PathVariable("targetId") String targetId,
|
||||
@RequestBody(required = false) MgmtTargetAutoConfirmUpdate update);
|
||||
|
||||
/**
|
||||
* Deactivate auto-confirm on a specific target.
|
||||
*
|
||||
* @param targetId
|
||||
* to deactivate auto-confirm on
|
||||
*
|
||||
* @return {@link org.springframework.http.HttpStatus#OK} in case of a success
|
||||
*/
|
||||
@PostMapping(value = "/{targetId}/autoConfirm/deactivate")
|
||||
ResponseEntity<Void> deactivateAutoConfirm(@PathVariable("targetId") String targetId);
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignment;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
|
||||
|
||||
/**
|
||||
@@ -26,38 +25,37 @@ public final class MgmtDeploymentRequestMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert assignment information to an {@link DeploymentRequest}
|
||||
* Convert assignment information to an {@link DeploymentRequestBuilder}
|
||||
*
|
||||
* @param dsAssignment
|
||||
* DS assignment information
|
||||
* @param targetId
|
||||
* target to assign the DS to
|
||||
* @return resulting {@link DeploymentRequest}
|
||||
* @return resulting {@link DeploymentRequestBuilder}
|
||||
*/
|
||||
public static DeploymentRequest createAssignmentRequest(final MgmtDistributionSetAssignment dsAssignment,
|
||||
final String targetId) {
|
||||
public static DeploymentRequestBuilder createAssignmentRequestBuilder(
|
||||
final MgmtDistributionSetAssignment dsAssignment, final String targetId) {
|
||||
|
||||
return createAssignmentRequest(targetId, dsAssignment.getId(), dsAssignment.getType(),
|
||||
return createAssignmentRequestBuilder(targetId, dsAssignment.getId(), dsAssignment.getType(),
|
||||
dsAssignment.getForcetime(), dsAssignment.getWeight(), dsAssignment.getMaintenanceWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert assignment information to an {@link DeploymentRequest}
|
||||
* Convert assignment information to an {@link DeploymentRequestBuilder}
|
||||
*
|
||||
* @param targetAssignment
|
||||
* target assignment information
|
||||
* @param dsId
|
||||
* DS to assign the target to
|
||||
* @return resulting {@link DeploymentRequest}
|
||||
* @return resulting {@link DeploymentRequestBuilder}
|
||||
*/
|
||||
public static DeploymentRequest createAssignmentRequest(final MgmtTargetAssignmentRequestBody targetAssignment,
|
||||
final Long dsId) {
|
||||
|
||||
return createAssignmentRequest(targetAssignment.getId(), dsId, targetAssignment.getType(),
|
||||
public static DeploymentRequestBuilder createAssignmentRequestBuilder(
|
||||
final MgmtTargetAssignmentRequestBody targetAssignment, final Long dsId) {
|
||||
return createAssignmentRequestBuilder(targetAssignment.getId(), dsId, targetAssignment.getType(),
|
||||
targetAssignment.getForcetime(), targetAssignment.getWeight(), targetAssignment.getMaintenanceWindow());
|
||||
}
|
||||
|
||||
private static DeploymentRequest createAssignmentRequest(final String targetId, final Long dsId,
|
||||
private static DeploymentRequestBuilder createAssignmentRequestBuilder(final String targetId, final Long dsId,
|
||||
final MgmtActionType type, final long forcetime, final Integer weight,
|
||||
final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
|
||||
final DeploymentRequestBuilder request = DeploymentManagement.deploymentRequest(targetId, dsId)
|
||||
@@ -69,6 +67,6 @@ public final class MgmtDeploymentRequestMapper {
|
||||
MaintenanceScheduleHelper.validateMaintenanceSchedule(cronSchedule, duration, timezone);
|
||||
request.setMaintenance(cronSchedule, duration, timezone);
|
||||
}
|
||||
return request.build();
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
@@ -51,6 +52,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -88,13 +90,16 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
|
||||
private final DistributionSetInvalidationManagement distributionSetInvalidationManagement;
|
||||
|
||||
private final TenantConfigHelper tenantConfigHelper;
|
||||
|
||||
MgmtDistributionSetResource(final SoftwareModuleManagement softwareModuleManagement,
|
||||
final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final DeploymentManagement deployManagament, final SystemManagement systemManagement,
|
||||
final EntityFactory entityFactory, final DistributionSetManagement distributionSetManagement,
|
||||
final SystemSecurityContext systemSecurityContext,
|
||||
final DistributionSetInvalidationManagement distributionSetInvalidationManagement) {
|
||||
final DistributionSetInvalidationManagement distributionSetInvalidationManagement,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
this.softwareModuleManagement = softwareModuleManagement;
|
||||
this.targetManagement = targetManagement;
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
@@ -104,6 +109,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.distributionSetInvalidationManagement = distributionSetInvalidationManagement;
|
||||
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -203,8 +209,9 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
targetsAssignedDS = this.targetManagement.findByAssignedDistributionSet(pageable, distributionSetId);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponse(targetsAssignedDS.getContent()),
|
||||
targetsAssignedDS.getTotalElements()));
|
||||
return ResponseEntity
|
||||
.ok(new PagedList<>(MgmtTargetMapper.toResponse(targetsAssignedDS.getContent(), tenantConfigHelper),
|
||||
targetsAssignedDS.getTotalElements()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,8 +238,9 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
targetsInstalledDS = this.targetManagement.findByInstalledDistributionSet(pageable, distributionSetId);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponse(targetsInstalledDS.getContent()),
|
||||
targetsInstalledDS.getTotalElements()));
|
||||
return ResponseEntity
|
||||
.ok(new PagedList<>(MgmtTargetMapper.toResponse(targetsInstalledDS.getContent(), tenantConfigHelper),
|
||||
targetsInstalledDS.getTotalElements()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -251,8 +259,8 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
.findByAutoAssignDSAndRsql(pageable, distributionSetId, rsqlParam);
|
||||
|
||||
return ResponseEntity
|
||||
.ok(new PagedList<>(MgmtTargetFilterQueryMapper.toResponse(targetFilterQueries.getContent()),
|
||||
targetFilterQueries.getTotalElements()));
|
||||
.ok(new PagedList<>(MgmtTargetFilterQueryMapper.toResponse(targetFilterQueries.getContent(),
|
||||
tenantConfigHelper.isConfirmationFlowEnabled()), targetFilterQueries.getTotalElements()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -268,9 +276,13 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
.toResponse(deployManagament.offlineAssignedDistributionSets(offlineAssignments)));
|
||||
}
|
||||
|
||||
final List<DeploymentRequest> deploymentRequests = assignments.stream()
|
||||
.map(assignment -> MgmtDeploymentRequestMapper.createAssignmentRequest(assignment, distributionSetId))
|
||||
.collect(Collectors.toList());
|
||||
final List<DeploymentRequest> deploymentRequests = assignments.stream().map(dsAssignment -> {
|
||||
final boolean isConfirmationRequired = dsAssignment.isConfirmationRequired() == null
|
||||
? tenantConfigHelper.isConfirmationFlowEnabled()
|
||||
: dsAssignment.isConfirmationRequired();
|
||||
return MgmtDeploymentRequestMapper.createAssignmentRequestBuilder(dsAssignment, distributionSetId)
|
||||
.setConfirmationRequired(isConfirmationRequired).build();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
final List<DistributionSetAssignmentResult> assignmentResults = deployManagament
|
||||
.assignDistributionSets(deploymentRequests);
|
||||
@@ -302,7 +314,6 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
return ResponseEntity
|
||||
.ok(new PagedList<>(MgmtDistributionSetMapper.toResponseDsMetadata(metaDataPage.getContent()),
|
||||
metaDataPage.getTotalElements()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -157,16 +157,18 @@ final class MgmtRolloutMapper {
|
||||
return conditions.build();
|
||||
}
|
||||
|
||||
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
|
||||
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts,
|
||||
final boolean confirmationFlowEnabled) {
|
||||
if (rollouts == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return rollouts.stream().map(group -> toResponseRolloutGroup(group, false)).collect(Collectors.toList());
|
||||
return rollouts.stream().map(group -> toResponseRolloutGroup(group, false, confirmationFlowEnabled))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static MgmtRolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup,
|
||||
final boolean withDetailedStatus) {
|
||||
final boolean withDetailedStatus, final boolean confirmationFlowEnabled) {
|
||||
final MgmtRolloutGroupResponseBody body = new MgmtRolloutGroupResponseBody();
|
||||
body.setCreatedAt(rolloutGroup.getCreatedAt());
|
||||
body.setCreatedBy(rolloutGroup.getCreatedBy());
|
||||
@@ -180,6 +182,10 @@ final class MgmtRolloutMapper {
|
||||
body.setTargetFilterQuery(rolloutGroup.getTargetFilterQuery());
|
||||
body.setTotalTargets(rolloutGroup.getTotalTargets());
|
||||
|
||||
if (confirmationFlowEnabled) {
|
||||
body.setConfirmationRequired(rolloutGroup.isConfirmationRequired());
|
||||
}
|
||||
|
||||
body.setSuccessCondition(new MgmtRolloutCondition(map(rolloutGroup.getSuccessCondition()),
|
||||
rolloutGroup.getSuccessConditionExp()));
|
||||
body.setSuccessAction(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.ValidationException;
|
||||
@@ -16,6 +17,7 @@ import javax.validation.ValidationException;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
|
||||
@@ -27,6 +29,7 @@ import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
@@ -38,6 +41,8 @@ import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -66,15 +71,19 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
private final TargetFilterQueryManagement targetFilterQueryManagement;
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
private final TenantConfigHelper tenantConfigHelper;
|
||||
|
||||
MgmtRolloutResource(final RolloutManagement rolloutManagement, final RolloutGroupManagement rolloutGroupManagement,
|
||||
final DistributionSetManagement distributionSetManagement,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement, final EntityFactory entityFactory) {
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement, final EntityFactory entityFactory,
|
||||
final SystemSecurityContext systemSecurityContext,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.rolloutGroupManagement = rolloutGroupManagement;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
this.entityFactory = entityFactory;
|
||||
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,18 +141,26 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
final DistributionSet distributionSet = distributionSetManagement
|
||||
.getValidAndComplete(rolloutRequestBody.getDistributionSetId());
|
||||
final RolloutGroupConditions rolloutGroupConditions = MgmtRolloutMapper.fromRequest(rolloutRequestBody, true);
|
||||
|
||||
final RolloutCreate create = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
|
||||
final boolean confirmationFlowActive = tenantConfigHelper.isConfirmationFlowEnabled();
|
||||
|
||||
Rollout rollout;
|
||||
if (rolloutRequestBody.getGroups() != null) {
|
||||
final List<RolloutGroupCreate> rolloutGroups = rolloutRequestBody.getGroups().stream()
|
||||
.map(mgmtRolloutGroup -> MgmtRolloutMapper.fromRequest(entityFactory, mgmtRolloutGroup))
|
||||
.collect(Collectors.toList());
|
||||
.map(mgmtRolloutGroup -> {
|
||||
final boolean confirmationRequired = isConfirmationRequiredForGroup(mgmtRolloutGroup,
|
||||
rolloutRequestBody).orElse(confirmationFlowActive);
|
||||
return MgmtRolloutMapper.fromRequest(entityFactory, mgmtRolloutGroup)
|
||||
.confirmationRequired(confirmationRequired);
|
||||
}).collect(Collectors.toList());
|
||||
rollout = rolloutManagement.create(create, rolloutGroups, rolloutGroupConditions);
|
||||
|
||||
} else if (rolloutRequestBody.getAmountGroups() != null) {
|
||||
rollout = rolloutManagement.create(create, rolloutRequestBody.getAmountGroups(), rolloutGroupConditions);
|
||||
final boolean confirmationRequired = rolloutRequestBody.isConfirmationRequired() == null
|
||||
? confirmationFlowActive
|
||||
: rolloutRequestBody.isConfirmationRequired();
|
||||
rollout = rolloutManagement.create(create, rolloutRequestBody.getAmountGroups(), confirmationRequired,
|
||||
rolloutGroupConditions);
|
||||
|
||||
} else {
|
||||
throw new ValidationException("Either 'amountGroups' or 'groups' must be defined in the request");
|
||||
@@ -152,6 +169,16 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout, true));
|
||||
}
|
||||
|
||||
private Optional<Boolean> isConfirmationRequiredForGroup(final MgmtRolloutGroup group,
|
||||
final MgmtRolloutRestRequestBody request) {
|
||||
if (group.isConfirmationRequired() != null) {
|
||||
return Optional.of(group.isConfirmationRequired());
|
||||
} else if (request.isConfirmationRequired() != null) {
|
||||
return Optional.of(request.isConfirmationRequired());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> approve(@PathVariable("rolloutId") final Long rolloutId, final String remark) {
|
||||
rolloutManagement.approveOrDeny(rolloutId, Rollout.ApprovalDecision.APPROVED, remark);
|
||||
@@ -209,7 +236,7 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
}
|
||||
|
||||
final List<MgmtRolloutGroupResponseBody> rest = MgmtRolloutMapper
|
||||
.toResponseRolloutGroup(findRolloutGroupsAll.getContent());
|
||||
.toResponseRolloutGroup(findRolloutGroupsAll.getContent(), tenantConfigHelper.isConfirmationFlowEnabled());
|
||||
return ResponseEntity.ok(new PagedList<>(rest, findRolloutGroupsAll.getTotalElements()));
|
||||
}
|
||||
|
||||
@@ -220,7 +247,8 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
|
||||
final RolloutGroup rolloutGroup = rolloutGroupManagement.getWithDetailedStatus(groupId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, rolloutId));
|
||||
return ResponseEntity.ok(MgmtRolloutMapper.toResponseRolloutGroup(rolloutGroup, true));
|
||||
return ResponseEntity.ok(MgmtRolloutMapper.toResponseRolloutGroup(rolloutGroup, true,
|
||||
tenantConfigHelper.isConfirmationFlowEnabled()));
|
||||
}
|
||||
|
||||
private void findRolloutOrThrowException(final Long rolloutId) {
|
||||
@@ -251,7 +279,7 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
final Page<Target> pageTargets = this.rolloutGroupManagement.findTargetsOfRolloutGroup(pageable, groupId);
|
||||
rolloutGroupTargets = pageTargets;
|
||||
}
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent());
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent(), tenantConfigHelper);
|
||||
return ResponseEntity.ok(new PagedList<>(rest, rolloutGroupTargets.getTotalElements()));
|
||||
}
|
||||
|
||||
|
||||
@@ -38,14 +38,15 @@ public final class MgmtTargetFilterQueryMapper {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
static List<MgmtTargetFilterQuery> toResponse(final List<TargetFilterQuery> filters) {
|
||||
static List<MgmtTargetFilterQuery> toResponse(final List<TargetFilterQuery> filters,
|
||||
final boolean confirmationFlowEnabled) {
|
||||
if (CollectionUtils.isEmpty(filters)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return filters.stream().map(MgmtTargetFilterQueryMapper::toResponse).collect(Collectors.toList());
|
||||
return filters.stream().map(filter -> toResponse(filter, confirmationFlowEnabled)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static MgmtTargetFilterQuery toResponse(final TargetFilterQuery filter) {
|
||||
static MgmtTargetFilterQuery toResponse(final TargetFilterQuery filter, final boolean confirmationFlowEnabled) {
|
||||
final MgmtTargetFilterQuery targetRest = new MgmtTargetFilterQuery();
|
||||
targetRest.setFilterId(filter.getId());
|
||||
targetRest.setName(filter.getName());
|
||||
@@ -62,6 +63,9 @@ public final class MgmtTargetFilterQueryMapper {
|
||||
targetRest.setAutoAssignDistributionSet(distributionSet.getId());
|
||||
targetRest.setAutoAssignActionType(MgmtRestModelMapper.convertActionType(filter.getAutoAssignActionType()));
|
||||
filter.getAutoAssignWeight().ifPresent(targetRest::setAutoAssignWeight);
|
||||
if (confirmationFlowEnabled) {
|
||||
targetRest.setConfirmationRequired(filter.isConfirmationRequired());
|
||||
}
|
||||
}
|
||||
|
||||
targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withSelfRel());
|
||||
|
||||
@@ -20,9 +20,13 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -46,18 +50,23 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
|
||||
private final TargetFilterQueryManagement filterManagement;
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
|
||||
private final TenantConfigHelper tenantConfigHelper;
|
||||
|
||||
MgmtTargetFilterQueryResource(final TargetFilterQueryManagement filterManagement,
|
||||
final EntityFactory entityFactory) {
|
||||
MgmtTargetFilterQueryResource(final TargetFilterQueryManagement filterManagement, final EntityFactory entityFactory,
|
||||
final SystemSecurityContext systemSecurityContext,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
this.filterManagement = filterManagement;
|
||||
this.entityFactory = entityFactory;
|
||||
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<MgmtTargetFilterQuery> getFilter(@PathVariable("filterId") final Long filterId) {
|
||||
final TargetFilterQuery findTarget = findFilterWithExceptionIfNotFound(filterId);
|
||||
// to single response include poll status
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(findTarget);
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(findTarget,
|
||||
tenantConfigHelper.isConfirmationFlowEnabled());
|
||||
MgmtTargetFilterQueryMapper.addLinks(response);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
@@ -87,7 +96,7 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
|
||||
}
|
||||
|
||||
final List<MgmtTargetFilterQuery> rest = MgmtTargetFilterQueryMapper
|
||||
.toResponse(findTargetFiltersAll.getContent());
|
||||
.toResponse(findTargetFiltersAll.getContent(), tenantConfigHelper.isConfirmationFlowEnabled());
|
||||
return ResponseEntity.ok(new PagedList<>(rest, countTargetsAll));
|
||||
}
|
||||
|
||||
@@ -97,7 +106,8 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
|
||||
final TargetFilterQuery createdTarget = filterManagement
|
||||
.create(MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filter));
|
||||
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(createdTarget);
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(createdTarget,
|
||||
tenantConfigHelper.isConfirmationFlowEnabled());
|
||||
MgmtTargetFilterQueryMapper.addLinks(response);
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.CREATED);
|
||||
@@ -112,7 +122,8 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
|
||||
.update(entityFactory.targetFilterQuery().update(filterId).name(targetFilterRest.getName())
|
||||
.query(targetFilterRest.getQuery()));
|
||||
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter);
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter,
|
||||
tenantConfigHelper.isConfirmationFlowEnabled());
|
||||
MgmtTargetFilterQueryMapper.addLinks(response);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
@@ -130,10 +141,17 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
|
||||
@PathVariable("filterId") final Long filterId,
|
||||
@RequestBody final MgmtDistributionSetAutoAssignment autoAssignRequest) {
|
||||
|
||||
final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDS(
|
||||
MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filterId, autoAssignRequest));
|
||||
final boolean confirmationRequired = autoAssignRequest.isConfirmationRequired() == null
|
||||
? tenantConfigHelper.isConfirmationFlowEnabled()
|
||||
: autoAssignRequest.isConfirmationRequired();
|
||||
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter);
|
||||
final AutoAssignDistributionSetUpdate update = MgmtTargetFilterQueryMapper
|
||||
.fromRequest(entityFactory, filterId, autoAssignRequest).confirmationRequired(confirmationRequired);
|
||||
|
||||
final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDS(update);
|
||||
|
||||
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter,
|
||||
tenantConfigHelper.isConfirmationFlowEnabled());
|
||||
MgmtTargetFilterQueryMapper.addLinks(response);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirm;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
@@ -40,6 +41,7 @@ import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.model.MetaData;
|
||||
import org.eclipse.hawkbit.repository.model.PollStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
@@ -48,6 +50,7 @@ import org.eclipse.hawkbit.repository.model.TargetMetadata;
|
||||
import org.eclipse.hawkbit.rest.data.ResponseList;
|
||||
import org.eclipse.hawkbit.rest.data.SortDirection;
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
/**
|
||||
@@ -85,6 +88,27 @@ public final class MgmtTargetMapper {
|
||||
response.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(response.getTargetType()))
|
||||
.withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_TARGET_TYPE));
|
||||
}
|
||||
if (response.getAutoConfirmActive() != null) {
|
||||
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getAutoConfirmStatus(response.getControllerId()))
|
||||
.withRel(MgmtRestConstants.TARGET_V1_AUTO_CONFIRM));
|
||||
}
|
||||
}
|
||||
|
||||
public static MgmtTargetAutoConfirm getTargetAutoConfirmResponse(final Target target) {
|
||||
final AutoConfirmationStatus status = target.getAutoConfirmationStatus();
|
||||
final MgmtTargetAutoConfirm response;
|
||||
if (status != null) {
|
||||
response = MgmtTargetAutoConfirm.active(status.getActivatedAt());
|
||||
response.setInitiator(status.getInitiator());
|
||||
response.setRemark(status.getRemark());
|
||||
response.add(linkTo(methodOn(MgmtTargetRestApi.class).deactivateAutoConfirm(target.getControllerId()))
|
||||
.withRel(MgmtRestConstants.TARGET_V1_DEACTIVATE_AUTO_CONFIRM));
|
||||
} else {
|
||||
response = MgmtTargetAutoConfirm.disabled();
|
||||
response.add(linkTo(methodOn(MgmtTargetRestApi.class).activateAutoConfirm(target.getControllerId(), null))
|
||||
.withRel(MgmtRestConstants.TARGET_V1_ACTIVATE_AUTO_CONFIRM));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
static void addPollStatus(final Target target, final MgmtTarget targetRest) {
|
||||
@@ -107,12 +131,13 @@ public final class MgmtTargetMapper {
|
||||
* list of targets
|
||||
* @return the response
|
||||
*/
|
||||
public static List<MgmtTarget> toResponse(final Collection<Target> targets) {
|
||||
public static List<MgmtTarget> toResponse(final Collection<Target> targets, final TenantConfigHelper configHelper) {
|
||||
if (targets == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return new ResponseList<>(targets.stream().map(MgmtTargetMapper::toResponse).collect(Collectors.toList()));
|
||||
return new ResponseList<>(
|
||||
targets.stream().map(target -> toResponse(target, configHelper)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +147,7 @@ public final class MgmtTargetMapper {
|
||||
* the target
|
||||
* @return the response
|
||||
*/
|
||||
public static MgmtTarget toResponse(final Target target) {
|
||||
public static MgmtTarget toResponse(final Target target, final TenantConfigHelper configHelper) {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -163,6 +188,9 @@ public final class MgmtTargetMapper {
|
||||
targetRest.setTargetType(target.getTargetType().getId());
|
||||
targetRest.setTargetTypeName(target.getTargetType().getName());
|
||||
}
|
||||
if (configHelper.isConfirmationFlowEnabled()) {
|
||||
targetRest.setAutoConfirmActive(target.getAutoConfirmationStatus() != null);
|
||||
}
|
||||
|
||||
targetRest.add(linkTo(methodOn(MgmtTargetRestApi.class).getTarget(target.getControllerId())).withSelfRel());
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.Valid;
|
||||
@@ -31,13 +32,17 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignments;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirm;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirmUpdate;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi;
|
||||
import org.eclipse.hawkbit.repository.ConfirmationManagement;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
@@ -45,6 +50,8 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetMetadata;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -69,22 +76,30 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
||||
|
||||
private final TargetManagement targetManagement;
|
||||
|
||||
private final ConfirmationManagement confirmationManagement;
|
||||
|
||||
private final DeploymentManagement deploymentManagement;
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
|
||||
private final TenantConfigHelper tenantConfigHelper;
|
||||
|
||||
MgmtTargetResource(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||
final EntityFactory entityFactory) {
|
||||
final ConfirmationManagement confirmationManagement, final EntityFactory entityFactory,
|
||||
final SystemSecurityContext systemSecurityContext,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
this.targetManagement = targetManagement;
|
||||
this.deploymentManagement = deploymentManagement;
|
||||
this.confirmationManagement = confirmationManagement;
|
||||
this.entityFactory = entityFactory;
|
||||
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<MgmtTarget> getTarget(@PathVariable("targetId") final String targetId) {
|
||||
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
|
||||
// to single response include poll status
|
||||
final MgmtTarget response = MgmtTargetMapper.toResponse(findTarget);
|
||||
final MgmtTarget response = MgmtTargetMapper.toResponse(findTarget, tenantConfigHelper);
|
||||
MgmtTargetMapper.addPollStatus(findTarget, response);
|
||||
MgmtTargetMapper.addTargetLinks(response);
|
||||
|
||||
@@ -113,7 +128,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
||||
countTargetsAll = targetManagement.count();
|
||||
}
|
||||
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(findTargetsAll.getContent());
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(findTargetsAll.getContent(), tenantConfigHelper);
|
||||
return ResponseEntity.ok(new PagedList<>(rest, countTargetsAll));
|
||||
}
|
||||
|
||||
@@ -123,7 +138,8 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
||||
final Collection<Target> createdTargets = this.targetManagement
|
||||
.create(MgmtTargetMapper.fromRequest(entityFactory, targets));
|
||||
LOG.debug("{} targets created, return status {}", targets.size(), HttpStatus.CREATED);
|
||||
return new ResponseEntity<>(MgmtTargetMapper.toResponse(createdTargets), HttpStatus.CREATED);
|
||||
return new ResponseEntity<>(MgmtTargetMapper.toResponse(createdTargets, tenantConfigHelper),
|
||||
HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,7 +159,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
||||
.targetType(targetRest.getTargetType()).securityToken(targetRest.getSecurityToken())
|
||||
.requestAttributes(targetRest.isRequestAttributes()));
|
||||
|
||||
final MgmtTarget response = MgmtTargetMapper.toResponse(updateTarget);
|
||||
final MgmtTarget response = MgmtTargetMapper.toResponse(updateTarget, tenantConfigHelper);
|
||||
MgmtTargetMapper.addPollStatus(updateTarget, response);
|
||||
MgmtTargetMapper.addTargetLinks(response);
|
||||
|
||||
@@ -309,9 +325,13 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
||||
}
|
||||
findTargetWithExceptionIfNotFound(targetId);
|
||||
|
||||
final List<DeploymentRequest> deploymentRequests = dsAssignments.stream()
|
||||
.map(dsAssignment -> MgmtDeploymentRequestMapper.createAssignmentRequest(dsAssignment, targetId))
|
||||
.collect(Collectors.toList());
|
||||
final List<DeploymentRequest> deploymentRequests = dsAssignments.stream().map(dsAssignment -> {
|
||||
final boolean isConfirmationRequired = dsAssignment.isConfirmationRequired() == null
|
||||
? tenantConfigHelper.isConfirmationFlowEnabled()
|
||||
: dsAssignment.isConfirmationRequired();
|
||||
return MgmtDeploymentRequestMapper.createAssignmentRequestBuilder(dsAssignment, targetId)
|
||||
.setConfirmationRequired(isConfirmationRequired).build();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
final List<DistributionSetAssignmentResult> assignmentResults = deploymentManagement
|
||||
.assignDistributionSets(deploymentRequests);
|
||||
@@ -416,4 +436,30 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
||||
return new ResponseEntity<>(MgmtTargetMapper.toResponseTargetMetadata(created), HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<MgmtTargetAutoConfirm> getAutoConfirmStatus(@PathVariable("targetId") final String targetId) {
|
||||
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
|
||||
final MgmtTargetAutoConfirm state = MgmtTargetMapper.getTargetAutoConfirmResponse(findTarget);
|
||||
return ResponseEntity.ok(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> activateAutoConfirm(@PathVariable("targetId") final String targetId,
|
||||
@RequestBody(required = false) final MgmtTargetAutoConfirmUpdate update) {
|
||||
final String initiator = getNullIfEmpty(update, MgmtTargetAutoConfirmUpdate::getInitiator);
|
||||
final String remark = getNullIfEmpty(update, MgmtTargetAutoConfirmUpdate::getRemark);
|
||||
confirmationManagement.activateAutoConfirmation(targetId, initiator, remark);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
private <T, R> R getNullIfEmpty(final T object, final Function<T, R> extractMethod) {
|
||||
return object == null ? null : extractMethod.apply(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> deactivateAutoConfirm(@PathVariable("targetId") final String targetId) {
|
||||
confirmationManagement.deactivateAutoConfirmation(targetId);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@ import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -54,11 +57,15 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi {
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
|
||||
private final TenantConfigHelper tenantConfigHelper;
|
||||
|
||||
MgmtTargetTagResource(final TargetTagManagement tagManagement, final TargetManagement targetManagement,
|
||||
final EntityFactory entityFactory) {
|
||||
final EntityFactory entityFactory, final SystemSecurityContext securityContext,
|
||||
final TenantConfigurationManagement configurationManagement) {
|
||||
this.tagManagement = tagManagement;
|
||||
this.targetManagement = targetManagement;
|
||||
this.entityFactory = entityFactory;
|
||||
this.tenantConfigHelper = TenantConfigHelper.usingContext(securityContext, configurationManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -149,7 +156,7 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi {
|
||||
findTargetsAll = targetManagement.findByRsqlAndTag(pageable, rsqlParam, targetTagId);
|
||||
}
|
||||
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(findTargetsAll.getContent());
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(findTargetsAll.getContent(), tenantConfigHelper);
|
||||
return ResponseEntity.ok(new PagedList<>(rest, findTargetsAll.getTotalElements()));
|
||||
}
|
||||
|
||||
@@ -164,8 +171,10 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi {
|
||||
.toggleTagAssignment(findTargetControllerIds(assignedTargetRequestBodies), targetTag.getName());
|
||||
|
||||
final MgmtTargetTagAssigmentResult tagAssigmentResultRest = new MgmtTargetTagAssigmentResult();
|
||||
tagAssigmentResultRest.setAssignedTargets(MgmtTargetMapper.toResponse(assigmentResult.getAssignedEntity()));
|
||||
tagAssigmentResultRest.setUnassignedTargets(MgmtTargetMapper.toResponse(assigmentResult.getUnassignedEntity()));
|
||||
tagAssigmentResultRest.setAssignedTargets(
|
||||
MgmtTargetMapper.toResponse(assigmentResult.getAssignedEntity(), tenantConfigHelper));
|
||||
tagAssigmentResultRest.setUnassignedTargets(
|
||||
MgmtTargetMapper.toResponse(assigmentResult.getUnassignedEntity(), tenantConfigHelper));
|
||||
return ResponseEntity.ok(tagAssigmentResultRest);
|
||||
}
|
||||
|
||||
@@ -175,7 +184,7 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi {
|
||||
LOG.debug("Assign Targets {} for target tag {}", assignedTargetRequestBodies.size(), targetTagId);
|
||||
final List<Target> assignedTarget = this.targetManagement
|
||||
.assignTag(findTargetControllerIds(assignedTargetRequestBodies), targetTagId);
|
||||
return ResponseEntity.ok(MgmtTargetMapper.toResponse(assignedTarget));
|
||||
return ResponseEntity.ok(MgmtTargetMapper.toResponse(assignedTarget, tenantConfigHelper));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
@@ -54,6 +55,9 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@@ -143,7 +147,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
|
||||
// create Targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2" };
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, knownTargetIds);
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, null, knownTargetIds);
|
||||
// assign DisSet to target and test assignment
|
||||
assignDistributionSet(disSet.getId(), knownTargetIds[0]);
|
||||
mvc.perform(
|
||||
@@ -257,7 +261,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
|
||||
// prepare targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, knownTargetIds);
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, null, knownTargetIds);
|
||||
// assign already one target to DS
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
||||
|
||||
@@ -378,7 +382,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
final DistributionSet createdDs = testdataFactory.createDistributionSet();
|
||||
// prepare targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
||||
final JSONArray list = createTargetAndJsonArray(getTestSchedule(0), null, null, null, knownTargetIds);
|
||||
final JSONArray list = createTargetAndJsonArray(getTestSchedule(0), null, null, null, null, knownTargetIds);
|
||||
// assign already one target to DS
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
||||
|
||||
@@ -395,7 +399,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
final DistributionSet createdDs = testdataFactory.createDistributionSet();
|
||||
// prepare targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
||||
final JSONArray list = createTargetAndJsonArray(null, getTestDuration(10), null, null, knownTargetIds);
|
||||
final JSONArray list = createTargetAndJsonArray(null, getTestDuration(10), null, null, null, knownTargetIds);
|
||||
// assign already one target to DS
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
||||
|
||||
@@ -413,7 +417,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
// prepare targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
||||
final JSONArray list = createTargetAndJsonArray(getTestSchedule(10), getTestDuration(10), getTestTimeZone(),
|
||||
null, knownTargetIds);
|
||||
null, null, knownTargetIds);
|
||||
// assign already one target to DS
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
||||
|
||||
@@ -431,7 +435,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
// prepare targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
||||
final JSONArray list = createTargetAndJsonArray(getTestSchedule(-30), getTestDuration(5), getTestTimeZone(),
|
||||
null, knownTargetIds);
|
||||
null, null, knownTargetIds);
|
||||
// assign already one target to DS
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
||||
|
||||
@@ -474,7 +478,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
final DistributionSet createdDs = testdataFactory.createDistributionSet();
|
||||
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3" };
|
||||
final JSONArray assignTargetJson = createTargetAndJsonArray(null, null, null, "forced", knownTargetIds);
|
||||
final JSONArray assignTargetJson = createTargetAndJsonArray(null, null, null, "forced", null, knownTargetIds);
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
||||
|
||||
assignTargetJson.put(new JSONObject().put("id", "notexistingtarget").put("type", "forced"));
|
||||
@@ -487,7 +491,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
}
|
||||
|
||||
private JSONArray createTargetAndJsonArray(final String schedule, final String duration, final String timezone,
|
||||
final String type, final String... targetIds) throws Exception {
|
||||
final String type, final Boolean confirmationRequired, final String... targetIds) throws Exception {
|
||||
final JSONArray result = new JSONArray();
|
||||
for (final String targetId : targetIds) {
|
||||
testdataFactory.createTarget(targetId);
|
||||
@@ -501,7 +505,6 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
|
||||
if (schedule != null || duration != null || timezone != null) {
|
||||
final JSONObject maintenanceJsonObject = new JSONObject();
|
||||
targetJsonObject.put("maintenanceWindow", maintenanceJsonObject);
|
||||
if (schedule != null) {
|
||||
maintenanceJsonObject.put("schedule", schedule);
|
||||
}
|
||||
@@ -511,6 +514,10 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
if (timezone != null) {
|
||||
maintenanceJsonObject.put("timezone", timezone);
|
||||
}
|
||||
targetJsonObject.put("maintenanceWindow", maintenanceJsonObject);
|
||||
}
|
||||
if (confirmationRequired != null) {
|
||||
targetJsonObject.put("confirmationRequired", confirmationRequired);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1263,7 +1270,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
|
||||
// prepare targets
|
||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, knownTargetIds);
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, null, knownTargetIds);
|
||||
// assign already one target to DS
|
||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0], Action.ActionType.DOWNLOAD_ONLY);
|
||||
|
||||
@@ -1277,6 +1284,41 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
.as("Five targets in repository have DS assigned").hasSize(5);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Ensures that confirmation option is considered in assignment request.")
|
||||
public void assignTargetsToDistributionSetWithConfirmationOptions(final boolean confirmationFlowActive,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
final DistributionSet createdDs = testdataFactory.createDistributionSet();
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
// prepare targets
|
||||
final String targetId = "1";
|
||||
final JSONArray list = createTargetAndJsonArray(null, null, null, null, confirmationRequired, targetId);
|
||||
|
||||
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", createdDs.getId())
|
||||
.contentType(MediaType.APPLICATION_JSON).content(list.toString())).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.assigned", equalTo(1))).andExpect(jsonPath("$.alreadyAssigned", equalTo(0)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)));
|
||||
|
||||
assertThat(deploymentManagement.findActionsByDistributionSet(PAGE, createdDs.getId()).getContent()).hasSize(1)
|
||||
.allMatch(action -> {
|
||||
if (!confirmationFlowActive) {
|
||||
return !action.isWaitingConfirmation();
|
||||
}
|
||||
return confirmationRequired == null ? action.isWaitingConfirmation()
|
||||
: confirmationRequired == action.isWaitingConfirmation();
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<Arguments> confirmationOptions() {
|
||||
return Stream.of(Arguments.of(true, true), Arguments.of(true, false), Arguments.of(false, true),
|
||||
Arguments.of(false, false), Arguments.of(true, null), Arguments.of(false, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("A request for assigning a target multiple times results in a Bad Request when multiassignment is disabled.")
|
||||
public void multiassignmentRequestNotAllowedIfDisabled() throws Exception {
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
@@ -52,6 +53,10 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
@@ -209,7 +214,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
.content(JsonBuilder.rolloutWithGroups("rollout2", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
|
||||
@@ -239,7 +244,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
.content(JsonBuilder.rolloutWithGroups("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest())
|
||||
@@ -270,7 +275,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
.content(JsonBuilder.rolloutWithGroups("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest())
|
||||
@@ -296,13 +301,11 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
// create rollout including the created targets with prefix 'rollout'
|
||||
final Rollout rollout = rolloutManagement.create(entityFactory.rollout()
|
||||
.create()
|
||||
.name("rollout1")
|
||||
.set(dsA.getId())
|
||||
.targetFilterQuery("controllerId==rollout*"), 4, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100")
|
||||
.build());
|
||||
final Rollout rollout = rolloutManagement.create(
|
||||
entityFactory.rollout().create().name("rollout1").set(dsA.getId())
|
||||
.targetFilterQuery("controllerId==rollout*"),
|
||||
4, false, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
|
||||
retrieveAndVerifyRolloutInCreating(dsA, rollout);
|
||||
retrieveAndVerifyRolloutInReady(rollout);
|
||||
@@ -310,6 +313,134 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
retrieveAndVerifyRolloutInRunning(rollout);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
@Description("Verify the confirmation required flag is not part of the rollout parent entity")
|
||||
void verifyConfirmationFlagIsNeverPartOfRolloutEntity(final boolean confirmationFlowActive) throws Exception {
|
||||
testdataFactory.createTargets(20, "rollout", "rollout");
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
// create rollout including the created targets with prefix 'rollout'
|
||||
final Rollout rollout = rolloutManagement.create(
|
||||
entityFactory.rollout().create().name("rollout1").set(dsA.getId())
|
||||
.targetFilterQuery("controllerId==rollout*"),
|
||||
4, false, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
|
||||
mvc.perform(get("/rest/v1/rollouts/" + rollout.getId()).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
|
||||
.andExpect(jsonPath("$.id", equalTo(rollout.getId().intValue())))
|
||||
.andExpect(jsonPath("$.confirmationRequired").doesNotExist());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
@Description("Verify the confirmation required flag will be set based on the feature state")
|
||||
void verifyConfirmationStateIfNotProvided(final boolean confirmationFlowActive) throws Exception {
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
testdataFactory.createTargets(20, "target", "rollout");
|
||||
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
postRollout("rollout1", 5, dsA.getId(), "id==target*", 20, Action.ActionType.FORCED);
|
||||
|
||||
final List<Rollout> content = rolloutManagement.findAll(PAGE, false).getContent();
|
||||
assertThat(content).hasSizeGreaterThan(0).allSatisfy(rollout -> {
|
||||
assertThat(rolloutGroupManagement.findByRollout(PAGE, rollout.getId()))
|
||||
.describedAs("Confirmation required flag depends on feature active.")
|
||||
.allMatch(group -> group.isConfirmationRequired() == confirmationFlowActive);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Confirmation required flag will be read from the Rollout, if specified.")
|
||||
void verifyRolloutGroupWillUseRolloutPropertyFirst() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("ro");
|
||||
|
||||
final int amountTargets = 10;
|
||||
testdataFactory.createTargets(amountTargets, "ro-target", "rollout");
|
||||
|
||||
final float percentTargetsInGroup1 = 20;
|
||||
final float percentTargetsInGroup2 = 100;
|
||||
|
||||
final RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
|
||||
final List<String> rolloutGroups = Arrays.asList(
|
||||
JsonBuilder.rolloutGroup("Group1", "Group1desc", null, percentTargetsInGroup1, true,
|
||||
rolloutGroupConditions),
|
||||
JsonBuilder.rolloutGroup("Group2", "Group1desc", null, percentTargetsInGroup2, null,
|
||||
rolloutGroupConditions));
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups, null, null, false))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
|
||||
|
||||
final List<Rollout> content = rolloutManagement.findAll(PAGE, false).getContent();
|
||||
assertThat(content).hasSize(1).allSatisfy(rollout -> {
|
||||
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent();
|
||||
assertThat(groups).hasSize(2).allMatch(group -> {
|
||||
if (group.getName().equals("Group1")) {
|
||||
return group.isConfirmationRequired();
|
||||
} else if (group.getName().equals("Group2")) {
|
||||
return !group.isConfirmationRequired();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Confirmation required flag will be read from the tenant config (confirmation flow state), if never specified.")
|
||||
void verifyRolloutGroupWillUseConfigIfNotProvidedWithRollout() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("ro");
|
||||
|
||||
final int amountTargets = 10;
|
||||
testdataFactory.createTargets(amountTargets, "ro-target", "rollout");
|
||||
|
||||
final float percentTargetsInGroup1 = 20;
|
||||
final float percentTargetsInGroup2 = 100;
|
||||
|
||||
final RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
|
||||
final List<String> rolloutGroups = Arrays.asList(
|
||||
JsonBuilder.rolloutGroup("Group1", "Group1desc", null, percentTargetsInGroup1, false,
|
||||
rolloutGroupConditions),
|
||||
JsonBuilder.rolloutGroup("Group2", "Group1desc", null, percentTargetsInGroup2, null,
|
||||
rolloutGroupConditions));
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups, null, null, null))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
|
||||
|
||||
final List<Rollout> content = rolloutManagement.findAll(PAGE, false).getContent();
|
||||
assertThat(content).hasSize(1).allSatisfy(rollout -> {
|
||||
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent();
|
||||
assertThat(groups).hasSize(2).allMatch(group -> {
|
||||
if (group.getName().equals("Group1")) {
|
||||
return !group.isConfirmationRequired();
|
||||
} else if (group.getName().equals("Group2")) {
|
||||
return group.isConfirmationRequired();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Step
|
||||
private void retrieveAndVerifyRolloutInRunning(final Rollout rollout) throws Exception {
|
||||
rolloutManagement.handleRollouts();
|
||||
@@ -459,16 +590,22 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.content", hasSize(1))).andExpect(jsonPath("$.total", equalTo(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Testing that rollout paged list is limited by the query param limit")
|
||||
void retrieveRolloutGroupsForSpecificRollout() throws Exception {
|
||||
void retrieveRolloutGroupsForSpecificRollout(final boolean confirmationFlowEnabled, final boolean confirmationRequired) throws Exception {
|
||||
// setup
|
||||
final int amountTargets = 20;
|
||||
testdataFactory.createTargets(amountTargets, "rollout", "rollout");
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
if (confirmationFlowEnabled) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
// create rollout including the created targets with prefix 'rollout'
|
||||
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
|
||||
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*",
|
||||
confirmationRequired);
|
||||
|
||||
// retrieve rollout groups from created rollout
|
||||
mvc.perform(
|
||||
@@ -479,7 +616,19 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.content[0].status", equalTo("ready")))
|
||||
.andExpect(jsonPath("$.content[1].status", equalTo("ready")))
|
||||
.andExpect(jsonPath("$.content[2].status", equalTo("ready")))
|
||||
.andExpect(jsonPath("$.content[3].status", equalTo("ready")));
|
||||
.andExpect(jsonPath("$.content[3].status", equalTo("ready")))
|
||||
.andExpect(isConfirmationFlowEnabled()
|
||||
? jsonPath("$.content[0].confirmationRequired", equalTo(confirmationRequired))
|
||||
: jsonPath("confirmationRequired").doesNotExist())
|
||||
.andExpect(isConfirmationFlowEnabled()
|
||||
? jsonPath("$.content[1].confirmationRequired", equalTo(confirmationRequired))
|
||||
: jsonPath("confirmationRequired").doesNotExist())
|
||||
.andExpect(isConfirmationFlowEnabled()
|
||||
? jsonPath("$.content[2].confirmationRequired", equalTo(confirmationRequired))
|
||||
: jsonPath("confirmationRequired").doesNotExist())
|
||||
.andExpect(isConfirmationFlowEnabled()
|
||||
? jsonPath("$.content[3].confirmationRequired", equalTo(confirmationRequired))
|
||||
: jsonPath("confirmationRequired").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -648,20 +797,25 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.content[1].status", equalTo("scheduled")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Testing that a single rollout group can be retrieved")
|
||||
void retrieveSingleRolloutGroup() throws Exception {
|
||||
void retrieveSingleRolloutGroup(final boolean confirmationFlowEnabled, final boolean confirmationRequired)
|
||||
throws Exception {
|
||||
// setup
|
||||
final int amountTargets = 20;
|
||||
testdataFactory.createTargets(amountTargets, "rollout", "rollout");
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
if (confirmationFlowEnabled) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
// create rollout including the created targets with prefix 'rollout'
|
||||
final Rollout rollout = rolloutManagement.create(entityFactory.rollout()
|
||||
.create()
|
||||
.name("rollout1")
|
||||
.set(dsA.getId())
|
||||
.targetFilterQuery("controllerId==rollout*"), 4, new RolloutGroupConditionBuilder().withDefaults()
|
||||
final Rollout rollout = rolloutManagement.create(
|
||||
entityFactory.rollout().create().name("rollout1").set(dsA.getId())
|
||||
.targetFilterQuery("controllerId==rollout*"),
|
||||
4, confirmationRequired, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
|
||||
final RolloutGroup firstGroup = rolloutGroupManagement
|
||||
@@ -671,12 +825,18 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
|
||||
retrieveAndVerifyRolloutGroupInCreating(rollout, firstGroup);
|
||||
retrieveAndVerifyRolloutGroupInReady(rollout, firstGroup);
|
||||
retrieveAndVerifyRolloutGroupInRunningAndScheduled(rollout, firstGroup, secondGroup);
|
||||
retrieveAndVerifyRolloutGroupInRunningAndScheduled(rollout, firstGroup, secondGroup, confirmationFlowEnabled,
|
||||
confirmationRequired);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> confirmationOptions() {
|
||||
return Stream.of(Arguments.of(true, false), Arguments.of(true, true), Arguments.of(false, true));
|
||||
}
|
||||
|
||||
@Step
|
||||
private void retrieveAndVerifyRolloutGroupInRunningAndScheduled(final Rollout rollout,
|
||||
final RolloutGroup firstGroup, final RolloutGroup secondGroup) throws Exception {
|
||||
final RolloutGroup firstGroup, final RolloutGroup secondGroup, final boolean confirmationFlowEnabled,
|
||||
final boolean confirmationRequired) throws Exception {
|
||||
rolloutManagement.start(rollout.getId());
|
||||
rolloutManagement.handleRollouts();
|
||||
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId())
|
||||
@@ -688,7 +848,9 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.scheduled", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.cancelled", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.finished", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)));
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)))
|
||||
.andExpect(isConfirmationFlowEnabled() ? jsonPath("confirmationRequired", equalTo(confirmationRequired))
|
||||
: jsonPath("confirmationRequired").doesNotExist());
|
||||
|
||||
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), secondGroup.getId())
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
@@ -699,7 +861,10 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.scheduled", equalTo(5)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.cancelled", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.finished", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)));
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)))
|
||||
.andExpect(isConfirmationFlowEnabled() ? jsonPath("confirmationRequired", equalTo(confirmationRequired))
|
||||
: jsonPath("confirmationRequired").doesNotExist());
|
||||
;
|
||||
}
|
||||
|
||||
@Step
|
||||
@@ -718,7 +883,10 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.scheduled", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.cancelled", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.finished", equalTo(0)))
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)));
|
||||
.andExpect(jsonPath("$.totalTargetsPerStatus.error", equalTo(0)))
|
||||
.andExpect(isConfirmationFlowEnabled() ? jsonPath("confirmationRequired").exists()
|
||||
: jsonPath("confirmationRequired").doesNotExist());
|
||||
;
|
||||
}
|
||||
|
||||
@Step
|
||||
@@ -729,6 +897,8 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
|
||||
.andExpect(jsonPath("id", equalTo(firstGroup.getId().intValue())))
|
||||
.andExpect(isConfirmationFlowEnabled() ? jsonPath("confirmationRequired").exists()
|
||||
: jsonPath("confirmationRequired").doesNotExist())
|
||||
.andExpect(jsonPath("status", equalTo("creating"))).andExpect(jsonPath("name", endsWith("1")))
|
||||
.andExpect(jsonPath("description", endsWith("1")))
|
||||
.andExpect(jsonPath("$.targetFilterQuery", equalTo("")))
|
||||
@@ -983,9 +1153,9 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final int weight = 66;
|
||||
|
||||
final String invalideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
|
||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, Action.WEIGHT_MIN - 1);
|
||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, Action.WEIGHT_MIN - 1, null);
|
||||
final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
|
||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight);
|
||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null);
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
@@ -1009,7 +1179,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final String targetFilterQuery, final int targets, final Action.ActionType type) throws Exception {
|
||||
final String actionType = MgmtRestModelMapper.convertActionType(type).getName();
|
||||
final String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
|
||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null);
|
||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null, null);
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts").content(rollout).contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
|
||||
@@ -1040,7 +1210,15 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
|
||||
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
|
||||
final String targetFilterQuery) {
|
||||
final Rollout rollout = createRolloutInCreatingSatate(name, amountGroups, distributionSetId, targetFilterQuery);
|
||||
return createRollout(name, amountGroups, distributionSetId, targetFilterQuery, false);
|
||||
}
|
||||
|
||||
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
|
||||
final String targetFilterQuery, final boolean confirmationRequired) {
|
||||
final Rollout rollout = rolloutManagement.create(
|
||||
entityFactory.rollout().create().name(name).set(distributionSetId).targetFilterQuery(targetFilterQuery),
|
||||
amountGroups, confirmationRequired, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
|
||||
// Run here, because Scheduler is disabled during tests
|
||||
rolloutManagement.handleRollouts();
|
||||
@@ -1048,14 +1226,6 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
return rolloutManagement.get(rollout.getId()).orElseThrow(NoSuchElementException::new);
|
||||
}
|
||||
|
||||
private Rollout createRolloutInCreatingSatate(final String name, final int amountGroups,
|
||||
final long distributionSetId, final String targetFilterQuery) {
|
||||
return rolloutManagement.create(
|
||||
entityFactory.rollout().create().name(name).set(distributionSetId).targetFilterQuery(targetFilterQuery),
|
||||
amountGroups, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Trigger next rollout group")
|
||||
void triggeringNextGroupRollout() throws Exception {
|
||||
@@ -1085,7 +1255,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
// CREATING state
|
||||
final Rollout rollout = createRolloutInCreatingSatate("rollout1", 3, dsA.getId(), "controllerId==rollout*");
|
||||
final Rollout rollout = createRollout("rollout1", 3, dsA.getId(), "controllerId==rollout*", false);
|
||||
triggerNextGroupAndExpect(rollout, status().isBadRequest());
|
||||
|
||||
// READY state
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||
@@ -37,6 +38,10 @@ import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
|
||||
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
@@ -59,6 +64,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
private static final String JSON_PATH_FIELD_ID = ".id";
|
||||
private static final String JSON_PATH_FIELD_NAME = ".name";
|
||||
private static final String JSON_PATH_FIELD_QUERY = ".query";
|
||||
private static final String JSON_PATH_FIELD_CONFIRMATION_REQUIRED = ".confirmationRequired";
|
||||
private static final String JSON_PATH_FIELD_CONTENT = ".content";
|
||||
private static final String JSON_PATH_FIELD_SIZE = ".size";
|
||||
private static final String JSON_PATH_FIELD_TOTAL = ".total";
|
||||
@@ -76,6 +82,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
private static final String JSON_PATH_NAME = JSON_PATH_ROOT + JSON_PATH_FIELD_NAME;
|
||||
private static final String JSON_PATH_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID;
|
||||
private static final String JSON_PATH_QUERY = JSON_PATH_ROOT + JSON_PATH_FIELD_QUERY;
|
||||
private static final String JSON_PATH_CONFIRMATION_REQUIRED = JSON_PATH_ROOT
|
||||
+ JSON_PATH_FIELD_CONFIRMATION_REQUIRED;
|
||||
private static final String JSON_PATH_AUTO_ASSIGN_DS = JSON_PATH_ROOT + JSON_PATH_FIELD_AUTO_ASSIGN_DS;
|
||||
private static final String JSON_PATH_AUTO_ASSIGN_ACTION_TYPE = JSON_PATH_ROOT
|
||||
+ JSON_PATH_FIELD_AUTO_ASSIGN_ACTION_TYPE;
|
||||
@@ -126,7 +134,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath(JSON_PATH_ID, equalTo(tfq.getId().intValue())))
|
||||
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(filterQuery2)))
|
||||
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(filterName)));
|
||||
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(filterName)))
|
||||
.andExpect(jsonPath(JSON_PATH_CONFIRMATION_REQUIRED).doesNotExist());
|
||||
|
||||
final TargetFilterQuery tfqCheck = targetFilterQueryManagement.get(tfq.getId()).get();
|
||||
assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery2);
|
||||
@@ -149,7 +158,9 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath(JSON_PATH_ID, equalTo(tfq.getId().intValue())))
|
||||
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(filterQuery)))
|
||||
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(filterName2)));
|
||||
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(filterName2)))
|
||||
.andExpect(jsonPath(JSON_PATH_CONFIRMATION_REQUIRED).doesNotExist());
|
||||
;
|
||||
|
||||
final TargetFilterQuery tfqCheck = targetFilterQueryManagement.get(tfq.getId()).get();
|
||||
assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery);
|
||||
@@ -257,6 +268,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())).andDo(print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName)))
|
||||
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery)))
|
||||
.andExpect(jsonPath(JSON_PATH_CONFIRMATION_REQUIRED).doesNotExist())
|
||||
.andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix)))
|
||||
.andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS")));
|
||||
}
|
||||
@@ -364,24 +376,30 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensures that the distribution set auto-assignment works as intended with distribution set and action type validation")
|
||||
public void setAutoAssignDistributionSetToTargetFilterQuery() throws Exception {
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Ensures that the distribution set auto-assignment works as intended with distribution set, action type and confirmation validation")
|
||||
public void setAutoAssignDistributionSetToTargetFilterQuery(final boolean confirmationFlowActive,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
final String knownQuery = "name==test05";
|
||||
final String knownName = "filter05";
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
final DistributionSet set = testdataFactory.createDistributionSet();
|
||||
final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery);
|
||||
|
||||
verifyAutoAssignmentWithoutActionType(tfq, set);
|
||||
verifyAutoAssignmentWithoutActionType(tfq, set, confirmationRequired);
|
||||
|
||||
verifyAutoAssignmentWithForcedActionType(tfq, set);
|
||||
verifyAutoAssignmentWithForcedActionType(tfq, set, confirmationRequired);
|
||||
|
||||
verifyAutoAssignmentWithSoftActionType(tfq, set);
|
||||
verifyAutoAssignmentWithSoftActionType(tfq, set, confirmationRequired);
|
||||
|
||||
verifyAutoAssignmentWithTimeForcedActionType(tfq, set);
|
||||
|
||||
verifyAutoAssignmentWithDownloadOnlyActionType(tfq, set);
|
||||
verifyAutoAssignmentWithDownloadOnlyActionType(tfq, set, confirmationRequired);
|
||||
|
||||
verifyAutoAssignmentWithUnknownActionType(tfq, set);
|
||||
|
||||
@@ -390,34 +408,68 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
verifyAutoAssignmentWithSoftDeletedDs(tfq);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyAutoAssignmentWithoutActionType(final TargetFilterQuery tfq, final DistributionSet set)
|
||||
throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, null);
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
@Description("Verify the confirmation required flag will be set based on the feature state")
|
||||
void verifyConfirmationStateIfNotProvided(final boolean confirmationFlowActive) throws Exception {
|
||||
final String knownQuery = "name==test05";
|
||||
final String knownName = "filter05";
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
final DistributionSet set = testdataFactory.createDistributionSet();
|
||||
final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery);
|
||||
|
||||
// do not provide something about the confirmation
|
||||
verifyAutoAssignmentByActionType(tfq, set, null, null);
|
||||
|
||||
assertThat(targetFilterQueryManagement.get(tfq.getId())).hasValueSatisfying(filter -> {
|
||||
assertThat(filter.isConfirmationRequired()).isEqualTo(confirmationFlowActive);
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<Arguments> confirmationOptions() {
|
||||
return Stream.of(Arguments.of(true, false), Arguments.of(true, true), Arguments.of(false, true),
|
||||
Arguments.of(true, null), Arguments.of(false, null));
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyAutoAssignmentWithForcedActionType(final TargetFilterQuery tfq, final DistributionSet set)
|
||||
throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.FORCED);
|
||||
private void verifyAutoAssignmentWithoutActionType(final TargetFilterQuery tfq, final DistributionSet set,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, null, confirmationRequired);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyAutoAssignmentWithSoftActionType(final TargetFilterQuery tfq, final DistributionSet set)
|
||||
throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.SOFT);
|
||||
private void verifyAutoAssignmentWithForcedActionType(final TargetFilterQuery tfq, final DistributionSet set,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.FORCED, confirmationRequired);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyAutoAssignmentWithSoftActionType(final TargetFilterQuery tfq, final DistributionSet set,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.SOFT, confirmationRequired);
|
||||
}
|
||||
|
||||
private void verifyAutoAssignmentByActionType(final TargetFilterQuery tfq, final DistributionSet set,
|
||||
final MgmtActionType actionType) throws Exception {
|
||||
final MgmtActionType actionType, final Boolean confirmationRequired) throws Exception {
|
||||
final String hrefPrefix = "http://localhost" + MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"
|
||||
+ tfq.getId();
|
||||
|
||||
final String payload = actionType != null
|
||||
? "{\"id\":" + set.getId() + ", \"type\":\"" + actionType.getName() + "\"}"
|
||||
: "{\"id\":" + set.getId() + "}";
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", set.getId());
|
||||
if (actionType != null) {
|
||||
jsonObject.put("type", actionType.getName());
|
||||
}
|
||||
if (confirmationRequired != null) {
|
||||
jsonObject.put("confirmationRequired", confirmationRequired);
|
||||
}
|
||||
|
||||
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
|
||||
.content(payload).contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isOk());
|
||||
.content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON)).andDo(print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get();
|
||||
final MgmtActionType expectedActionType = actionType != null ? actionType : MgmtActionType.FORCED;
|
||||
@@ -429,6 +481,10 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())).andDo(print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(tfq.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(tfq.getQuery())))
|
||||
.andExpect(isConfirmationFlowEnabled()
|
||||
? jsonPath(JSON_PATH_CONFIRMATION_REQUIRED,
|
||||
equalTo(confirmationRequired == null || confirmationRequired))
|
||||
: jsonPath(JSON_PATH_CONFIRMATION_REQUIRED).doesNotExist())
|
||||
.andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_DS, equalTo(set.getId().intValue())))
|
||||
.andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_ACTION_TYPE, equalTo(expectedActionType.getName())))
|
||||
.andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix)))
|
||||
@@ -448,9 +504,9 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyAutoAssignmentWithDownloadOnlyActionType(final TargetFilterQuery tfq, final DistributionSet set)
|
||||
throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.DOWNLOAD_ONLY);
|
||||
private void verifyAutoAssignmentWithDownloadOnlyActionType(final TargetFilterQuery tfq, final DistributionSet set,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.DOWNLOAD_ONLY, confirmationRequired);
|
||||
}
|
||||
|
||||
@Step
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.TARGET_V1_AUTO_CONFIRM;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
@@ -36,6 +37,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
@@ -73,6 +75,9 @@ import org.hamcrest.Matchers;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -764,9 +769,10 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final Target test1 = entityFactory.target().create().controllerId("id1")
|
||||
.name(RandomStringUtils.randomAlphanumeric(NamedEntity.NAME_MAX_SIZE + 1)).build();
|
||||
|
||||
final MvcResult mvcResult = mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)
|
||||
.content(JsonBuilder.targets(Collections.singletonList(test1), true))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
final MvcResult mvcResult = mvc
|
||||
.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)
|
||||
.content(JsonBuilder.targets(Collections.singletonList(test1), true))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn();
|
||||
|
||||
assertThat(targetManagement.count()).isZero();
|
||||
@@ -1253,8 +1259,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
// Update
|
||||
if (schedule == null) {
|
||||
final List<Target> updatedTargets = assignDistributionSet(one, Collections.singletonList(target))
|
||||
.getAssignedEntity()
|
||||
.stream().map(Action::getTarget).collect(Collectors.toList());
|
||||
.getAssignedEntity().stream().map(Action::getTarget).collect(Collectors.toList());
|
||||
// 2nd update
|
||||
// sleep 10ms to ensure that we can sort by reportedAt
|
||||
Awaitility.await().atMost(Duration.ONE_HUNDRED_MILLISECONDS).atLeast(5, TimeUnit.MILLISECONDS)
|
||||
@@ -1336,6 +1341,48 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
assertThat(targetManagement.getByControllerID(target.getControllerId()).get()).isEqualTo(target);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Ensures that confirmation option is considered in assignment request.")
|
||||
void assignDistributionSetToTargetWithConfirmationOptions(final boolean confirmationFlowActive,
|
||||
final Boolean confirmationRequired) throws Exception {
|
||||
|
||||
final Target target = testdataFactory.createTarget();
|
||||
final DistributionSet set = testdataFactory.createDistributionSet("one");
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
final JSONObject jsonPayload = new JSONObject();
|
||||
jsonPayload.put("id", set.getId());
|
||||
if (confirmationRequired != null) {
|
||||
jsonPayload.put("confirmationRequired", confirmationRequired);
|
||||
}
|
||||
|
||||
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
|
||||
.content(jsonPayload.toString()).contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("assigned", equalTo(1))).andExpect(jsonPath("alreadyAssigned", equalTo(0)))
|
||||
.andExpect(jsonPath("total", equalTo(1)));
|
||||
|
||||
assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()).isEqualTo(set);
|
||||
|
||||
assertThat(deploymentManagement.findActionsByDistributionSet(PAGE, set.getId()).getContent()).hasSize(1)
|
||||
.allMatch(action -> {
|
||||
if (!confirmationFlowActive) {
|
||||
return !action.isWaitingConfirmation();
|
||||
}
|
||||
return confirmationRequired == null ? action.isWaitingConfirmation()
|
||||
: confirmationRequired == action.isWaitingConfirmation();
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<Arguments> confirmationOptions() {
|
||||
return Stream.of(Arguments.of(true, true), Arguments.of(true, false), Arguments.of(false, true),
|
||||
Arguments.of(false, false), Arguments.of(true, null), Arguments.of(false, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfies that a DOWNLOAD_ONLY DS to target assignment is properly handled")
|
||||
void assignDownloadOnlyDistributionSetToTarget() throws Exception {
|
||||
@@ -2003,9 +2050,9 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, 98);
|
||||
|
||||
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2057,7 +2104,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Description("An action provides information of the rollout it was created for (if any).")
|
||||
void getActionWithRolloutInfo() throws Exception {
|
||||
@@ -2094,8 +2141,10 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
@Test
|
||||
@Description("Ensures that a post request for creating targets with target type works.")
|
||||
void createTargetsWithTargetType() throws Exception {
|
||||
final TargetType type1 = testdataFactory.createTargetType("typeWithDs", Collections.singletonList(standardDsType));
|
||||
final TargetType type2 = testdataFactory.createTargetType("typeWithOutDs", Collections.singletonList(standardDsType));
|
||||
final TargetType type1 = testdataFactory.createTargetType("typeWithDs",
|
||||
Collections.singletonList(standardDsType));
|
||||
final TargetType type2 = testdataFactory.createTargetType("typeWithOutDs",
|
||||
Collections.singletonList(standardDsType));
|
||||
|
||||
final Target test1 = entityFactory.target().create().controllerId("id1").name("targetWithoutType")
|
||||
.securityToken("token").address("amqp://test123/foobar").description("testid1").build();
|
||||
@@ -2132,8 +2181,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("[2].description", equalTo("testid3")))
|
||||
.andExpect(jsonPath("[2].createdAt", not(equalTo(0))))
|
||||
.andExpect(jsonPath("[2].createdBy", equalTo("bumlux")))
|
||||
.andExpect(jsonPath("[2].targetType", equalTo(type2.getId().intValue())))
|
||||
.andReturn();
|
||||
.andExpect(jsonPath("[2].targetType", equalTo(type2.getId().intValue()))).andReturn();
|
||||
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + test2.getControllerId()))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
@@ -2159,10 +2207,11 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
@Description("Ensures that a post request for creating target with target type works.")
|
||||
void createTargetWithExistingTargetType() throws Exception {
|
||||
// create target type
|
||||
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype",1);
|
||||
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 1);
|
||||
assertThat(targetTypes).hasSize(1);
|
||||
|
||||
final Target target = entityFactory.target().create().controllerId("targetcontroller").name("testtarget").targetType(targetTypes.get(0).getId()).build();
|
||||
final Target target = entityFactory.target().create().controllerId("targetcontroller").name("testtarget")
|
||||
.targetType(targetTypes.get(0).getId()).build();
|
||||
|
||||
final String targetList = JsonBuilder.targets(Collections.singletonList(target), false);
|
||||
|
||||
@@ -2172,14 +2221,15 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andExpect(jsonPath("[0].controllerId", equalTo("targetcontroller")))
|
||||
.andExpect(jsonPath("[0].targetType", equalTo(targetTypes.get(0).getId().intValue())));
|
||||
|
||||
assertThat(targetManagement.getByControllerID("targetcontroller").get().getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
|
||||
assertThat(targetManagement.getByControllerID("targetcontroller").get().getTargetType().getId())
|
||||
.isEqualTo(targetTypes.get(0).getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensures that a put request for updating targets with target type works.")
|
||||
void updateTargetTypeInTarget() throws Exception {
|
||||
// create target type
|
||||
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype",2);
|
||||
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 2);
|
||||
assertThat(targetTypes).hasSize(2);
|
||||
|
||||
String controllerId = "targetcontroller";
|
||||
@@ -2234,7 +2284,8 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.content("{\"id\":" + targetType.getId() + "}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
assertThat(targetManagement.getByControllerID(targetControllerId).get().getTargetType().getId()).isEqualTo(targetType.getId());
|
||||
assertThat(targetManagement.getByControllerID(targetControllerId).get().getTargetType().getId())
|
||||
.isEqualTo(targetType.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2254,8 +2305,10 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
|
||||
|
||||
// verify response json exception message if body does not include id field
|
||||
final MvcResult mvcResult = mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
|
||||
.content("{\"unknownfield\":" + invalidTargetTypeId + "}").contentType(MediaType.APPLICATION_JSON))
|
||||
final MvcResult mvcResult = mvc
|
||||
.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
|
||||
.content("{\"unknownfield\":" + invalidTargetTypeId + "}")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn();
|
||||
final ExceptionInfo exceptionInfo = ResourceUtility
|
||||
.convertException(mvcResult.getResponse().getContentAsString());
|
||||
@@ -2268,7 +2321,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
@Description("Ensures that a delete request for unassign target type from target works.")
|
||||
void unassignTargetTypeFromTarget() throws Exception {
|
||||
// create target type
|
||||
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype",1);
|
||||
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 1);
|
||||
assertThat(targetTypes).hasSize(1);
|
||||
|
||||
String targetControllerId = "targetcontroller";
|
||||
@@ -2296,24 +2349,24 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
|
||||
|
||||
// PUT is not allowed
|
||||
mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING
|
||||
+ MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING, knownTargetId))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
|
||||
mvc.perform(put(
|
||||
MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING,
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
|
||||
|
||||
// POST does not exist with path parameter targettype
|
||||
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING
|
||||
+ MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING + "/123", knownTargetId))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
|
||||
|
||||
// DELETE does not exist with path parameter targettype
|
||||
// DELETE does not exist with path parameter targettype
|
||||
mvc.perform(delete(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING
|
||||
+ MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING + "/123", knownTargetId))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
|
||||
|
||||
// Invalid content
|
||||
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING,
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isUnsupportedMediaType());
|
||||
mvc.perform(post(
|
||||
MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING,
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isUnsupportedMediaType());
|
||||
|
||||
// Bad request if id field is missing
|
||||
mvc.perform(post(
|
||||
@@ -2321,4 +2374,87 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
knownTargetId).content("{\"unknownfield\":123}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("possibleActiveStates")
|
||||
void getAutoConfirmActive(final String initiator, final String remark) throws Exception {
|
||||
final String knownTargetId = "targetId";
|
||||
testdataFactory.createTarget(knownTargetId);
|
||||
confirmationManagement.activateAutoConfirmation(knownTargetId, initiator, remark);
|
||||
|
||||
// GET with all possible responses
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM,
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("active", equalTo(Boolean.TRUE)))
|
||||
.andExpect(initiator == null ? jsonPath("initiator").doesNotExist()
|
||||
: jsonPath("initiator", equalTo(initiator)))
|
||||
.andExpect(remark == null ? jsonPath("remark").doesNotExist() : jsonPath("remark", equalTo(remark)))
|
||||
.andExpect(jsonPath("_links.deactivate").exists())
|
||||
.andExpect(jsonPath("_links.activate").doesNotExist());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getAutoConfirmStateFromTargetsEndpoint() throws Exception {
|
||||
final String knownTargetId = "targetId";
|
||||
testdataFactory.createTarget(knownTargetId);
|
||||
|
||||
// GET if active
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("content.[0].autoConfirmActive").doesNotExist());
|
||||
|
||||
enableConfirmationFlow();
|
||||
|
||||
// GET if not active
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("content.[0].autoConfirmActive", equalTo(Boolean.FALSE)));
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(knownTargetId, "test", "remark");
|
||||
|
||||
// GET if active
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("content.[0].autoConfirmActive", equalTo(Boolean.TRUE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAutoConfirmNotActive() throws Exception {
|
||||
final String knownTargetId = "targetId";
|
||||
|
||||
// GET for not existing target
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM,
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
|
||||
|
||||
testdataFactory.createTarget(knownTargetId);
|
||||
|
||||
// GET for auto-confirm not active
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM,
|
||||
knownTargetId)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("active", equalTo(Boolean.FALSE))).andExpect(jsonPath("initiator").doesNotExist())
|
||||
.andExpect(jsonPath("remark").doesNotExist()).andExpect(jsonPath("_links.activate").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfirmStateReferenceOnTarget() throws Exception {
|
||||
final String knownTargetId = "targetId";
|
||||
testdataFactory.createTarget(knownTargetId);
|
||||
|
||||
// GET with confirmation flow not active should not expose
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}", knownTargetId))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("autoConfirmActive").doesNotExist())
|
||||
.andExpect(jsonPath("_links.autoConfirm").doesNotExist());
|
||||
|
||||
enableConfirmationFlow();
|
||||
|
||||
// GET with confirmation flow active should expose
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}", knownTargetId))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("autoConfirmActive").exists()).andExpect(jsonPath("_links.autoConfirm").exists());
|
||||
}
|
||||
|
||||
private static Stream<Arguments> possibleActiveStates() {
|
||||
return Stream.of(Arguments.of("someInitiator", "someRemark"), Arguments.of(null, "someRemark"),
|
||||
Arguments.of("someInitiator", null), Arguments.of(null, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ public class ResponseExceptionHandler {
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONCURRENT_MODIFICATION, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_AUTO_CONFIRMATION_ALREADY_ACTIVE, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
|
||||
|
||||
@@ -17,6 +17,7 @@ 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;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
@@ -26,6 +27,9 @@ import org.eclipse.hawkbit.repository.model.TargetType;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Builder class for building certain json strings.
|
||||
@@ -478,26 +482,36 @@ public abstract class JsonBuilder {
|
||||
public static String rollout(final String name, final String description, final int groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) {
|
||||
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
public static String rollout(final String name, final String description, final Integer groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
|
||||
final List<RolloutGroup> groups) {
|
||||
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, groups, null,
|
||||
null);
|
||||
null, null);
|
||||
}
|
||||
|
||||
public static String rollout(final String name, final String description, final Integer groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
|
||||
final String type) {
|
||||
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, type,
|
||||
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, type, null,
|
||||
null);
|
||||
}
|
||||
|
||||
public static String rolloutWithGroups(final String name, final String description, final Integer groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
|
||||
final List<RolloutGroup> groups) {
|
||||
return rolloutWithGroups(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, groups,
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
public static String rolloutWithGroups(final String name, final String description, final Integer groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
|
||||
final List<RolloutGroup> groups, final String type, final Integer weight,
|
||||
final Boolean confirmationRequired) {
|
||||
final List<String> rolloutGroupsJson = groups.stream().map(JsonBuilder::rolloutGroup)
|
||||
.collect(Collectors.toList());
|
||||
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions,
|
||||
rolloutGroupsJson, type, weight, confirmationRequired);
|
||||
}
|
||||
|
||||
public static String rollout(final String name, final String description, final Integer groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
|
||||
final List<RolloutGroup> groups, final String type, final Integer weight) {
|
||||
final List<String> groupJsonList, final String type, final Integer weight, final Boolean confirmationRequired) {
|
||||
final JSONObject json = new JSONObject();
|
||||
|
||||
try {
|
||||
@@ -515,6 +529,10 @@ public abstract class JsonBuilder {
|
||||
json.put("weight", weight);
|
||||
}
|
||||
|
||||
if (confirmationRequired != null) {
|
||||
json.put("confirmationRequired", confirmationRequired);
|
||||
}
|
||||
|
||||
if (conditions != null) {
|
||||
final JSONObject successCondition = new JSONObject();
|
||||
|
||||
@@ -539,44 +557,11 @@ public abstract class JsonBuilder {
|
||||
errorAction.put("expression", conditions.getErrorActionExp());
|
||||
}
|
||||
|
||||
if (groups != null) {
|
||||
if (!CollectionUtils.isEmpty(groupJsonList)) {
|
||||
final JSONArray jsonGroups = new JSONArray();
|
||||
|
||||
for (final RolloutGroup group : groups) {
|
||||
final JSONObject jsonGroup = new JSONObject();
|
||||
jsonGroup.put("name", group.getName());
|
||||
jsonGroup.put("description", group.getDescription());
|
||||
jsonGroup.put("targetFilterQuery", group.getTargetFilterQuery());
|
||||
jsonGroup.put("targetPercentage", group.getTargetPercentage());
|
||||
|
||||
if (group.getSuccessCondition() != null) {
|
||||
final JSONObject successCondition = new JSONObject();
|
||||
jsonGroup.put("successCondition", successCondition);
|
||||
successCondition.put("condition", group.getSuccessCondition().toString());
|
||||
successCondition.put("expression", group.getSuccessConditionExp());
|
||||
}
|
||||
if (group.getSuccessAction() != null) {
|
||||
final JSONObject successAction = new JSONObject();
|
||||
jsonGroup.put("successAction", successAction);
|
||||
successAction.put("action", group.getSuccessAction().toString());
|
||||
successAction.put("expression", group.getSuccessActionExp());
|
||||
}
|
||||
if (group.getErrorCondition() != null) {
|
||||
final JSONObject errorCondition = new JSONObject();
|
||||
jsonGroup.put("errorCondition", errorCondition);
|
||||
errorCondition.put("condition", group.getErrorCondition().toString());
|
||||
errorCondition.put("expression", group.getErrorConditionExp());
|
||||
}
|
||||
if (group.getErrorAction() != null) {
|
||||
final JSONObject errorAction = new JSONObject();
|
||||
jsonGroup.put("errorAction", errorAction);
|
||||
errorAction.put("action", group.getErrorAction().toString());
|
||||
errorAction.put("expression", group.getErrorActionExp());
|
||||
}
|
||||
|
||||
jsonGroups.put(jsonGroup);
|
||||
for (final String groupJson : groupJsonList) {
|
||||
jsonGroups.put(new JSONObject(groupJson));
|
||||
}
|
||||
|
||||
json.put("groups", jsonGroups);
|
||||
}
|
||||
|
||||
@@ -587,6 +572,72 @@ public abstract class JsonBuilder {
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
public static String rolloutGroup(final RolloutGroup rolloutGroup) {
|
||||
final RolloutGroupConditions conditions = getConditions(rolloutGroup);
|
||||
return rolloutGroup(rolloutGroup.getName(), rolloutGroup.getDescription(), rolloutGroup.getTargetFilterQuery(),
|
||||
rolloutGroup.getTargetPercentage(), rolloutGroup.isConfirmationRequired(), conditions);
|
||||
|
||||
}
|
||||
|
||||
private static RolloutGroupConditions getConditions(final RolloutGroup rolloutGroup) {
|
||||
return new RolloutGroupConditionBuilder()
|
||||
.errorCondition(rolloutGroup.getErrorCondition(), rolloutGroup.getErrorConditionExp())
|
||||
.errorAction(rolloutGroup.getErrorAction(), rolloutGroup.getErrorActionExp())
|
||||
.successAction(rolloutGroup.getSuccessAction(), rolloutGroup.getSuccessActionExp())
|
||||
.successCondition(rolloutGroup.getSuccessCondition(), rolloutGroup.getSuccessConditionExp()).build();
|
||||
}
|
||||
|
||||
public static String rolloutGroup(final String name, final String description, final String targetFilterQuery,
|
||||
final Float targetPercentage, final Boolean confirmationRequired,
|
||||
final RolloutGroupConditions rolloutGroupConditions) {
|
||||
final JSONObject jsonGroup = new JSONObject();
|
||||
try {
|
||||
jsonGroup.put("name", name);
|
||||
jsonGroup.put("description", description);
|
||||
jsonGroup.put("targetFilterQuery", targetFilterQuery);
|
||||
if (targetPercentage == null) {
|
||||
jsonGroup.put("targetPercentage", 100F);
|
||||
} else {
|
||||
jsonGroup.put("targetPercentage", targetPercentage);
|
||||
}
|
||||
|
||||
if (confirmationRequired != null) {
|
||||
jsonGroup.put("confirmationRequired", confirmationRequired);
|
||||
}
|
||||
|
||||
if (rolloutGroupConditions.getSuccessCondition() != null) {
|
||||
final JSONObject successCondition = new JSONObject();
|
||||
jsonGroup.put("successCondition", successCondition);
|
||||
successCondition.put("condition", rolloutGroupConditions.getSuccessCondition().toString());
|
||||
successCondition.put("expression", rolloutGroupConditions.getSuccessConditionExp());
|
||||
}
|
||||
if (rolloutGroupConditions.getSuccessAction() != null) {
|
||||
final JSONObject successAction = new JSONObject();
|
||||
jsonGroup.put("successAction", successAction);
|
||||
successAction.put("action", rolloutGroupConditions.getSuccessAction().toString());
|
||||
successAction.put("expression", rolloutGroupConditions.getSuccessActionExp());
|
||||
}
|
||||
if (rolloutGroupConditions.getErrorCondition() != null) {
|
||||
final JSONObject errorCondition = new JSONObject();
|
||||
jsonGroup.put("errorCondition", errorCondition);
|
||||
errorCondition.put("condition", rolloutGroupConditions.getErrorCondition().toString());
|
||||
errorCondition.put("expression", rolloutGroupConditions.getErrorConditionExp());
|
||||
}
|
||||
if (rolloutGroupConditions.getErrorAction() != null) {
|
||||
final JSONObject errorAction = new JSONObject();
|
||||
jsonGroup.put("errorAction", errorAction);
|
||||
errorAction.put("action", rolloutGroupConditions.getErrorAction().toString());
|
||||
errorAction.put("expression", rolloutGroupConditions.getErrorActionExp());
|
||||
}
|
||||
|
||||
} catch (final JSONException e) {
|
||||
e.printStackTrace();
|
||||
fail("Cannot parse JSON for rollout group.");
|
||||
}
|
||||
|
||||
return jsonGroup.toString();
|
||||
}
|
||||
|
||||
public static String cancelActionFeedback(final String id, final String execution) throws JSONException {
|
||||
return cancelActionFeedback(id, execution, null, RandomStringUtils.randomAlphanumeric(1000));
|
||||
|
||||
|
||||
@@ -299,9 +299,237 @@ include::{snippets}/rootcontroller/post-basedeployment-action-feedback/request-f
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/410.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== GET /{tenant}/controller/v1/{controllerid}/confirmationBase
|
||||
|
||||
=== Implementation notes
|
||||
|
||||
Core resource for confirmation related operations. While active actions awaiting confirmation will be referenced, the current auto-confirmation status will be shown. In case auto-confirmation is active, details like the initiator, remark and date of activation (as unix timestamp) will be provided. Reference links to switch the auto-confirmation state are exposed as well.
|
||||
|
||||
=== Resource to request confirmation specific information for the controller
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-with-auto-confirm-active/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-with-auto-confirm-active/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-with-auto-confirm-active/path-parameters.adoc[]
|
||||
|
||||
==== Response example (auto-confirmation is active)
|
||||
|
||||
The response body in case auto-confirmation is active.
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-with-auto-confirm-active/http-response.adoc[]
|
||||
|
||||
==== Response example (auto-confirmation is not active)
|
||||
|
||||
The response body references a link to activate auto-confirmation as well as a link to an open action waiting for confirmation (if present).
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-with-auto-confirm-deactivated/http-response.adoc[]
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== POST /{tenant}/controller/v1/{controllerid}/confirmationBase/activateAutoConfirm
|
||||
|
||||
=== Implementation notes
|
||||
|
||||
The device can use this resource to activate auto-confirmation. As a result all current active as well as future actions will automatically be confirmed by mentioning the initiator as triggered person. Actions will be automatically confirmed, as long as auto-confirmation is active.
|
||||
|
||||
=== Interface to activate auto-confirmation for a specific device
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/rootcontroller/activate-auto-confirmation/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rootcontroller/activate-auto-confirmation/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/rootcontroller/activate-auto-confirmation/path-parameters.adoc[]
|
||||
|
||||
==== Request fields
|
||||
|
||||
include::{snippets}/rootcontroller/activate-auto-confirmation/request-fields.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== POST /{tenant}/controller/v1/{controllerid}/confirmationBase/deactivateAutoConfirm
|
||||
|
||||
=== Implementation notes
|
||||
|
||||
The device can use this resource to deactivate auto-confirmation. All active actions will remain unchanged while all future actions need to be confirmed, before processing with the deployment.
|
||||
|
||||
=== Interface to deactivate auto-confirmation for a specific controller
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/rootcontroller/deactivate-auto-confirmation/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rootcontroller/deactivate-auto-confirmation/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/rootcontroller/deactivate-auto-confirmation/path-parameters.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== GET /{tenant}/controller/v1/{controllerid}/confirmationBase/{actionId}
|
||||
|
||||
=== Implementation notes
|
||||
|
||||
Resource to receive information about a pending confirmation.
|
||||
The response will be of the same format as the deploymentBase operation.
|
||||
The controller should provide feedback about the confirmation first, before processing the deployment.
|
||||
|
||||
Keep in mind that the provided download links for the artifacts are generated dynamically by the update server.
|
||||
Host, port and path are not guaranteed to be similar to the provided examples below but will be defined at runtime.
|
||||
|
||||
=== Confirmation status of an action
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-action/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-action/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-action/path-parameters.adoc[]
|
||||
|
||||
==== Request query parameter
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-action/request-parameters.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
==== Response fields
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-action/response-fields.adoc[]
|
||||
|
||||
==== Response example
|
||||
|
||||
The response body includes the detailed information about the action awaiting confirmation in the same format as for the deploymentBase operation.
|
||||
|
||||
include::{snippets}/rootcontroller/get-confirmation-base-action/http-response.adoc[]
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404_target_action.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== POST /{tenant}/controller/v1/{controllerid}/confirmationBase/{actionId}/feedback
|
||||
|
||||
=== Implementation notes
|
||||
|
||||
The device will use this resource to either confirm or deny an action which is waiting for confirmation. The action will be transferred into the RUNNING state in case the device is confirming it. Afterwards it will be exposed by the deploymentBase.
|
||||
|
||||
=== Feedback channel for actions waiting for confirmation
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/rootcontroller/post-confirmation-feedback/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rootcontroller/post-confirmation-feedback/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/rootcontroller/post-confirmation-feedback/path-parameters.adoc[]
|
||||
|
||||
==== Request fields
|
||||
|
||||
include::{snippets}/rootcontroller/post-confirmation-feedback/request-fields.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404_target_action.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/410.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
@@ -245,6 +245,152 @@ include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
|
||||
== GET /rest/v1/targets/{targetId}/autoConfirm
|
||||
|
||||
=== Implementation Notes
|
||||
|
||||
Handles the GET request to check the current auto-confirmation state of a target. Required Permission: READ_TARGET
|
||||
|
||||
=== Request auto-confirmation state for a specific target
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-active/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-active/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-active/path-parameters.adoc[]
|
||||
|
||||
=== Response in case auto-confirmation is active (Status 200)
|
||||
|
||||
In case auto-confirmation is active, details like the initiator, remark and date of activation (as unix timestamp) will be provided. Reference links to switch the auto-confirmation state are exposed as well.
|
||||
|
||||
==== Response fields
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-active/response-fields.adoc[]
|
||||
|
||||
==== Response example
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-active/http-response.adoc[]
|
||||
|
||||
=== Response in case auto-confirmation is not active (Status 200)
|
||||
|
||||
The response body references a link to activate auto-confirmation.
|
||||
|
||||
==== Response fields
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-not-active/response-fields.adoc[]
|
||||
|
||||
==== Response example
|
||||
|
||||
include::{snippets}/targets/get-target-auto-confirm-state-not-active/http-response.adoc[]
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
|
||||
== POST /rest/v1/targets/{targetId}/autoConfirm/activate
|
||||
|
||||
=== Implementation Notes
|
||||
|
||||
Handles the POST request to activate auto-confirmation for a specific target.
|
||||
As a result all current active as well as future actions will automatically be confirmed by mentioning the initiator as triggered person.
|
||||
Actions will be automatically confirmed, as long as auto-confirmation is active.
|
||||
Required Permission: UPDATE_TARGET
|
||||
|
||||
=== Activate auto-confirmation for a target
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/targets/post-activate-auto-confirm/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/targets/post-activate-auto-confirm/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/targets/post-activate-auto-confirm/path-parameters.adoc[]
|
||||
|
||||
==== Request fields
|
||||
|
||||
include::{snippets}/targets/post-activate-auto-confirm/request-fields.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
|
||||
== POST /rest/v1/targets/{targetId}/autoConfirm/deactivate
|
||||
|
||||
=== Implementation Notes
|
||||
|
||||
Handles the POST request to deactivate auto-confirmation for a specific target.
|
||||
All active actions will remain unchanged while all future actions need to be confirmed, before processing with the deployment.
|
||||
Required Permission: UPDATE_TARGET
|
||||
|
||||
=== Deactivate auto-confirmation for a target
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/targets/post-deactivate-auto-confirm/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/targets/post-deactivate-auto-confirm/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/targets/post-deactivate-auto-confirm/path-parameters.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
|
||||
== GET /rest/v1/targets/{targetId}/actions
|
||||
|
||||
=== Implementation Notes
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
| `404 Not Found`
|
||||
| Not Found Target.
|
||||
| Target not found.
|
||||
| See <<error-body>>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
| `404 Not Found`
|
||||
| Target or Action not found.
|
||||
| See <<error-body>>
|
||||
2
hawkbit-rest/hawkbit-rest-docs/src/main/errors/410.adoc
Normal file
2
hawkbit-rest/hawkbit-rest-docs/src/main/errors/410.adoc
Normal file
@@ -0,0 +1,2 @@
|
||||
| `410 Gone`
|
||||
| Action is not active anymore.
|
||||
@@ -96,6 +96,8 @@ final class DdiApiModelProperties {
|
||||
|
||||
static final String DEPLOYMENT = "Detailed deployment operation";
|
||||
|
||||
static final String CONFIRMATION = "Deployment confirmation operation";
|
||||
|
||||
static final String CANCEL = "Detailed cancel operation of a deployment";
|
||||
|
||||
static final String INSTALLED = "Detailed operation of last successfully finished action";
|
||||
@@ -137,4 +139,29 @@ final class DdiApiModelProperties {
|
||||
static final String UPDATE_MODE = "Optional parameter to specify the update mode that should be applied when updating target attributes. "
|
||||
+ "Valid values are 'merge', 'replace', and 'remove'. Defaults to 'merge'.";
|
||||
|
||||
static final String TARGET_CONFIRMATION_STATE = "action confirmation state";
|
||||
|
||||
static final String TARGET_CONFIRMATION_CODE = "optional individual status code";
|
||||
|
||||
static final String TARGET_CONFIRMATION_DETAILS = "List of detailed message information";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM = "id of the action";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_STATE = "flag if auto confirm is active";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_INITIATOR_RESPONSE = "(optional) initiator set on activation";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_REMARK_RESPONSE = "(optional) remark set on activation";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_ACTIVATED_AT = "timestamp of the activation";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_REFERENCE_ACTIVATE_AUTO_CONFIRM = "reference link to activate auto confirm";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_REFERENCE_DEACTIVATE_AUTO_CONFIRM = "reference link to deactivate auto confirm";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_REFERENCE_CONFIRMATION_BASE_ACTION = "reference link in case an action with open confirmation is present";
|
||||
|
||||
static final String TARGET_AUTO_CONFIRM_ACTIVATE_INITIATOR = "individual value (e.g. username) stored as initiator and automatically used as confirmed user in future actions";
|
||||
static final String TARGET_AUTO_CONFIRM_ACTIVATE_REMARK = "individual value to attach a remark which will be persisted when automatically confirming future actions";
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActivateAutoConfirmation;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiProgress;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
||||
@@ -194,14 +196,13 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
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)),
|
||||
requestFields(
|
||||
optionalRequestFieldWithPath("id")
|
||||
.description(DdiApiModelProperties.FEEDBACK_ACTION_ID)
|
||||
optionalRequestFieldWithPath("id").description(DdiApiModelProperties.FEEDBACK_ACTION_ID)
|
||||
.type(JsonFieldType.NUMBER),
|
||||
optionalRequestFieldWithPath("time")
|
||||
.description(DdiApiModelProperties.FEEDBACK_ACTION_TIME),
|
||||
@@ -409,14 +410,14 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
+ 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)),
|
||||
|
||||
requestFields(
|
||||
optionalRequestFieldWithPath("id")
|
||||
.description(DdiApiModelProperties.FEEDBACK_ACTION_ID)
|
||||
optionalRequestFieldWithPath("id").description(DdiApiModelProperties.FEEDBACK_ACTION_ID)
|
||||
.type(JsonFieldType.NUMBER),
|
||||
optionalRequestFieldWithPath("time")
|
||||
.description(DdiApiModelProperties.FEEDBACK_ACTION_TIME),
|
||||
@@ -580,4 +581,234 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Resource to retrieve the current state of auto confirmation. In case auto-confirm is active a reference to disable it will be provided.")
|
||||
@WithUser(tenantId = "TENANT_ID", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
|
||||
public void getConfirmationBaseWithAutoConfirmActive() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet set = testdataFactory.createDistributionSet("one");
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
confirmationManagement.activateAutoConfirmation(target.getControllerId(), "exampleUserId", "exampleRemark");
|
||||
assignDistributionSetWithMaintenanceWindow(set.getId(), target.getControllerId(), getTestSchedule(-5),
|
||||
getTestDuration(10), getTestTimeZone());
|
||||
|
||||
mockMvc.perform(
|
||||
get(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE,
|
||||
tenantAware.getCurrentTenant(), target.getControllerId()).accept(MediaTypes.HAL_JSON_VALUE))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID)),
|
||||
responseFields(
|
||||
fieldWithPath("autoConfirm").description(DdiApiModelProperties.TARGET_AUTO_CONFIRM),
|
||||
fieldWithPath("autoConfirm.active")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_STATE),
|
||||
fieldWithPath("autoConfirm.initiator")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_INITIATOR_RESPONSE),
|
||||
fieldWithPath("autoConfirm.remark")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_REMARK_RESPONSE),
|
||||
fieldWithPath("autoConfirm.activatedAt")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_ACTIVATED_AT),
|
||||
fieldWithPath("_links.deactivateAutoConfirm").description(
|
||||
DdiApiModelProperties.TARGET_AUTO_CONFIRM_REFERENCE_DEACTIVATE_AUTO_CONFIRM))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Resource to retrieve the current state of auto confirmation. In case actions are waiting for a confirmation, they will be referenced.")
|
||||
@WithUser(tenantId = "TENANT_ID", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
|
||||
public void getConfirmationBaseWithAutoConfirmDeactivated() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet set = testdataFactory.createDistributionSet("one");
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
assignDistributionSetWithMaintenanceWindow(set.getId(), target.getControllerId(), getTestSchedule(-5),
|
||||
getTestDuration(10), getTestTimeZone());
|
||||
|
||||
mockMvc.perform(
|
||||
get(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE,
|
||||
tenantAware.getCurrentTenant(), target.getControllerId()).accept(MediaTypes.HAL_JSON_VALUE))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID)),
|
||||
responseFields(
|
||||
fieldWithPath("autoConfirm").description(DdiApiModelProperties.TARGET_AUTO_CONFIRM),
|
||||
fieldWithPath("autoConfirm.active")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_STATE),
|
||||
fieldWithPath("_links.activateAutoConfirm").description(
|
||||
DdiApiModelProperties.TARGET_AUTO_CONFIRM_REFERENCE_ACTIVATE_AUTO_CONFIRM),
|
||||
fieldWithPath("_links.confirmationBase").description(
|
||||
DdiApiModelProperties.TARGET_AUTO_CONFIRM_REFERENCE_CONFIRMATION_BASE_ACTION))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Core resource for confirmation of actions. Contains all necessary information for confirmation.")
|
||||
@WithUser(tenantId = "TENANT_ID", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
|
||||
public void getConfirmationBaseAction() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet set = testdataFactory.createDistributionSet("one");
|
||||
|
||||
set.getModules().forEach(module -> {
|
||||
final byte[] random = RandomStringUtils.random(5).getBytes();
|
||||
|
||||
artifactManagement.create(
|
||||
new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "binary.tgz", false, 0));
|
||||
artifactManagement.create(
|
||||
new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "file.signature", false, 0));
|
||||
});
|
||||
|
||||
softwareModuleManagement.createMetaData(
|
||||
entityFactory.softwareModuleMetadata().create(set.getModules().iterator().next().getId())
|
||||
.key("aMetadataKey").value("Metadata value as defined in software module").targetVisible(true));
|
||||
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
final Long actionId = getFirstAssignedActionId(assignDistributionSetWithMaintenanceWindow(set.getId(),
|
||||
target.getControllerId(), getTestSchedule(-5), getTestDuration(10), getTestTimeZone()));
|
||||
|
||||
mockMvc.perform(get(
|
||||
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE
|
||||
+ "/{actionId}?actionHistory=10",
|
||||
tenantAware.getCurrentTenant(), target.getControllerId(), actionId).accept(MediaTypes.HAL_JSON_VALUE))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID),
|
||||
parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID)),
|
||||
requestParameters(
|
||||
parameterWithName("actionHistory").description(DdiApiModelProperties.ACTION_HISTORY)),
|
||||
responseFields(fieldWithPath("id").description(DdiApiModelProperties.ACTION_ID),
|
||||
fieldWithPath("confirmation").description(DdiApiModelProperties.CONFIRMATION),
|
||||
fieldWithPath("confirmation.download")
|
||||
.description(DdiApiModelProperties.HANDLING_DOWNLOAD).type("enum")
|
||||
.attributes(key("value").value("['skip', 'attempt', 'forced']")),
|
||||
fieldWithPath("confirmation.update").description(DdiApiModelProperties.HANDLING_UPDATE)
|
||||
.type("enum").attributes(key("value").value("['skip', 'attempt', 'forced']")),
|
||||
fieldWithPath("confirmation.maintenanceWindow")
|
||||
.description(DdiApiModelProperties.MAINTENANCE_WINDOW).type("enum")
|
||||
.attributes(key("value").value("['available', 'unavailable']")),
|
||||
fieldWithPath("confirmation.chunks").description(DdiApiModelProperties.CHUNK),
|
||||
fieldWithPath("confirmation.chunks[].metadata")
|
||||
.description(DdiApiModelProperties.CHUNK_META_DATA).optional(),
|
||||
fieldWithPath("confirmation.chunks[].metadata[].key")
|
||||
.description(DdiApiModelProperties.CHUNK_META_DATA_KEY).optional(),
|
||||
fieldWithPath("confirmation.chunks[].metadata[].value")
|
||||
.description(DdiApiModelProperties.CHUNK_META_DATA_VALUE).optional(),
|
||||
fieldWithPath("confirmation.chunks[].part")
|
||||
.description(DdiApiModelProperties.CHUNK_TYPE),
|
||||
fieldWithPath("confirmation.chunks[].name")
|
||||
.description(DdiApiModelProperties.CHUNK_NAME),
|
||||
fieldWithPath("confirmation.chunks[].version")
|
||||
.description(DdiApiModelProperties.CHUNK_VERSION),
|
||||
fieldWithPath("confirmation.chunks[].artifacts")
|
||||
.description(DdiApiModelProperties.ARTIFACTS),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[].filename")
|
||||
.description(DdiApiModelProperties.ARTIFACTS),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[].hashes")
|
||||
.description(DdiApiModelProperties.ARTIFACTS),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[].hashes.sha1")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HASHES_SHA1),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[].hashes.md5")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HASHES_MD5),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[].hashes.sha256")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HASHES_SHA256),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[].size")
|
||||
.description(DdiApiModelProperties.ARTIFACT_SIZE),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[]._links.download")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HTTPS_DOWNLOAD_LINK_BY_CONTROLLER),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[]._links.md5sum")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HTTPS_HASHES_MD5SUM_LINK),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[]._links.download-http")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HTTP_DOWNLOAD_LINK_BY_CONTROLLER),
|
||||
fieldWithPath("confirmation.chunks[].artifacts[]._links.md5sum-http")
|
||||
.description(DdiApiModelProperties.ARTIFACT_HTTP_HASHES_MD5SUM_LINK),
|
||||
fieldWithPath("actionHistory").description(DdiApiModelProperties.ACTION_HISTORY_RESP),
|
||||
fieldWithPath("actionHistory.status")
|
||||
.description(DdiApiModelProperties.ACTION_HISTORY_RESP_STATUS),
|
||||
fieldWithPath("actionHistory.messages")
|
||||
.description(DdiApiModelProperties.ACTION_HISTORY_RESP_MESSAGES))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Feedback channel for confirming an action")
|
||||
@WithUser(tenantId = "TENANT_ID", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
|
||||
public void postConfirmationFeedback() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet set = testdataFactory.createDistributionSet("one");
|
||||
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
final Long actionId = getFirstAssignedActionId(assignDistributionSet(set.getId(), target.getControllerId()));
|
||||
|
||||
final DdiConfirmationFeedback feedback = new DdiConfirmationFeedback(
|
||||
DdiConfirmationFeedback.Confirmation.CONFIRMED, 33, List.of("Feedback message"));
|
||||
|
||||
mockMvc.perform(
|
||||
post(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE
|
||||
+ "/{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(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID),
|
||||
parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID)),
|
||||
|
||||
requestFields(
|
||||
requestFieldWithPath("confirmation")
|
||||
.description(DdiApiModelProperties.TARGET_CONFIRMATION_STATE).type("enum")
|
||||
.attributes(key("value").value("['confirmed', 'denied']")),
|
||||
|
||||
optionalRequestFieldWithPath("code")
|
||||
.description(DdiApiModelProperties.TARGET_CONFIRMATION_CODE),
|
||||
|
||||
optionalRequestFieldWithPath("details")
|
||||
.description(DdiApiModelProperties.TARGET_CONFIRMATION_DETAILS))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Resource to activate auto-confirmation on a target.")
|
||||
@WithUser(tenantId = "TENANT_ID", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
|
||||
public void activateAutoConfirmation() throws Exception {
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
|
||||
final DdiActivateAutoConfirmation body = new DdiActivateAutoConfirmation("exampleUser", "exampleRemark");
|
||||
|
||||
mockMvc.perform(post(
|
||||
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE + "/"
|
||||
+ DdiRestConstants.AUTO_CONFIRM_ACTIVATE,
|
||||
tenantAware.getCurrentTenant(), target.getControllerId()).content(objectMapper.writeValueAsString(body))
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID)),
|
||||
requestFields(
|
||||
optionalRequestFieldWithPath("initiator")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_ACTIVATE_INITIATOR),
|
||||
optionalRequestFieldWithPath("remark")
|
||||
.description(DdiApiModelProperties.TARGET_AUTO_CONFIRM_ACTIVATE_REMARK))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Resource to deactivate auto-confirmation on a target.")
|
||||
@WithUser(tenantId = "TENANT_ID", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
|
||||
public void deactivateAutoConfirmation() throws Exception {
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
|
||||
mockMvc.perform(post(
|
||||
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.CONFIRMATION_BASE + "/"
|
||||
+ DdiRestConstants.AUTO_CONFIRM_DEACTIVATE,
|
||||
tenantAware.getCurrentTenant(), target.getControllerId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("tenant").description(ApiModelPropertiesGeneric.TENANT),
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
|
||||
final Rollout rollout = testdataFactory.createRolloutByVariables("rollout", "rollout desc", 1,
|
||||
"name==" + name, distributionSet, "50", "5", timeforced ? ActionType.TIMEFORCED : ActionType.FORCED,
|
||||
isMultiAssignmentsEnabled() ? 600 : null);
|
||||
isMultiAssignmentsEnabled() ? 600 : null, isConfirmationFlowActive());
|
||||
|
||||
// start the rollout and handle it
|
||||
rolloutManagement.start(rollout.getId());
|
||||
@@ -280,6 +280,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
fieldWithPath(fieldArrayPrefix + "securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN),
|
||||
fieldWithPath(fieldArrayPrefix + "requestAttributes")
|
||||
.description(MgmtApiModelProperties.REQUEST_ATTRIBUTES),
|
||||
fieldWithPath(fieldArrayPrefix + "autoConfirmActive")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_ACTIVE),
|
||||
fieldWithPath(fieldArrayPrefix + "installedAt").description(MgmtApiModelProperties.INSTALLED_AT),
|
||||
fieldWithPath(fieldArrayPrefix + "lastModifiedAt")
|
||||
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
|
||||
@@ -314,7 +316,9 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
.description(MgmtApiModelProperties.LINKS_ACTIONS),
|
||||
fieldWithPath(fieldArrayPrefix + "_links.metadata").description(MgmtApiModelProperties.META_DATA),
|
||||
fieldWithPath(fieldArrayPrefix + "_links.targetType")
|
||||
.description(MgmtApiModelProperties.LINK_TO_TARGET_TYPE)));
|
||||
.description(MgmtApiModelProperties.LINK_TO_TARGET_TYPE),
|
||||
fieldWithPath(fieldArrayPrefix + "_links.autoConfirm")
|
||||
.description(MgmtApiModelProperties.LINK_TO_AUTO_CONFIRM)));
|
||||
|
||||
}
|
||||
fields.addAll(Arrays.asList(descriptors));
|
||||
@@ -364,7 +368,12 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
|
||||
protected boolean isMultiAssignmentsEnabled() {
|
||||
return Boolean.TRUE.equals(tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
|
||||
.getConfigurationValue(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
|
||||
}
|
||||
|
||||
protected boolean isConfirmationFlowActive() {
|
||||
return Boolean.TRUE.equals(tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public final class MgmtApiModelProperties {
|
||||
public static final String LINK_TO_TARGET_TYPE = "The link to the target type.";
|
||||
public static final String LINK_TO_TARGET = "The link to the target.";
|
||||
|
||||
public static final String LINK_TO_AUTO_CONFIRM = "The link to the detailed auto confirm state.";
|
||||
|
||||
// software module types
|
||||
public static final String SMT_TYPE = "The type of the software module identified by its key.";
|
||||
public static final String SMT_VENDOR = "The software vendor of the entity.";
|
||||
@@ -93,6 +95,8 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
// rollout
|
||||
public static final String ROLLOUT_FILTER_QUERY = "target filter query language expression";
|
||||
public static final String ROLLOUT_CONFIRMATION_REQUIRED = "(available with user consent flow active) if the confirmation is required for this rollout. Value will be used if confirmation options are missing in the rollout group definitions. Confirmation is required per default";
|
||||
public static final String ROLLOUT_GROUP_CONFIRMATION_REQUIRED = "(available with user consent flow active) if the confirmation is required for this rollout group. Confirmation is required per default.";
|
||||
public static final String ROLLOUT_GROUP_FILTER_QUERY = "target filter query language expression that selects a subset of targets which match the target filter of the Rollout";
|
||||
public static final String ROLLOUT_GROUP_TARGET_PERCENTAGE = "percentage of remaining and matching targets that should be added to this group";
|
||||
public static final String ROLLOUT_DS_ID = "the ID of distributionset of this rollout";
|
||||
@@ -144,6 +148,8 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
public static final String ACTION_FORCE_TYPE = "Force type of the action that provides a hint if the controller should apply the action immediately or whenever possible.";
|
||||
|
||||
public static final String ACTION_CONFIRMATION_REQUIRED = "(Available with user consent flow active) Defines, if the confirmation is required for an action. Confirmation is required per default.";
|
||||
|
||||
public static final String ACTION_FORCE_TIME = "In case of timeforced mode the difference, measured in milliseconds, between the time the action should switch to forced and midnight, January 1, 1970 UTC.";
|
||||
|
||||
public static final String ACTION_FORCED = "Set to forced in order to switch action to forced mode.";
|
||||
@@ -178,6 +184,8 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
public static final String REQUEST_ATTRIBUTES = "Request re-transmission of target attributes.";
|
||||
|
||||
public static final String AUTO_CONFIRM_ACTIVE = "Present if user consent flow active. Indicates if auto-confirm is active";
|
||||
|
||||
public static final String META_DATA = "List of metadata.";
|
||||
|
||||
public static final String META_DATA_KEY = "Metadata property key.";
|
||||
@@ -186,6 +194,21 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
public static final String SM_META_DATA_TARGET_VISIBLE = "Metadata property is visible to targets as part of software update action.";
|
||||
|
||||
public static final String AUTO_CONFIRM_STATE_ACTIVE = "Flag if auto confirm is active";
|
||||
|
||||
public static final String AUTO_CONFIRM_STATE_INITIATOR = "(Optional) initiator set on activation";
|
||||
|
||||
public static final String AUTO_CONFIRM_STATE_REMARK = "(Optional) remark set on activation";
|
||||
|
||||
public static final String AUTO_CONFIRM_STATE_ACTIVATED_AT = "timestamp of the activation";
|
||||
|
||||
public static final String AUTO_CONFIRM_STATE_REFERENCE_ACTIVATE_AUTO_CONFIRM = "reference link to activate auto confirm (present if not active)";
|
||||
|
||||
public static final String AUTO_CONFIRM_STATE_REFERENCE_DEACTIVATE_AUTO_CONFIRM = "reference link to deactivate auto confirm (present if active)";
|
||||
|
||||
public static final String AUTO_CONFIRM_ACTIVATE_INITIATOR = "individual value (e.g. username) stored as initiator and automatically used as confirmed user in future actions";
|
||||
public static final String AUTO_CONFIRM_ACTIVATE_REMARK = "individual value to attach a remark which will be persisted when automatically confirming future actions";
|
||||
|
||||
public static final String SM_TYPE_KEY = "Key that can be interpreted by the target.";
|
||||
|
||||
public static final String SM_MAX_ASSIGNMENTS = "Maximum number of assignments to a distribution set/target, e.g. only one firmware but multiple applications.";
|
||||
@@ -225,6 +248,7 @@ public final class MgmtApiModelProperties {
|
||||
public static final String FORCETIME = "Forcetime in milliseconds.";
|
||||
public static final String FORCE = "Force as boolean.";
|
||||
public static final String ASSIGNMENT_WEIGHT = "Importance of the assignment.";
|
||||
public static final String ASSIGNMENT_CONFIRMATION_REQUIRED = "(Available with user consent flow active) Specifies if the confirmation by the device is required for this action.";
|
||||
public static final String ASSIGNMENT_TYPE = "The type of the assignment.";
|
||||
public static final String TARGET_ASSIGNED = "The number of targets that have been assigned as part of this operation.";
|
||||
public static final String TARGET_ASSIGNED_ALREADY = "The number of targets which already had been the assignment.";
|
||||
|
||||
@@ -354,6 +354,8 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
|
||||
@Description("Handles the POST request for assigning multiple targets to a distribution set.The request body must always be a list of target IDs."
|
||||
+ " Required Permission: " + SpPermission.READ_REPOSITORY + " and " + SpPermission.UPDATE_TARGET)
|
||||
public void createAssignedTarget() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final DistributionSet set = testdataFactory.createUpdatedDistributionSet();
|
||||
|
||||
// prepare targets
|
||||
@@ -396,7 +398,10 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
|
||||
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE),
|
||||
optionalRequestFieldWithPath("[].type")
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_TYPE).attributes(
|
||||
key("value").value("['soft', 'forced','timeforced', 'downloadonly']"))),
|
||||
key("value").value("['soft', 'forced','timeforced', 'downloadonly']")),
|
||||
optionalRequestFieldWithPath("[].confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ACTION_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString())),
|
||||
responseFields(
|
||||
fieldWithPath("assigned").description(MgmtApiModelProperties.DS_NEW_ASSIGNED_TARGETS),
|
||||
fieldWithPath("alreadyAssigned").type(JsonFieldType.NUMBER)
|
||||
|
||||
@@ -51,8 +51,6 @@ import org.springframework.restdocs.payload.FieldDescriptor;
|
||||
import org.springframework.restdocs.payload.JsonFieldType;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
@@ -181,6 +179,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
@Test
|
||||
@Description("Handles the POST request of creating a rollout. Required Permission: " + SpPermission.CREATE_ROLLOUT)
|
||||
public void createRollout() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
testdataFactory.createTargets(20, "targets-");
|
||||
|
||||
@@ -212,6 +211,9 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
|
||||
optionalRequestFieldWithPath("type").description(MgmtApiModelProperties.ROLLOUT_TYPE)
|
||||
.attributes(key("value").value("['soft', 'forced', 'timeforced', 'downloadonly']")),
|
||||
optionalRequestFieldWithPath("confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString()),
|
||||
requestFieldWithPath("distributionSetId").description(MgmtApiModelProperties.ROLLOUT_DS_ID),
|
||||
requestFieldWithPath("targetFilterQuery")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
|
||||
@@ -254,6 +256,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
@Description("Handles the POST request of creating a rollout with a groups definition. Required Permission: "
|
||||
+ SpPermission.CREATE_ROLLOUT)
|
||||
public void createRolloutWithGroupsDefinition() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final int amountTargets = 10;
|
||||
testdataFactory.createTargets(amountTargets, "targets-", "rollout");
|
||||
@@ -285,11 +288,11 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
.successAction(RolloutGroupSuccessAction.NEXTGROUP, "")
|
||||
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, "80")
|
||||
.errorAction(RolloutGroupErrorAction.PAUSE, "").build();
|
||||
mockMvc.perform(
|
||||
post(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING)
|
||||
.content(JsonBuilder.rollout(name, description, null, dsId, targetFilter,
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaTypes.HAL_JSON_VALUE))
|
||||
mockMvc.perform(post(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING)
|
||||
.content(JsonBuilder.rolloutWithGroups(name, description, null, dsId, targetFilter,
|
||||
rolloutGroupConditions, rolloutGroups, null, null, true))
|
||||
.contentType(
|
||||
MediaType.APPLICATION_JSON).accept(MediaTypes.HAL_JSON_VALUE))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON))
|
||||
.andDo(this.document.document(
|
||||
@@ -302,6 +305,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
.description(MgmtApiModelProperties.ROLLOUT_DS_ID),
|
||||
requestFieldWithPath("targetFilterQuery")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
|
||||
requestFieldWithPath("confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_CONFIRMATION_REQUIRED),
|
||||
optionalRequestFieldWithPath("description")
|
||||
.description(ApiModelPropertiesGeneric.DESCRPTION),
|
||||
optionalRequestFieldWithPath("successCondition")
|
||||
@@ -338,6 +343,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
.description(ApiModelPropertiesGeneric.DESCRPTION),
|
||||
optionalRequestFieldWithPath("groups[].targetFilterQuery")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_GROUP_FILTER_QUERY),
|
||||
optionalRequestFieldWithPath("groups[].confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_GROUP_CONFIRMATION_REQUIRED),
|
||||
optionalRequestFieldWithPath("groups[].targetPercentage")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_GROUP_TARGET_PERCENTAGE)
|
||||
.attributes(key("value").value("0..100")),
|
||||
@@ -467,6 +474,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
@Description("Handles the GET request of retrieving the deploy groups of a rollout. Required Permission: "
|
||||
+ SpPermission.READ_ROLLOUT)
|
||||
public void getRolloutDeployGroups() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Rollout rollout = createRolloutEntity();
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}/deploygroups", rollout.getId())
|
||||
@@ -485,6 +494,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
@Description("Handles the GET request of retrieving a deploy group of a rollout. Required Permission: "
|
||||
+ SpPermission.READ_ROLLOUT)
|
||||
public void getRolloutDeployGroup() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Rollout rollout = createRolloutEntity();
|
||||
final RolloutGroup firstRolloutGroup = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent()
|
||||
.get(0);
|
||||
@@ -499,7 +510,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
}
|
||||
|
||||
private Snippet getRolloutDeployGroupResponseFields(final boolean isArray, final boolean withDetails,
|
||||
final FieldDescriptor... descriptors) throws JsonProcessingException {
|
||||
final FieldDescriptor... descriptors) {
|
||||
final String arrayPrefix = getArrayPrefix(isArray);
|
||||
final List<FieldDescriptor> allFieldDescriptor = new ArrayList<>();
|
||||
allFieldDescriptor.addAll(Arrays.asList(descriptors));
|
||||
@@ -534,6 +545,9 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "targetPercentage")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_GROUP_TARGET_PERCENTAGE));
|
||||
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_GROUP_CONFIRMATION_REQUIRED));
|
||||
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "successCondition")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION));
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "successCondition.condition")
|
||||
@@ -663,7 +677,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
rolloutCreate.weight(400);
|
||||
}
|
||||
final Rollout rollout = rolloutManagement.create(rolloutCreate, 5, new RolloutGroupConditionBuilder()
|
||||
final Rollout rollout = rolloutManagement.create(rolloutCreate, 5, false, new RolloutGroupConditionBuilder()
|
||||
.withDefaults().successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
||||
|
||||
// Run here, because Scheduler is disabled during tests
|
||||
|
||||
@@ -61,7 +61,6 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving all target filter queries within SP. Required Permission: READ_TARGET.")
|
||||
public void getTargetFilterQueries() throws Exception {
|
||||
|
||||
createTargetFilterQueryWithDS(createDistributionSet());
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk())
|
||||
@@ -80,6 +79,12 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
|
||||
.description(MgmtApiModelProperties.ACTION_FORCE_TYPE)
|
||||
.type(JsonFieldType.STRING.toString())
|
||||
.attributes(key("value").value("['forced', 'soft', 'downloadonly']")),
|
||||
fieldWithPath("content[].autoAssignWeight")
|
||||
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
|
||||
.type(JsonFieldType.NUMBER.toString()),
|
||||
fieldWithPath("content[].confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ACTION_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString()),
|
||||
fieldWithPath("content[].createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
|
||||
fieldWithPath("content[].createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
|
||||
fieldWithPath("content[].lastModifiedAt")
|
||||
@@ -182,6 +187,8 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
|
||||
@Description("Handles the POST request of setting a distribution set for auto assignment within SP. Required Permission: CREATE_TARGET.")
|
||||
public void postAutoAssignDS() throws Exception {
|
||||
enableMultiAssignments();
|
||||
enableConfirmationFlow();
|
||||
|
||||
final TargetFilterQuery tfq = createTargetFilterQuery();
|
||||
final DistributionSet distributionSet = createDistributionSet();
|
||||
final String autoAssignBody = new JSONObject().put("id", distributionSet.getId())
|
||||
@@ -201,7 +208,10 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
|
||||
.attributes(key("value").value("['forced', 'soft', 'downloadonly']")),
|
||||
requestFieldWithPathMandatoryInMultiAssignMode("weight")
|
||||
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
|
||||
.attributes(key("value").value("0 - 1000"))),
|
||||
.attributes(key("value").value("0 - 1000")),
|
||||
optionalRequestFieldWithPath("confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ACTION_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString())),
|
||||
getResponseFieldTargetFilterQuery(false)));
|
||||
}
|
||||
|
||||
@@ -232,6 +242,9 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
|
||||
fieldWithPath(arrayPrefix + "autoAssignWeight")
|
||||
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
|
||||
.type(JsonFieldType.NUMBER.toString()),
|
||||
fieldWithPath(arrayPrefix + "confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ACTION_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString()),
|
||||
fieldWithPath(arrayPrefix + "createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
|
||||
fieldWithPath(arrayPrefix + "createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
|
||||
fieldWithPath(arrayPrefix + "lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT),
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
package org.eclipse.hawkbit.rest.mgmt.documentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.TARGET_V1_ACTIVATE_AUTO_CONFIRM;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.TARGET_V1_AUTO_CONFIRM;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.TARGET_V1_DEACTIVATE_AUTO_CONFIRM;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
|
||||
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
|
||||
@@ -31,6 +34,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirmUpdate;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.repository.ActionStatusFields;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
@@ -78,6 +82,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving all targets within SP. Required Permission: READ_TARGET.")
|
||||
public void getTargets() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
createTargetByGivenNameWithAttributes(targetId, createDistributionSet());
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)).andExpect(status().isOk())
|
||||
@@ -98,9 +104,11 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
fieldWithPath("content[].securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN),
|
||||
fieldWithPath("content[].requestAttributes")
|
||||
.description(MgmtApiModelProperties.REQUEST_ATTRIBUTES),
|
||||
fieldWithPath("content[].autoConfirmActive")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_ACTIVE),
|
||||
fieldWithPath("content[].installedAt").description(MgmtApiModelProperties.INSTALLED_AT),
|
||||
fieldWithPath("content[].lastModifiedAt")
|
||||
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
|
||||
fieldWithPath("content[].lastModifiedAt").description(
|
||||
ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
|
||||
fieldWithPath("content[].lastModifiedBy")
|
||||
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY).type("String"),
|
||||
fieldWithPath("content[].ipAddress").description(MgmtApiModelProperties.IP_ADDRESS)
|
||||
@@ -125,6 +133,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
@Test
|
||||
@Description("Handles the POST request of creating new targets within SP. The request body must always be a list of targets. Required Permission: CREATE_TARGET.")
|
||||
public void postTargets() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final TargetType defaultType = testdataFactory.createTargetType("defaultType", Collections.emptyList());
|
||||
final String target = createTargetJsonForPostRequest("123456", "controllerId", "test", defaultType);
|
||||
|
||||
@@ -157,6 +167,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
fieldWithPath("[]securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN),
|
||||
fieldWithPath("[]requestAttributes")
|
||||
.description(MgmtApiModelProperties.REQUEST_ATTRIBUTES),
|
||||
fieldWithPath("[]autoConfirmActive")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_ACTIVE),
|
||||
fieldWithPath("[]targetType").description(MgmtApiModelProperties.TARGETTYPE_ID),
|
||||
fieldWithPath("[]targetTypeName").description(MgmtApiModelProperties.TARGETTYPE_NAME),
|
||||
fieldWithPath("[]_links.self").ignored())));
|
||||
@@ -175,6 +187,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving a single target within SP. Required Permission: READ_TARGET.")
|
||||
public void getTarget() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Target target = createTargetByGivenNameWithAttributes(targetId, createDistributionSet());
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}", target.getControllerId()))
|
||||
@@ -187,6 +201,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
@Test
|
||||
@Description("Handles the PUT request of updating a target within SP. Required Permission: UPDATE_TARGET.")
|
||||
public void putTarget() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Target target = createTargetByGivenNameWithAttributes(targetId, createDistributionSet());
|
||||
final String targetAsJson = createJsonTarget(targetId, "newTargetName", "I've been updated");
|
||||
|
||||
@@ -504,7 +520,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.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']")))));
|
||||
"['finished', 'error', 'warning', 'running', 'canceled', 'canceling', " //
|
||||
+ "'retrieved', 'download', 'scheduled', 'cancel_rejected', " //
|
||||
+ "'downloaded', 'wait_for_confirmation']")))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -565,6 +583,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
|
||||
.type(JsonFieldType.NUMBER).attributes(key("value").value("0 - 1000")),
|
||||
optionalRequestFieldWithPath("forcetime").description(MgmtApiModelProperties.FORCETIME),
|
||||
optionalRequestFieldWithPath("[].confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString()),
|
||||
optionalRequestFieldWithPath("maintenanceWindow")
|
||||
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
|
||||
optionalRequestFieldWithPath("maintenanceWindow.schedule")
|
||||
@@ -594,7 +615,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
new JSONObject().put("schedule", getTestSchedule(100))
|
||||
.put("duration", getTestDuration(10)).put("timezone", getTestTimeZone())))
|
||||
.toString();
|
||||
body.put(new JSONObject().put("id", sets.get(0).getId()).put("type", "forced").put("weight", 800));
|
||||
body.put(new JSONObject().put("id", sets.get(0).getId()).put("type", "forced").put("weight", 800)
|
||||
.put("confirmationRequired", true));
|
||||
|
||||
enableMultiAssignments();
|
||||
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/"
|
||||
@@ -611,6 +633,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.attributes(key("value").value("0 - 1000")),
|
||||
optionalRequestFieldWithPath("[].forcetime")
|
||||
.description(MgmtApiModelProperties.FORCETIME),
|
||||
optionalRequestFieldWithPath("[].confirmationRequired")
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_CONFIRMATION_REQUIRED)
|
||||
.type(JsonFieldType.BOOLEAN.toString()),
|
||||
optionalRequestFieldWithPath("[].maintenanceWindow")
|
||||
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
|
||||
optionalRequestFieldWithPath("[].maintenanceWindow.schedule")
|
||||
@@ -869,6 +894,76 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving the auto-confirm state of a target. In this case auto-confirm is active for the requested target. Required Permission: READ_TARGET.")
|
||||
public void getTargetAutoConfirmStateActive() throws Exception {
|
||||
final Target testTarget = testdataFactory.createTarget(targetId);
|
||||
confirmationManagement.activateAutoConfirmation(testTarget.getControllerId(), "custom_initiator_value",
|
||||
"custom_remark");
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM,
|
||||
testTarget.getControllerId())).andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
|
||||
responseFields(
|
||||
fieldWithPath("active").description(MgmtApiModelProperties.AUTO_CONFIRM_STATE_ACTIVE),
|
||||
fieldWithPath("initiator")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_STATE_INITIATOR),
|
||||
fieldWithPath("remark").description(MgmtApiModelProperties.AUTO_CONFIRM_STATE_REMARK),
|
||||
fieldWithPath("activatedAt")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_STATE_ACTIVATED_AT),
|
||||
fieldWithPath("_links.deactivate").optional().description(
|
||||
MgmtApiModelProperties.AUTO_CONFIRM_STATE_REFERENCE_DEACTIVATE_AUTO_CONFIRM))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving the auto-confirm state of a target. In this case auto-confirm is not active for the requested target. Required Permission: READ_TARGET.")
|
||||
public void getTargetAutoConfirmStateNotActive() throws Exception {
|
||||
final Target testTarget = testdataFactory.createTarget(targetId);
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM,
|
||||
testTarget.getControllerId())).andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
|
||||
responseFields(
|
||||
fieldWithPath("active").description(MgmtApiModelProperties.AUTO_CONFIRM_STATE_ACTIVE),
|
||||
fieldWithPath("_links.activate").optional().description(
|
||||
MgmtApiModelProperties.AUTO_CONFIRM_STATE_REFERENCE_ACTIVATE_AUTO_CONFIRM))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the POST request to activate auto-confirm on a target. Payload can be provided to specify more details about the operation. Required Permission: UPDATE_TARGET.")
|
||||
public void postActivateAutoConfirm() throws Exception {
|
||||
final Target testTarget = testdataFactory.createTarget(targetId);
|
||||
|
||||
final MgmtTargetAutoConfirmUpdate body = new MgmtTargetAutoConfirmUpdate("custom_initiator_value",
|
||||
"custom_remark_value");
|
||||
|
||||
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM + "/"
|
||||
+ TARGET_V1_ACTIVATE_AUTO_CONFIRM, testTarget.getControllerId())
|
||||
.content(this.objectMapper.writeValueAsString(body)).contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
|
||||
requestFields(
|
||||
optionalRequestFieldWithPath("initiator")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_ACTIVATE_INITIATOR),
|
||||
optionalRequestFieldWithPath("remark")
|
||||
.description(MgmtApiModelProperties.AUTO_CONFIRM_ACTIVATE_REMARK))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the POST request to deactivate auto-confirm on a target. Required Permission: UPDATE_TARGET.")
|
||||
public void postDeactivateAutoConfirm() throws Exception {
|
||||
final Target testTarget = testdataFactory.createTarget(targetId);
|
||||
confirmationManagement.activateAutoConfirmation(testTarget.getControllerId(), null, null);
|
||||
|
||||
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM + "/"
|
||||
+ TARGET_V1_DEACTIVATE_AUTO_CONFIRM, testTarget.getControllerId())).andExpect(status().isOk())
|
||||
.andDo(MockMvcResultPrinter.print()).andDo(this.document.document(
|
||||
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID))));
|
||||
}
|
||||
|
||||
private String createTargetJsonForPostRequest(final String controllerId, final String name,
|
||||
final String description, final TargetType targetType) throws JsonProcessingException {
|
||||
final Map<String, Object> target = new HashMap<>();
|
||||
|
||||
@@ -153,6 +153,8 @@ public class TargetTagResourceDocumentationTest extends AbstractApiRestDocumenta
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving all assigned targets by the given")
|
||||
public void getAssignedTargets() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final TargetTag tag = createTargetTagEntitiy();
|
||||
final Target target = createTargetByGivenNameWithAttributes("Target1", distributionSet);
|
||||
targetManagement.assignTag(Arrays.asList(target.getControllerId()), tag.getId());
|
||||
@@ -200,6 +202,8 @@ public class TargetTagResourceDocumentationTest extends AbstractApiRestDocumenta
|
||||
@Test
|
||||
@Description("Handles the POST request to assign targets to the given tag id")
|
||||
public void assignTargets() throws Exception {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Long tagId = createTargetTagId();
|
||||
final MgmtAssignedTargetRequestBody assignedTargetRequestBody1 = new MgmtAssignedTargetRequestBody();
|
||||
assignedTargetRequestBody1
|
||||
|
||||
@@ -91,6 +91,8 @@ public class TenantResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
"if multiple distribution sets can be assigned to the same targets.");
|
||||
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED,
|
||||
"if distribution set can be assigned to multiple targets in a single batch message.");
|
||||
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.USER_CONFIRMATION_ENABLED,
|
||||
"if confirmation is required when distribution set is assigned to target.");
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
||||
Reference in New Issue
Block a user