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);
}
}

View File

@@ -89,8 +89,8 @@ public class PropertiesQuotaManagement implements QuotaManagement {
}
@Override
public int getMaxTargetsPerManualAssignment() {
return securityProperties.getDos().getMaxTargetsPerManualAssignment();
public int getMaxTargetDistributionSetAssignmentsPerManualAssignment() {
return securityProperties.getDos().getMaxTargetDistributionSetAssignmentsPerManualAssignment();
}
@Override

View File

@@ -10,9 +10,9 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -51,17 +51,19 @@ public abstract class AbstractDsAssignmentStrategy {
protected final ActionRepository actionRepository;
private final ActionStatusRepository actionStatusRepository;
private final QuotaManagement quotaManagement;
private final BooleanSupplier multiAssignmentsConfig;
AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement) {
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig) {
this.targetRepository = targetRepository;
this.afterCommit = afterCommit;
this.eventPublisherHolder = eventPublisherHolder;
this.actionRepository = actionRepository;
this.actionStatusRepository = actionStatusRepository;
this.quotaManagement = quotaManagement;
this.multiAssignmentsConfig = multiAssignmentsConfig;
}
/**
@@ -199,14 +201,14 @@ public abstract class AbstractDsAssignmentStrategy {
new CancelTargetAssignmentEvent(target, actionId, eventPublisherHolder.getApplicationId())));
}
JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, final JpaTarget target,
JpaAction createTargetAction(final TargetWithActionType targetWithActionType, final List<JpaTarget> targets,
final JpaDistributionSet set) {
// enforce the 'max actions per target' quota
assertActionsPerTargetQuota(target, 1);
final Optional<JpaTarget> optTarget = targets.stream()
.filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst();
// create the action
return getTargetWithActionType(targetsWithActionMap, target.getControllerId()).map(targetWithActionType -> {
return optTarget.map(target -> {
assertActionsPerTargetQuota(target, 1);
final JpaAction actionForTarget = new JpaAction();
actionForTarget.setActionType(targetWithActionType.getActionType());
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
@@ -218,7 +220,7 @@ public abstract class AbstractDsAssignmentStrategy {
actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone());
return actionForTarget;
}).orElseGet(() -> {
LOG.warn("Cannot find targetWithActionType for target '{}'.", target.getControllerId());
LOG.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId());
return null;
});
}
@@ -241,14 +243,7 @@ public abstract class AbstractDsAssignmentStrategy {
actionRepository::countByTargetId);
}
private static Optional<TargetWithActionType> getTargetWithActionType(
final Map<String, TargetWithActionType> targetsWithActionMap, final String controllerId) {
if (targetsWithActionMap.containsKey(controllerId)) {
return Optional.of(targetsWithActionMap.get(controllerId));
} else {
return targetsWithActionMap.values().stream()
.filter(t -> controllerId.equalsIgnoreCase(t.getControllerId())).findFirst();
}
protected boolean isMultiAssignmentsEnabled() {
return multiAssignmentsConfig.getAsBoolean();
}
}

View File

@@ -18,6 +18,7 @@ import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -43,6 +44,7 @@ import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -59,6 +61,7 @@ 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -81,8 +84,12 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -131,6 +138,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private final SystemSecurityContext systemSecurityContext;
private final TenantAware tenantAware;
private final Database database;
private final RetryTemplate retryTemplate;
protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
@@ -150,80 +158,88 @@ public class JpaDeploymentManagement implements DeploymentManagement {
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisherHolder,
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled);
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit,
eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement);
eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement,
this::isMultiAssignmentsEnabled);
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.quotaManagement = quotaManagement;
this.systemSecurityContext = systemSecurityContext;
this.tenantAware = tenantAware;
this.database = database;
retryTemplate = createRetryTemplate();
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult offlineAssignedDistributionSet(final Long dsID,
final Collection<String> controllerIDs) {
final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID,
controllerIDs.stream()
.map(controllerId -> new TargetWithActionType(controllerId, ActionType.FORCED, -1))
.collect(Collectors.toList()),
null, offlineDsAssignmentStrategy);
offlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult assignDistributionSet(final long dsID, final ActionType actionType,
final long forcedTimestamp, final Collection<String> controllerIDs) {
final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID,
controllerIDs.stream()
.map(controllerId -> new TargetWithActionType(controllerId, actionType, forcedTimestamp))
.collect(Collectors.toList()),
null, onlineDsAssignmentStrategy);
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult assignDistributionSet(final long dsID,
final Collection<TargetWithActionType> targets) {
final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID, targets, null,
onlineDsAssignmentStrategy);
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
}
@Override
public List<DistributionSetAssignmentResult> assignDistributionSets(final Set<Long> dsIDs,
final Collection<TargetWithActionType> targets) {
final List<DistributionSetAssignmentResult> results = dsIDs.stream()
.map(dsID -> assignDistributionSetToTargets(dsID, targets, null, onlineDsAssignmentStrategy))
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final Collection<Entry<String, Long>> assignments) {
final Collection<Entry<String, Long>> distinctAssignments = assignments.stream().distinct()
.collect(Collectors.toList());
onlineDsAssignmentStrategy.sendDeploymentEvents(results);
enforceMaxAssignmentsPerRequest(distinctAssignments.size());
final List<DeploymentRequest> deploymentRequests = distinctAssignments.stream()
.map(entry -> DeploymentManagement.deploymentRequest(entry.getKey(), entry.getValue()).build())
.collect(Collectors.toList());
return assignDistributionSets(deploymentRequests, null, offlineDsAssignmentStrategy);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests) {
return assignDistributionSets(deploymentRequests, null);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
}
private List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests, final String actionMessage,
final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> validatedRequests = validateRequestForAssignments(deploymentRequests);
final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests);
final List<DistributionSetAssignmentResult> results = assignmentsByDsIds.entrySet().stream()
.map(entry -> assignDistributionSetToTargetsWithRetry(entry.getKey(), entry.getValue(), actionMessage,
strategy))
.collect(Collectors.toList());
strategy.sendDeploymentEvents(results);
return results;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult assignDistributionSet(final long dsID,
final Collection<TargetWithActionType> targets, final String actionMessage) {
private List<DeploymentRequest> validateRequestForAssignments(List<DeploymentRequest> deploymentRequests) {
if (!isMultiAssignmentsEnabled()) {
deploymentRequests = deploymentRequests.stream().distinct().collect(Collectors.toList());
checkIfRequiresMultiAssignment(deploymentRequests);
}
checkQuotaForAssignment(deploymentRequests);
return deploymentRequests;
}
final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID, targets, actionMessage,
onlineDsAssignmentStrategy);
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
private static Map<Long, List<TargetWithActionType>> convertRequest(
final Collection<DeploymentRequest> deploymentRequests) {
return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId,
Collectors.mapping(DeploymentRequest::getTargetWithActionType, Collectors.toList())));
}
private static void checkIfRequiresMultiAssignment(final Collection<DeploymentRequest> deploymentRequests) {
final long distinctTargetsInRequest = deploymentRequests.stream()
.map(request -> request.getTargetWithActionType().getControllerId()).distinct().count();
if (distinctTargetsInRequest < deploymentRequests.size()) {
throw new MultiAssignmentIsNotEnabledException();
}
}
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final Long dsID,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback = retryContext -> assignDistributionSetToTargets(
dsID, targetsWithActionType, actionMessage, assignmentStrategy);
return retryTemplate.execute(retryCallback);
}
/**
@@ -259,10 +275,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID);
checkQuotaForAssignment(targetsWithActionType, distributionSetEntity);
final List<String> targetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId).distinct()
.collect(Collectors.toList());
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(
targetsWithActionType.stream().map(TargetWithActionType::getControllerId).collect(Collectors.toList()),
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(targetIds,
distributionSetEntity.getId());
if (targetEntities.isEmpty()) {
@@ -288,7 +304,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet distributionSetEntity,
final List<JpaTarget> targetEntities) {
final List<List<Long>> targetEntitiesIdsChunks = getTargetEntitiesAsChunks(targetEntities);
closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks);
if (!isMultiAssignmentsEnabled()) {
closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks);
}
// cancel all scheduled actions which are in-active, these actions were
// not active before and the manual assignment which has been done
// cancels them
@@ -320,7 +339,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private static DistributionSetAssignmentResult buildAssignmentResult(final JpaDistributionSet distributionSet,
final List<JpaAction> assignedActions, final int totalTargetsForAssignment) {
int alreadyAssignedTargetsCount = totalTargetsForAssignment - assignedActions.size();
final int alreadyAssignedTargetsCount = totalTargetsForAssignment - assignedActions.size();
return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions);
}
@@ -337,37 +356,31 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return distributionSet;
}
private void checkQuotaForAssignment(final Collection<TargetWithActionType> targetsWithActionType,
final JpaDistributionSet distributionSet) {
// enforce the 'max targets per manual assignment' quota
if (!targetsWithActionType.isEmpty()) {
assertMaxTargetsPerManualAssignmentQuota(distributionSet.getId(), targetsWithActionType.size());
private void checkQuotaForAssignment(final Collection<DeploymentRequest> deploymentRequests) {
if (!deploymentRequests.isEmpty()) {
enforceMaxAssignmentsPerRequest(deploymentRequests.size());
enforceMaxActionsPerTarget(deploymentRequests);
}
}
/**
* Enforces the quota defining the maximum number of {@link Target}s per
* manual {@link DistributionSet} assignment.
*
* @param id
* of the distribution set
* @param requested
* number of targets to check
*/
private void assertMaxTargetsPerManualAssignmentQuota(final Long distributionSetId,
final int requestedTargetsCount) {
QuotaHelper.assertAssignmentQuota(distributionSetId, requestedTargetsCount,
quotaManagement.getMaxTargetsPerManualAssignment(), Target.class, DistributionSet.class, null);
private void enforceMaxAssignmentsPerRequest(final int requestedActions) {
QuotaHelper.assertAssignmentRequestSizeQuota(requestedActions,
quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment());
}
private void enforceMaxActionsPerTarget(final Collection<DeploymentRequest> deploymentRequests) {
final int quota = quotaManagement.getMaxActionsPerTarget();
final Map<String, Long> countOfTargtInRequest = deploymentRequests.stream()
.map(DeploymentRequest::getControllerId)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
countOfTargtInRequest.forEach((controllerId, count) -> QuotaHelper.assertAssignmentQuota(controllerId, count,
quota, Action.class, Target.class, actionRepository::countByTargetControllerId));
}
private void closeOrCancelActiveActions(final AbstractDsAssignmentStrategy assignmentStrategy,
final List<List<Long>> targetIdsChunks) {
if (isMultiAssignmentsEnabled()) {
LOG.debug("Multi Assignments feature is enabled: No need to close /cancel active actions.");
return;
}
if (isActionsAutocloseEnabled()) {
assignmentStrategy.closeActiveActions(targetIdsChunks);
} else {
@@ -396,10 +409,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private List<JpaAction> createActions(final Collection<TargetWithActionType> targetsWithActionType,
final List<JpaTarget> targets, final AbstractDsAssignmentStrategy assignmentStrategy,
final JpaDistributionSet set) {
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
.collect(Collectors.toMap(TargetWithActionType::getControllerId, Function.identity()));
return targets.stream().map(trg -> assignmentStrategy.createTargetAction(targetsWithActionMap, trg, set))
return targetsWithActionType.stream().map(twt -> assignmentStrategy.createTargetAction(twt, targets, set))
.filter(Objects::nonNull).map(actionRepository::save).collect(Collectors.toList());
}
@@ -820,4 +831,17 @@ public class JpaDeploymentManagement implements DeploymentManagement {
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
}
private static RetryTemplate createRetryTemplate() {
final RetryTemplate template = new RetryTemplate();
final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(Constants.TX_RT_DELAY);
template.setBackOffPolicy(backOffPolicy);
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(Constants.TX_RT_MAX,
Collections.singletonMap(ConcurrencyFailureException.class, true));
template.setRetryPolicy(retryPolicy);
return template;
}
}

View File

@@ -11,8 +11,9 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -44,9 +45,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OfflineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement) {
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig) {
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
quotaManagement);
quotaManagement, multiAssignmentsConfig);
}
@Override
@@ -59,10 +60,15 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
@Override
public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
.map(ids -> targetRepository.findAll(SpecificationsBuilder.combineWithAnd(
Arrays.asList(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId),
TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING)))))
final Function<List<String>, List<JpaTarget>> mapper;
if (isMultiAssignmentsEnabled()) {
mapper = targetRepository::findAllByControllerId;
} else {
mapper = ids -> targetRepository.findAll(SpecificationsBuilder.combineWithAnd(
Arrays.asList(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId),
TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING))));
}
return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper)
.flatMap(List::stream).collect(Collectors.toList());
}
@@ -84,9 +90,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
@Override
protected JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap,
final JpaTarget target, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(targetsWithActionMap, target, set);
protected JpaAction createTargetAction(final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(targetWithActionType, targets, set);
if (result != null) {
result.setStatus(Status.FINISHED);
result.setActive(Boolean.FALSE);

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
@@ -46,15 +45,12 @@ import com.google.common.collect.Lists;
*/
public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
private final BooleanSupplier multiAssignmentsConfig;
OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig) {
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
quotaManagement);
this.multiAssignmentsConfig = multiAssignmentsConfig;
quotaManagement, multiAssignmentsConfig);
}
@Override
@@ -130,9 +126,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
@Override
JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, final JpaTarget target,
JpaAction createTargetAction(final TargetWithActionType targetWithActionType, final List<JpaTarget> targets,
final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(targetsWithActionMap, target, set);
final JpaAction result = super.createTargetAction(targetWithActionType, targets, set);
if (result != null) {
result.setStatus(Status.RUNNING);
}
@@ -207,10 +203,6 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
.publishEvent(new MultiActionEvent(tenant, eventPublisherHolder.getApplicationId(), controllerIds)));
}
private boolean isMultiAssignmentsEnabled() {
return multiAssignmentsConfig.getAsBoolean();
}
private static Stream<Action> filterCancellations(final List<Action> actions) {
return actions.stream().filter(action -> {
final Status actionStatus = action.getStatus();

View File

@@ -19,11 +19,10 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
@@ -143,11 +142,11 @@ public class AutoAssignChecker {
return DeploymentHelper.runInNewTransaction(transactionManager, "autoAssignDSToTargets",
Isolation.READ_COMMITTED.value(), status -> {
final List<TargetWithActionType> targets = getTargetsWithActionType(targetFilterQuery.getQuery(),
dsId, targetFilterQuery.getAutoAssignActionType(), PAGE_SIZE);
final int count = targets.size();
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(
targetFilterQuery.getQuery(), dsId, targetFilterQuery.getAutoAssignActionType(), PAGE_SIZE);
final int count = deploymentRequests.size();
if (count > 0) {
deploymentManagement.assignDistributionSet(dsId, targets, actionMessage);
deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage);
}
return count;
});
@@ -168,7 +167,7 @@ public class AutoAssignChecker {
* maximum amount of targets to retrieve
* @return list of targets with action type
*/
private List<TargetWithActionType> getTargetsWithActionType(final String targetFilterQuery, final Long dsId,
private List<DeploymentRequest> createAssignmentRequests(final String targetFilterQuery, final Long dsId,
final ActionType type, final int count) {
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNonDS(PageRequest.of(0, count), dsId,
targetFilterQuery);
@@ -176,8 +175,10 @@ public class AutoAssignChecker {
// specified)
final ActionType autoAssignActionType = type == null ? ActionType.FORCED : type;
return targets.getContent().stream().map(t -> new TargetWithActionType(t.getControllerId(),
autoAssignActionType, RepositoryModelConstants.NO_FORCE_TIME)).collect(Collectors.toList());
return targets.getContent().stream()
.map(t -> DeploymentManagement.deploymentRequest(t.getControllerId(), dsId)
.setActionType(autoAssignActionType).build())
.collect(Collectors.toList());
}
}

View File

@@ -8,7 +8,7 @@
*/
package org.eclipse.hawkbit.repository.jpa.utils;
import java.util.function.Function;
import java.util.function.ToLongFunction;
import javax.validation.constraints.NotNull;
@@ -76,8 +76,8 @@ public final class QuotaHelper {
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final Long parentId, final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final Function<Long, Long> countFct) {
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final ToLongFunction<T> countFct) {
assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct);
}
@@ -104,8 +104,8 @@ public final class QuotaHelper {
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final Long parentId, final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType, final Function<Long, Long> countFct) {
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType, final ToLongFunction<T> countFct) {
// check if the quota is unlimited
if (limit <= 0) {
@@ -121,7 +121,7 @@ public final class QuotaHelper {
}
if (parentId != null && countFct != null) {
final long currentCount = countFct.apply(parentId);
final long currentCount = countFct.applyAsLong(parentId);
if (currentCount + requested > limit) {
LOG.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.",
@@ -130,4 +130,22 @@ public final class QuotaHelper {
}
}
}
/**
* Assert that the number of assignments in a request does not exceed the
* limit.
*
* @param requested
* the number of assignments that are to be made
* @param limit
* the maximum number of assignments per request
*/
public static void assertAssignmentRequestSizeQuota(final long requested, final long limit) {
if (requested > limit) {
final String message = String.format(
"Quota exceeded: Cannot assign %s entities at once. The maximum is %s.", requested, limit);
LOG.warn(message);
throw new QuotaExceededException(message);
}
}
}

View File

@@ -55,7 +55,6 @@ import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
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.Artifact;
@@ -1328,8 +1327,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final String knownExternalref = "externalRefId" + i;
testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
knownDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(knownDistributionSet.getId(),
knownControllerId);
final Long actionId = getFirstAssignedActionId(assignmentResult);
controllerManagement.updateActionExternalRef(actionId, knownExternalref);

View File

@@ -13,15 +13,18 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.event.remote.MultiActionEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
@@ -35,6 +38,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
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.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -47,13 +51,13 @@ 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
@@ -100,12 +104,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget();
final String dsName = "DistributionSet";
verifyThrownExceptionBy(() -> deploymentManagement.assignDistributionSet(NOT_EXIST_IDL,
Collections.singletonList(new TargetWithActionType(target.getControllerId()))), dsName);
verifyThrownExceptionBy(() -> deploymentManagement.assignDistributionSet(NOT_EXIST_IDL,
Collections.singletonList(new TargetWithActionType(target.getControllerId())), "xxx"), dsName);
verifyThrownExceptionBy(() -> deploymentManagement.assignDistributionSet(NOT_EXIST_IDL, ActionType.FORCED,
System.currentTimeMillis(), Collections.singletonList(target.getControllerId())), dsName);
verifyThrownExceptionBy(() -> assignDistributionSet(NOT_EXIST_IDL, target.getControllerId()), dsName);
verifyThrownExceptionBy(() -> deploymentManagement.cancelAction(NOT_EXIST_IDL), "Action");
verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget(NOT_EXIST_ID), "Target");
@@ -158,34 +157,35 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Test verifies that the 'max actions per target' quota is enforced if the assigned distribution set is changed permanently.")
public void changeDistributionSetAssignmentUntilMaxActionsPerTargetQuotaIsExceeded() {
@Description("Test verifies that the 'max actions per target' quota is enforced.")
public void assertMaxActionsPerTargetQuotaIsEnforced() {
final int maxActions = quotaManagement.getMaxActionsPerTarget();
final List<Target> testTargets = testdataFactory.createTargets(1);
final Target testTarget = testdataFactory.createTarget();
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
IntStream.range(0, maxActions).forEach(i -> {
assignDistributionSet(i % 2 == 0 ? ds1 : ds2, testTargets);
});
enableMultiAssignments();
for (int i = 0; i < maxActions; i++) {
deploymentManagement.offlineAssignedDistributionSets(Collections
.singletonList(new SimpleEntry<String, Long>(testTarget.getControllerId(), ds1.getId())));
}
// change the distribution set one last time to trigger a quota hit
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds3, testTargets));
.isThrownBy(() -> assignDistributionSet(ds1, Collections.singletonList(testTarget)));
}
@Test
@Description("Assigns the same distribution set to many targets until the 'max targets per manual assignment' quota is exceeded.")
public void assignDistributionSetUntilQuotaIsExceeded() {
@Description("An assignment request with more assignments than allowed by 'maxTargetDistributionSetAssignmentsPerManualAssignment' quota throws an exception.")
public void assignmentRequestThatIsTooLarge() {
final int maxActions = quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment();
final DistributionSet ds1 = testdataFactory.createDistributionSet("1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
final int maxTargets = quotaManagement.getMaxTargetsPerManualAssignment();
final DistributionSet ds = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(maxActions, "assignmentTest1");
assignDistributionSet(ds1, targets);
assignDistributionSet(ds, testdataFactory.createTargets(maxTargets, "ok"));
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds, testdataFactory.createTargets(maxTargets + 1, "fail")));
targets.add(testdataFactory.createTarget("assignmentTest2"));
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> assignDistributionSet(ds2, targets));
}
@Test
@@ -455,6 +455,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
public void assignedDistributionSet() {
final List<String> controllerIds = testdataFactory.createTargets(10).stream().map(Target::getControllerId)
.collect(Collectors.toList());
final List<Target> onlineAssignedTargets = testdataFactory.createTargets(10, "2");
@@ -464,8 +465,15 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assignDistributionSet(testdataFactory.createDistributionSet("2"), onlineAssignedTargets);
final long current = System.currentTimeMillis();
final List<Target> targets = deploymentManagement.offlineAssignedDistributionSet(ds.getId(), controllerIds)
.getAssignedEntity().stream().map(Action::getTarget).collect(Collectors.toList());;
final List<Entry<String, Long>> offlineAssignments = controllerIds.stream()
.map(targetId -> new SimpleEntry<String, Long>(targetId, ds.getId())).collect(Collectors.toList());
final List<DistributionSetAssignmentResult> assignmentResults = deploymentManagement
.offlineAssignedDistributionSets(offlineAssignments);
assertThat(assignmentResults).hasSize(1);
final List<Target> targets = assignmentResults.get(0).getAssignedEntity().stream().map(Action::getTarget)
.collect(Collectors.toList());
assertThat(actionRepository.count()).isEqualTo(20);
assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active")
.allMatch(action -> !action.isActive());
@@ -480,6 +488,33 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.allMatch(target -> target.getLastModifiedAt() == target.getInstallationDate());
}
@Test
@Description("Offline assign multiple DSs to multiple Targets in multiassignment mode.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 4), @Expect(type = ActionCreatedEvent.class, count = 4),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6) })
public void multiOfflineAssignment() {
final List<String> targetIds = testdataFactory.createTargets(2).stream().map(Target::getControllerId)
.collect(Collectors.toList());
final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId)
.collect(Collectors.toList());
enableMultiAssignments();
final List<Entry<String, Long>> offlineAssignments = new ArrayList<>();
targetIds.forEach(targetId -> dsIds
.forEach(dsId -> offlineAssignments.add(new SimpleEntry<String, Long>(targetId, dsId))));
final List<DistributionSetAssignmentResult> assignmentResults = deploymentManagement
.offlineAssignedDistributionSets(offlineAssignments);
assertThat(getResultingActionCount(assignmentResults)).isEqualTo(4);
targetIds.forEach(controllerId -> {
final List<Long> assignedDsIds = actionRepository.findByTargetControllerId(PAGE, controllerId).stream()
.map(action -> action.getDistributionSet().getId()).collect(Collectors.toList());
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
});
}
@Test
@Description("Verifies that if an account is set to action autoclose running actions in case of a new assigned set get closed and set to CANCELED.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 10),
@@ -555,6 +590,92 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray());
}
@Test
@Description("Assign multiple DSs to multiple Targets in one request in multiassignment mode.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 4), @Expect(type = ActionCreatedEvent.class, count = 4),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = MultiActionEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0) })
public void multiassignmentInOneRequest() {
final List<Target> targets = testdataFactory.createTargets(2);
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets);
enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(deploymentRequests);
assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size());
final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toList());
targets.forEach(target -> {
final List<Long> assignedDsIds = actionRepository.findByTargetControllerId(PAGE, target.getControllerId())
.stream().map(action -> action.getDistributionSet().getId()).collect(Collectors.toList());
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
});
}
@Test
@Description("A Request resulting in multiple assignments to a single target is only allowed when multiassignment is enabled.")
public void multipleAssignmentsToTargetOnlyAllowedInMultiAssignMode() {
final Target target = testdataFactory.createTarget();
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
final DeploymentRequest targetToDS0 = DeploymentManagement
.deploymentRequest(target.getControllerId(), distributionSets.get(0).getId()).build();
final DeploymentRequest targetToDS1 = DeploymentManagement
.deploymentRequest(target.getControllerId(), distributionSets.get(1).getId()).build();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1)));
enableMultiAssignments();
assertThat(getResultingActionCount(
deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1)))).isEqualTo(2);
}
@Test
@Description("Duplicate Assignments are removed from a request when multiassignment is disabled, otherwise not")
public void duplicateAssignmentsInRequestAreOnlyRemovedIfMultiassignmentDisabled() {
final Target target = testdataFactory.createTarget();
final DistributionSet ds = testdataFactory.createDistributionSet();
final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2,
DeploymentManagement.deploymentRequest(target.getControllerId(), ds.getId()).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments)))
.isEqualTo(1);
enableMultiAssignments();
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments)))
.isEqualTo(2);
}
private int getResultingActionCount(final List<DistributionSetAssignmentResult> results) {
return results.stream().map(DistributionSetAssignmentResult::getTotal).reduce(0, Integer::sum);
}
@Test
@Description("An assignment request is not accepted if it would lead to a target exceeding the max actions per target quota.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0) })
public void maxActionsPerTargetIsCheckedBeforeAssignmentExecution() {
final int maxActions = quotaManagement.getMaxActionsPerTarget();
final String controllerId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> deploymentRequests = Collections.nCopies(maxActions + 1,
DeploymentManagement.deploymentRequest(controllerId, dsId).build());
enableMultiAssignments();
Assertions.assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests));
assertThat(actionRepository.countByTargetControllerId(controllerId)).isEqualTo(0);
}
/**
* test a simple deployment by calling the
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
@@ -563,11 +684,11 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/
@Test
@Description("Simple deployment or distribution set to target assignment test.")
@ExpectEvents({@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@ExpectEvents({ @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 30), @Expect(type = ActionCreatedEvent.class, count = 20),
@Expect(type = TargetUpdatedEvent.class, count = 20),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void assignDistributionSet2Targets() {
final String myCtrlIDPref = "myCtrlID";
@@ -617,7 +738,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Test that it is not possible to assign a distribution set that is not complete.")
@ExpectEvents({@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@ExpectEvents({ @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 10), @Expect(type = ActionCreatedEvent.class, count = 10),
@Expect(type = TargetUpdatedEvent.class, count = 10),
@@ -650,14 +771,14 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Multiple deployments or distribution set to target assignment test. Expected behaviour is that a new deployment "
+ "overides unfinished old one which are canceled as part of the operation.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 5 + 4),
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 5 + 4),
@Expect(type = TargetUpdatedEvent.class, count = 3 * 4),
@Expect(type = ActionCreatedEvent.class, count = 3 * 4),
@Expect(type = ActionUpdatedEvent.class, count = 4 * 2),
@Expect(type = CancelTargetAssignmentEvent.class, count = 4 * 2),
@Expect(type = DistributionSetCreatedEvent.class, count = 3),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 9),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2)})
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2) })
public void mutipleDeployments() throws InterruptedException {
final String undeployedTargetPrefix = "undep-T";
final int noOfUndeployedTargets = 5;
@@ -982,10 +1103,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget("knownControllerId");
final DistributionSet ds = testdataFactory.createDistributionSet("a");
// assign ds to create an action
final DistributionSetAssignmentResult assignDistributionSet = deploymentManagement.assignDistributionSet(
ds.getId(), ActionType.SOFT,
org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME,
Collections.singletonList(target.getControllerId()));
final DistributionSetAssignmentResult assignDistributionSet = assignDistributionSet(ds.getId(),
target.getControllerId(), ActionType.SOFT);
final Long actionId = getFirstAssignedActionId(assignDistributionSet);
// verify preparation
Action findAction = deploymentManagement.findAction(actionId).get();
@@ -1006,10 +1125,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget("knownControllerId");
final DistributionSet ds = testdataFactory.createDistributionSet("a");
// assign ds to create an action
final DistributionSetAssignmentResult assignDistributionSet = deploymentManagement.assignDistributionSet(
ds.getId(), ActionType.FORCED,
org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME,
Collections.singletonList(target.getControllerId()));
final DistributionSetAssignmentResult assignDistributionSet = assignDistributionSet(ds.getId(),
target.getControllerId(), ActionType.FORCED);
final Long actionId = getFirstAssignedActionId(assignDistributionSet);
// verify perparation
Action findAction = deploymentManagement.findAction(actionId).get();
@@ -1023,23 +1140,22 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
findAction = deploymentManagement.findAction(actionId).get();
assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED);
}
@Test
@Description("Tests the computation of already assigned entities returned as a result of an assignment")
public void testAlreadyAssignedAndAssignedActionsInAssignmentResult(){
// create target1, distributionSet, assign ds to target1 and finish update (close all actions)
final Action action = prepareFinishedUpdate("target1", "ds", false);
final Target target2 = testdataFactory.createTarget("target2");
final Target target2 = testdataFactory.createTarget("target2");
final Target target3 = testdataFactory.createTarget("target3");
// assign ds to target2, but don't finish update (actions should be still open)
assignDistributionSet(action.getDistributionSet().getId(), target2.getControllerId());
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(
action.getDistributionSet().getId(),
Arrays.asList(new TargetWithActionType(action.getTarget().getControllerId()),
new TargetWithActionType(target3.getControllerId())));
Arrays.asList(action.getTarget().getControllerId(), target3.getControllerId()), ActionType.FORCED);
assertThat(assignmentResult).isNotNull();
assertThat(assignmentResult.getTotal()).as("Total count of assigned and already assigned targets").isEqualTo(2);
assertThat(assignmentResult.getAssigned()).as("Total count of assigned targets").isEqualTo(1);
@@ -1170,8 +1286,4 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
}
private void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
}

View File

@@ -13,7 +13,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -102,8 +101,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final String knownControllerId = "controller12345";
final DistributionSet knownDistributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
knownDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(knownDistributionSet.getId(),
knownControllerId);
final Long manuallyAssignedActionId = getFirstAssignedActionId(assignmentResult);
// create rollout with the same distribution set already assigned
@@ -142,8 +141,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet firstDistributionSet = testdataFactory.createDistributionSet();
final DistributionSet secondDistributionSet = testdataFactory.createDistributionSet("second");
testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
firstDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(firstDistributionSet.getId(),
knownControllerId);
final Long manuallyAssignedActionId = getFirstAssignedActionId(assignmentResult);
// create rollout with the same distribution set already assigned

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.autoassign;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -57,8 +56,8 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final DistributionSet firstDistributionSet = testdataFactory.createDistributionSet();
final DistributionSet secondDistributionSet = testdataFactory.createDistributionSet("second");
testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
firstDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(firstDistributionSet.getId(),
knownControllerId);
final Long manuallyAssignedActionId = getFirstAssignedActionId(assignmentResult);
// target filter query that matches all targets
@@ -218,11 +217,11 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final String targetDsBIdPref = "B";
final String targetDsCIdPref = "C";
List<Target> targetsA = createTargetsAndAutoAssignDistSet(targetDsAIdPref, 5, distributionSet,
final List<Target> targetsA = createTargetsAndAutoAssignDistSet(targetDsAIdPref, 5, distributionSet,
ActionType.FORCED);
List<Target> targetsB = createTargetsAndAutoAssignDistSet(targetDsBIdPref, 10, distributionSet,
final List<Target> targetsB = createTargetsAndAutoAssignDistSet(targetDsBIdPref, 10, distributionSet,
ActionType.SOFT);
List<Target> targetsC = createTargetsAndAutoAssignDistSet(targetDsCIdPref, 10, distributionSet,
final List<Target> targetsC = createTargetsAndAutoAssignDistSet(targetDsCIdPref, 10, distributionSet,
ActionType.DOWNLOAD_ONLY);
final int targetsCount = targetsA.size() + targetsB.size() + targetsC.size();

View File

@@ -14,12 +14,10 @@ import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationPrope
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ACTION_CLEANUP_ENABLED;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
@@ -54,10 +52,8 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg1.getControllerId()));
deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg2.getControllerId()));
assignDistributionSet(ds1.getId(), trg1.getControllerId());
assignDistributionSet(ds2.getId(), trg2.getControllerId());
assertThat(actionRepository.count()).isEqualTo(2);
@@ -80,10 +76,8 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds1.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg1.getControllerId())));
deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg2.getControllerId()));
final Long action1 = getFirstAssignedActionId(assignDistributionSet(ds1.getId(), trg1.getControllerId()));
assignDistributionSet(ds2.getId(), trg2.getControllerId());
setActionToCanceled(action1);
@@ -109,12 +103,9 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds1.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg1.getControllerId())));
final Long action2 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds2.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg2.getControllerId())));
final Long action3 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds2.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg3.getControllerId())));
final Long action1 = getFirstAssignedActionId(assignDistributionSet(ds1.getId(), trg1.getControllerId()));
final Long action2 = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), trg2.getControllerId()));
final Long action3 = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), trg3.getControllerId()));
assertThat(actionRepository.count()).isEqualTo(3);
@@ -144,12 +135,9 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds1.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg1.getControllerId())));
final Long action2 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds2.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg2.getControllerId())));
final Long action3 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds2.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg3.getControllerId())));
final Long action1 = getFirstAssignedActionId(assignDistributionSet(ds1.getId(), trg1.getControllerId()));
final Long action2 = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), trg2.getControllerId()));
final Long action3 = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), trg3.getControllerId()));
assertThat(actionRepository.count()).isEqualTo(3);
@@ -181,12 +169,9 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds1.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg1.getControllerId())));
final Long action2 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds2.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg2.getControllerId())));
final Long action3 = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(ds2.getId(),
ActionType.FORCED, 0, Collections.singletonList(trg3.getControllerId())));
final Long action1 = getFirstAssignedActionId(assignDistributionSet(ds1.getId(), trg1.getControllerId()));
final Long action2 = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), trg2.getControllerId()));
final Long action3 = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), trg3.getControllerId()));
assertThat(actionRepository.count()).isEqualTo(3);

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.repository.test.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
@@ -15,7 +16,9 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -46,6 +49,7 @@ import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
@@ -55,11 +59,11 @@ import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -231,8 +235,42 @@ public abstract class AbstractIntegrationTest {
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final String controllerId,
final ActionType actionType) {
return deploymentManagement.assignDistributionSet(dsID, Collections.singletonList(
new TargetWithActionType(controllerId, actionType, RepositoryModelConstants.NO_FORCE_TIME)));
return assignDistributionSet(dsID, Collections.singletonList(controllerId), actionType);
}
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final String controllerId,
final ActionType actionType, final long forcedTime) {
return assignDistributionSet(dsID, Collections.singletonList(controllerId), actionType, forcedTime);
}
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final List<String> controllerIds,
final ActionType actionType) {
return assignDistributionSet(dsID, controllerIds, actionType, RepositoryModelConstants.NO_FORCE_TIME);
}
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final List<String> controllerIds,
final ActionType actionType, final long forcedTime) {
final List<DeploymentRequest> deploymentRequests = controllerIds.stream()
.map(id -> DeploymentManagement.deploymentRequest(id, dsID).setActionType(actionType)
.setForceTime(forcedTime).build())
.collect(Collectors.toList());
final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(deploymentRequests);
assertThat(results).hasSize(1);
return results.get(0);
}
protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet ds,
final List<Target> targets) {
final List<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toList());
return assignDistributionSet(ds.getId(), targetIds, ActionType.FORCED);
}
private DistributionSetAssignmentResult makeAssignment(final DeploymentRequest request) {
final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(Collections.singletonList(request));
assertThat(results).hasSize(1);
return results.get(0);
}
/**
@@ -264,34 +302,37 @@ public abstract class AbstractIntegrationTest {
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindow(final long dsID,
final String controllerId, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
return deploymentManagement.assignDistributionSet(dsID,
Arrays.asList(new TargetWithActionType(controllerId, ActionType.FORCED,
RepositoryModelConstants.NO_FORCE_TIME, maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone)));
return makeAssignment(DeploymentManagement.deploymentRequest(controllerId, dsID)
.setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)
.build());
}
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindowTimeForced(final long dsID,
final String controllerId, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindow(final long dsID,
final String controllerId, final ActionType type, final String maintenanceWindowSchedule,
final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
return deploymentManagement.assignDistributionSet(dsID,
Arrays.asList(new TargetWithActionType(controllerId, ActionType.TIMEFORCED, System.currentTimeMillis(),
maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)));
return makeAssignment(DeploymentManagement.deploymentRequest(controllerId, dsID).setActionType(type)
.setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)
.build());
}
protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset,
final List<Target> targets) {
return deploymentManagement.assignDistributionSet(pset.getId(),
targets.stream().map(Target::getTargetWithActionType).collect(Collectors.toList()));
protected List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
final Collection<Target> targets) {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
distributionSets.forEach(ds -> targets.forEach(
target -> deploymentRequests
.add(DeploymentManagement.deploymentRequest(target.getControllerId(), ds.getId()).build()))
);
return deploymentRequests;
}
protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset, final Target target) {
return assignDistributionSet(pset, Arrays.asList(target));
}
protected DistributionSetAssignmentResult assignDistributionSetTimeForced(final DistributionSet pset,
final Target target) {
return deploymentManagement.assignDistributionSet(pset.getId(), Arrays.asList(
new TargetWithActionType(target.getControllerId(), ActionType.TIMEFORCED, System.currentTimeMillis())));
protected void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
protected DistributionSetMetadata createDistributionSetMetadata(final long dsId, final MetaData md) {