Assign multiple distribution sets to a target via mgmt api (#886)

* Add multiassignment to mgmt api target endpoint
* Remove single assignment ds to targets offline
* Fix tests
* Add quota for maxResultingActionsPerManualAssignment
* Fix assignment with same target or distribution set multiple times in one request
* Log UI error
* Add tests
* Enable single assignment requests with multiple DSs and types
* Remove redundant target to DS assignment methods
* Add tests, fix assignment
* Fix possible nullpointer during target assignment request
* Update api docu
* Clean up deployment management code
* Enforce MaxActions quota for offline assignment
* Fix review findings
* Rename property, add migration into
* Add builder for DeploymentRequest
* Change offline assignment method to accept an assignment list, like online assignment
* Fix PR findings

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>
This commit is contained in:
Stefan Klotz
2019-09-17 14:20:26 +02:00
committed by Stefan Behl
parent dba972423b
commit 8687510131
50 changed files with 1466 additions and 559 deletions

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
@@ -21,20 +22,21 @@ import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEv
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
@@ -48,71 +50,12 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface DeploymentManagement {
/**
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
* Assigns {@link DistributionSet}s to {@link Target}s according to the
* {@link DeploymentRequest}.
*
* @param dsID
* the ID of the distribution set to assign
* @param actionType
* the type of the action to apply on the assignment
* @param forcedTimestamp
* the time when the action should be forced, only necessary for
* {@link ActionType#TIMEFORCED}
* @param controllerIDs
* the IDs of the target to assign the distribution set
* @return the assignment result
*
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
*
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
* @param deploymentRequests
* information about all target-ds-assignments that shall be made
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotNull ActionType actionType,
long forcedTimestamp, @NotEmpty Collection<String> controllerIDs);
/**
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
*
* @param dsID
* the ID of the distribution set to assign
* @param targets
* a list of all targets and their action type
* @return the assignment result
*
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
*
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID,
@NotEmpty Collection<TargetWithActionType> targets);
/**
* Assigns the given set of {@link DistributionSet} entities to the given
* {@link Target}s using the specified {@link ActionType} and
* {@code forcetime}.
*
* @param dsIDs
* the set of IDs of the distribution sets to assign
* @param targets
* a list of all targets and their action type
* @return the list of assignment results
*
* @throws IncompleteDistributionSetException
@@ -126,26 +69,29 @@ public interface DeploymentManagement {
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException
* if the request results in multiple assignments to the same
* target and multiassignment is disabled
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets(@NotEmpty Set<Long> dsIDs,
@NotEmpty Collection<TargetWithActionType> targets);
List<DistributionSetAssignmentResult> assignDistributionSets(
@NotEmpty List<DeploymentRequest> deploymentRequests);
/**
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and an action message.
* Assigns {@link DistributionSet}s to {@link Target}s according to the
* {@link DeploymentRequest}.
*
* @param dsID
* the ID of the distribution set to assign
* @param targets
* a list of all targets and their action type
* @param deploymentRequests
* information about all target-ds-assignments that shall be made
* @param actionMessage
* an optional message for the action status
* @return the assignment result
*
* @return the list of assignment results
*
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
* defined by the {@link DistributionSetType}.
*
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
@@ -154,14 +100,32 @@ public interface DeploymentManagement {
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException
* if the request results in multiple assignments to the same
* target and multiassignment is disabled
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotEmpty Collection<TargetWithActionType> targets,
String actionMessage);
List<DistributionSetAssignmentResult> assignDistributionSets(
@NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage);
/**
* Registers an "offline" assignment, i.e. adds a completed action for the
* given {@link DistributionSet} to the given {@link Target}s.
* build a {@link DeploymentRequest} for a target distribution set
* assignment
*
* @param controllerId
* ID of target
* @param distributionSetId
* ID of distribution set
* @return the builder
*/
static DeploymentRequestBuilder deploymentRequest(final String controllerId, final long distributionSetId) {
return new DeploymentRequestBuilder(controllerId, distributionSetId);
}
/**
* Registers "offline" assignments. "offline" assignment means adding a
* completed action for a {@link DistributionSet} to a {@link Target}.
*
* The handling differs to hawkBit-managed updates by means that:<br/>
*
@@ -174,11 +138,10 @@ public interface DeploymentManagement {
* <li>does not send a {@link TargetAssignDistributionSetEvent}.</li>
* </ol>
*
* @param dsID
* the ID of the distribution set that was assigned
* @param controllerIDs
* a list of IDs of the targets that where assigned
* @return the assignment result
* @param assignments
* target IDs with the respective distribution set ID which they
* are supposed to be assigned to
* @return the assignment results
*
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
@@ -191,9 +154,13 @@ public interface DeploymentManagement {
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*
* @throws MultiAssignmentIsNotEnabledException
* if the request results in multiple assignments to the same
* target and multiassignment is disabled
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult offlineAssignedDistributionSet(Long dsID, Collection<String> controllerIDs);
List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(Collection<Entry<String, Long>> assignments);
/**
* Cancels the {@link Action} with the given ID. The method will immediately

View File

@@ -80,10 +80,10 @@ public interface QuotaManagement {
int getMaxTargetsPerRolloutGroup();
/**
* @return the maximum number of targets which for a manual distribution set
* assignment
* @return the maximum number of target distribution set assignments
* resulting from a manual assignment
*/
int getMaxTargetsPerManualAssignment();
int getMaxTargetDistributionSetAssignmentsPerManualAssignment();
/**
* @return the maximum number of targets for an automatic distribution set

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2019 Bosch Software Innovations 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.repository.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* This exception is thrown if an operation requires multiassignments, but the
* feature is not enabled.
*
*/
public class MultiAssignmentIsNotEnabledException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED;
/**
* Default constructor.
*/
public MultiAssignmentIsNotEnabledException() {
super(THIS_ERROR);
}
/**
* Parameterized constructor.
*
* @param cause
* of the exception
*/
public MultiAssignmentIsNotEnabledException(final Throwable cause) {
super(THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
* @param cause
* of the exception
*/
public MultiAssignmentIsNotEnabledException(final String message, final Throwable cause) {
super(message, THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
*/
public MultiAssignmentIsNotEnabledException(final String message) {
super(message, THIS_ERROR);
}
}

View File

@@ -120,7 +120,8 @@ public final class QuotaExceededException extends AbstractServerRtException {
* The maximum number of entities that can be assigned to the
* parent entity.
*/
public QuotaExceededException(final String type, final String parentType, final Long parentId, final long requested,
public QuotaExceededException(final String type, final String parentType, final Object parentId,
final long requested,
final long quota) {
super(String.format(ASSIGNMENT_QUOTA_EXCEEDED_MESSAGE, requested, type, parentType,
parentId != null ? String.valueOf(parentId) : "<new>", quota), SpServerError.SP_QUOTA_EXCEEDED);

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2019 Bosch Software Innovations 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.repository.model;
import java.util.Objects;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
/**
* A custom view on assigning a {@link DistributionSet} to a {@link Target}.
*
*/
public class DeploymentRequest {
private final Long distributionSetId;
private final TargetWithActionType targetWithActionType;
/**
* Constructor that also accepts maintenance schedule parameters and checks
* for validity of the specified maintenance schedule.
*
* @param controllerId
* for which the action is created.
* @param distributionSetId
* of the distribution set that that should be assigned to the
* controller.
* @param actionType
* specified for the action.
* @param forceTime
* at what time the type soft turns into forced.
* @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance
* windows. Expression has 6 mandatory fields and 1 last optional
* field: "second minute hour dayofmonth month weekday year"
* @param maintenanceWindowDuration
* in HH:mm:ss format specifying the duration of a maintenance
* window, for example 00:30:00 for 30 minutes
* @param maintenanceWindowTimeZone
* is the time zone specified as +/-hh:mm offset from UTC, for
* example +02:00 for CET summer time and +00:00 for UTC. The
* start time of a maintenance window calculated based on the
* cron expression is relative to this time zone.
*
* @throws InvalidMaintenanceScheduleException
* if the parameters do not define a valid maintenance schedule.
*/
public DeploymentRequest(final String controllerId, final Long distributionSetId, final ActionType actionType,
final long forceTime, final String maintenanceSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
this.targetWithActionType = new TargetWithActionType(controllerId, actionType, forceTime, maintenanceSchedule,
maintenanceWindowDuration,
maintenanceWindowTimeZone);
this.distributionSetId = distributionSetId;
}
public Long getDistributionSetId() {
return distributionSetId;
}
public String getControllerId() {
return targetWithActionType.getControllerId();
}
public TargetWithActionType getTargetWithActionType() {
return targetWithActionType;
}
@Override
public String toString() {
return String.format(
"DeploymentRequest [controllerId=%s, distributionSetId=%d, actionType=%s, forceTime=%d, maintenanceSchedule=%s, maintenanceWindowDuration=%s, maintenanceWindowTimeZone=%s]",
targetWithActionType.getControllerId(), getDistributionSetId(), targetWithActionType.getActionType(),
targetWithActionType.getForceTime(), targetWithActionType.getMaintenanceSchedule(),
targetWithActionType.getMaintenanceWindowDuration(),
targetWithActionType.getMaintenanceWindowTimeZone());
}
@Override
public int hashCode() {
return Objects.hash(distributionSetId, targetWithActionType);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DeploymentRequest other = (DeploymentRequest) obj;
return Objects.equals(distributionSetId, other.distributionSetId)
&& Objects.equals(targetWithActionType, other.targetWithActionType);
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2019 Bosch Software Innovations 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.repository.model;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
/**
* Builder for {@link DeploymentRequest}
*
*/
public class DeploymentRequestBuilder {
private final String controllerId;
private final Long distributionSetId;
private long forceTime = RepositoryModelConstants.NO_FORCE_TIME;
private ActionType actionType = ActionType.FORCED;
private String maintenanceSchedule;
private String maintenanceWindowDuration;
private String maintenanceWindowTimeZone;
/**
* Create a builder for a target distribution set assignment with the
* mandatory fields
*
* @param controllerId
* ID of the target
* @param distributionSetId
* ID of the distribution set
*/
public DeploymentRequestBuilder(final String controllerId, final Long distributionSetId) {
this.controllerId = controllerId;
this.distributionSetId = distributionSetId;
}
/**
* Set an other {@link ActionType} than {@link ActionType#FORCED}
*
* @param actionType
* type to used
* @return builder
*/
public DeploymentRequestBuilder setActionType(final ActionType actionType) {
this.actionType = actionType;
return this;
}
/**
* Set a forceTime other than the default one.
*
* @param forceTime
* at what time the type soft turns into forced.
* @return builder
*/
public DeploymentRequestBuilder setForceTime(final long forceTime) {
this.forceTime = forceTime;
return this;
}
/**
* Set a maintenanceWindow
*
* @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance
* windows. Expression has 6 mandatory fields and 1 last optional
* field: "second minute hour dayofmonth month weekday year"
* @param maintenanceWindowDuration
* in HH:mm:ss format specifying the duration of a maintenance
* window, for example 00:30:00 for 30 minutes
* @param maintenanceWindowTimeZone
* is the time zone specified as +/-hh:mm offset from UTC, for
* example +02:00 for CET summer time and +00:00 for UTC. The
* start time of a maintenance window calculated based on the
* cron expression is relative to this time zone.
* @return builder
*/
public DeploymentRequestBuilder setMaintenance(final String maintenanceSchedule,
final String maintenanceWindowDuration, final String maintenanceWindowTimeZone) {
this.maintenanceSchedule = maintenanceSchedule;
this.maintenanceWindowDuration = maintenanceWindowDuration;
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
return this;
}
/**
* build the request
*
* @return the request object
*/
public DeploymentRequest build() {
return new DeploymentRequest(controllerId, distributionSetId, actionType, forceTime, maintenanceSchedule,
maintenanceWindowDuration, maintenanceWindowTimeZone);
}
}

View File

@@ -8,6 +8,8 @@
*/
package org.eclipse.hawkbit.repository.model;
import java.util.Objects;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -16,7 +18,6 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
*
*/
public class TargetWithActionType {
private final String controllerId;
private final ActionType actionType;
private final long forceTime;
@@ -24,10 +25,27 @@ public class TargetWithActionType {
private String maintenanceWindowDuration;
private String maintenanceWindowTimeZone;
/**
* Constructor that uses {@link ActionType#FORCED}
*
* @param controllerId
* ID if the controller
*/
public TargetWithActionType(final String controllerId) {
this(controllerId, ActionType.FORCED, 0);
}
/**
* Constructor that leaves the maintenance info empty
*
* @param controllerId
* for which the action is created.
* @param actionType
* specified for the action.
* @param forceTime
* after that point in time the action is exposed as forcen in
* case the type is {@link ActionType#TIMEFORCED}
*/
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime) {
this.controllerId = controllerId;
this.actionType = actionType != null ? actionType : ActionType.FORCED;
@@ -42,6 +60,9 @@ public class TargetWithActionType {
* for which the action is created.
* @param actionType
* specified for the action.
* @param forceTime
* after that point in time the action is exposed as forcen in
* case the type is {@link ActionType#TIMEFORCED}
* @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance
* windows. Expression has 6 mandatory fields and 1 last optional
@@ -122,4 +143,30 @@ public class TargetWithActionType {
+ "]";
}
@Override
public int hashCode() {
return Objects.hash(actionType, controllerId, forceTime, maintenanceSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone);
}
@SuppressWarnings("squid:S1067")
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TargetWithActionType other = (TargetWithActionType) obj;
return Objects.equals(actionType, other.actionType) && Objects.equals(controllerId, other.controllerId)
&& Objects.equals(forceTime, other.forceTime)
&& Objects.equals(maintenanceSchedule, other.maintenanceSchedule)
&& Objects.equals(maintenanceWindowDuration, other.maintenanceWindowDuration)
&& Objects.equals(maintenanceWindowTimeZone, other.maintenanceWindowTimeZone);
}
}