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:
committed by
Stefan Behl
parent
dba972423b
commit
8687510131
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user