Transaction handling refactoring (#771)
* unified new transaction handling in jpa repositories, extended Deployment Management and Action Repository Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * moved Deployment Helper to utilities package Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * removed superfluous utility method from Deployment Helper Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * refactored distribution set to target assignment, fixed PR review findings Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * fixed PR review findings Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * added additional validation of active flag and current status fields for action status update repository method Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> * fixed timing issue in amqp message handler integration test, when validating target attributes update Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>
This commit is contained in:
committed by
Dominic Schabel
parent
b06928d089
commit
a2c1e5f132
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -411,6 +412,17 @@ public interface DeploymentManagement {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
||||
Action forceTargetAction(long actionId);
|
||||
|
||||
/**
|
||||
* Sets the status of inactive scheduled {@link Action}s for the specified
|
||||
* {@link Target}s to {@link Status#CANCELED}
|
||||
*
|
||||
* @param targetIds
|
||||
* ids of the {@link Target}s the actions belong to
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
||||
void cancelInactiveScheduledActionsForTargets(List<Long> targetIds);
|
||||
|
||||
/**
|
||||
* Starts all scheduled actions of an RolloutGroup parent.
|
||||
*
|
||||
|
||||
@@ -27,10 +27,6 @@ import org.springframework.integration.support.locks.LockRegistry;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.AsyncResult;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
@@ -65,8 +61,8 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
|
||||
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
|
||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware,
|
||||
final LockRegistry lockRegistry, final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
||||
final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
this.targetManagement = targetManagement;
|
||||
this.deploymentManagement = deploymentManagement;
|
||||
this.rolloutGroupManagement = rolloutGroupManagement;
|
||||
@@ -80,14 +76,6 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
|
||||
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
|
||||
}
|
||||
|
||||
protected Long runInNewTransaction(final String transactionName, final TransactionCallback<Long> action) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName(transactionName);
|
||||
def.setReadOnly(false);
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return new TransactionTemplate(txManager, def).execute(action);
|
||||
}
|
||||
|
||||
protected RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
|
||||
final long totalTargets) {
|
||||
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
|
||||
|
||||
@@ -207,10 +207,50 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
|
||||
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieves all IDs for {@link Action}s referring to the given target IDs,
|
||||
* active flag, current status and distribution set not requiring migration
|
||||
* step.
|
||||
*
|
||||
* @param targetIds
|
||||
* the IDs of targets for the actions
|
||||
* @param active
|
||||
* flag to indicate active/inactive actions
|
||||
* @param currentStatus
|
||||
* the current status of the actions
|
||||
* @return the found list of {@link Action} IDs
|
||||
*/
|
||||
@Query("SELECT a.id FROM JpaAction a WHERE a.target IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false")
|
||||
List<Long> findByTargetIdInAndIsActiveAndActionStatusAndDistributionSetNotRequiredMigrationStep(
|
||||
@Param("targetsIds") List<Long> targetIds, @Param("active") boolean active,
|
||||
@Param("currentStatus") Action.Status currentStatus);
|
||||
|
||||
/**
|
||||
* Switches the status of actions from one specific status into another for
|
||||
* given actions IDs, active flag and current status
|
||||
*
|
||||
* @param statusToSet
|
||||
* the new status the actions should get
|
||||
* @param actionIds
|
||||
* the IDs of the actions which are affected
|
||||
* @param active
|
||||
* flag to indicate active/inactive actions
|
||||
* @param currentStatus
|
||||
* the current status of the actions
|
||||
* @return the amount of updated actions
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.id IN :actionIds AND a.active = :active AND a.status = :currentStatus")
|
||||
int switchStatusForActionIdInAndIsActiveAndActionStatus(@Param("statusToSet") Action.Status statusToSet,
|
||||
@Param("actionIds") List<Long> actionIds, @Param("active") boolean active,
|
||||
@Param("currentStatus") Action.Status currentStatus);
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieves all {@link Action}s which are active and referring to the given
|
||||
* target Ids and distribution set required migration step.
|
||||
* target Ids and distribution set not requiring migration step.
|
||||
*
|
||||
* @param targetIds
|
||||
* the IDs of targets for the actions
|
||||
@@ -226,7 +266,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
/**
|
||||
*
|
||||
* Retrieves all {@link Action}s which are active and referring to the given
|
||||
* target Ids and distribution set required migration step.
|
||||
* target Ids and distribution set not requiring migration step.
|
||||
*
|
||||
* @param targetIds
|
||||
* the IDs of targets for the actions
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
@@ -88,12 +89,9 @@ import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
@@ -178,14 +176,6 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
this.repositoryProperties = repositoryProperties;
|
||||
}
|
||||
|
||||
private <T> T runInNewTransaction(final String transactionName, final TransactionCallback<T> action) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName(transactionName);
|
||||
def.setReadOnly(false);
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return new TransactionTemplate(txManager, def).execute(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPollingTime() {
|
||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
@@ -423,7 +413,8 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
try {
|
||||
events.stream().collect(Collectors.groupingBy(TargetPoll::getTenant)).forEach((tenant, polls) -> {
|
||||
final TransactionCallback<Void> createTransaction = status -> updateLastTargetQueries(tenant, polls);
|
||||
tenantAware.runAsTenant(tenant, () -> runInNewTransaction("flushUpdateQueue", createTransaction));
|
||||
tenantAware.runAsTenant(tenant,
|
||||
() -> DeploymentHelper.runInNewTransaction(txManager, "flushUpdateQueue", createTransaction));
|
||||
});
|
||||
} catch (final RuntimeException ex) {
|
||||
LOG.error("Failed to persist UpdateQueue content.", ex);
|
||||
@@ -605,8 +596,8 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
|
||||
switch (actionStatus.getStatus()) {
|
||||
case ERROR:
|
||||
final JpaTarget target = DeploymentHelper.updateTargetInfo((JpaTarget) action.getTarget(),
|
||||
TargetUpdateStatus.ERROR, false);
|
||||
final JpaTarget target = (JpaTarget) action.getTarget();
|
||||
target.setUpdateStatus(TargetUpdateStatus.ERROR);
|
||||
handleErrorOnAction(action, target);
|
||||
break;
|
||||
case FINISHED:
|
||||
@@ -626,7 +617,7 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
if (controllerId != null) {
|
||||
targetManagement.requestControllerAttributes(controllerId);
|
||||
}
|
||||
|
||||
|
||||
return savedAction;
|
||||
}
|
||||
|
||||
@@ -673,10 +664,10 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
final UpdateMode mode) {
|
||||
|
||||
/*
|
||||
Constraints on attribute keys & values are not validated by EclipseLink. Hence, they are validated here.
|
||||
* Constraints on attribute keys & values are not validated by
|
||||
* EclipseLink. Hence, they are validated here.
|
||||
*/
|
||||
if (data.entrySet().stream()
|
||||
.anyMatch(e -> !isAttributeEntryValid(e))) {
|
||||
if (data.entrySet().stream().anyMatch(e -> !isAttributeEntryValid(e))) {
|
||||
throw new InvalidTargetAttributeException();
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
@@ -82,11 +83,8 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@@ -142,7 +140,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
private final TenantAware tenantAware;
|
||||
private final Database database;
|
||||
|
||||
JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
|
||||
protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
|
||||
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
|
||||
final ActionStatusRepository actionStatusRepository, final TargetManagement targetManagement,
|
||||
final AuditorAware<String> auditorProvider, final ApplicationEventPublisher eventPublisher,
|
||||
@@ -253,102 +251,82 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy) {
|
||||
|
||||
final JpaDistributionSet set = distributionSetRepository.findOne(dsID);
|
||||
if (set == null) {
|
||||
throw new EntityNotFoundException(DistributionSet.class, dsID);
|
||||
}
|
||||
final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID);
|
||||
final List<String> controllerIDs = getControllerIdsForAssignmentAndCheckQuota(targetsWithActionType,
|
||||
distributionSetEntity);
|
||||
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(controllerIDs,
|
||||
distributionSetEntity.getId());
|
||||
|
||||
if (!set.isComplete()) {
|
||||
throw new IncompleteDistributionSetException(
|
||||
"Distribution set of type " + set.getType().getKey() + " is incomplete: " + set.getId());
|
||||
}
|
||||
|
||||
final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// enforce the 'max targets per manual assignment' quota
|
||||
if (!controllerIDs.isEmpty()) {
|
||||
assertMaxTargetsPerManualAssignmentQuota(set.getId(), controllerIDs.size());
|
||||
}
|
||||
|
||||
LOG.debug("assignDistribution({}) to {} targets", set, controllerIDs.size());
|
||||
|
||||
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
|
||||
.collect(Collectors.toMap(TargetWithActionType::getControllerId, Function.identity()));
|
||||
|
||||
// split tIDs length into max entries in-statement because many database
|
||||
// have constraint of max entries in in-statements e.g. Oracle with
|
||||
// maximum 1000 elements, so we need to split the entries here and
|
||||
// execute multiple statements we take the target only into account if
|
||||
// the requested operation is no duplicate of a previous one
|
||||
final List<JpaTarget> targets = assignmentStrategy.findTargetsForAssignment(controllerIDs, set.getId());
|
||||
|
||||
if (targets.isEmpty()) {
|
||||
if (targetEntities.isEmpty()) {
|
||||
// detaching as it is not necessary to persist the set itself
|
||||
entityManager.detach(set);
|
||||
entityManager.detach(distributionSetEntity);
|
||||
// return with nothing as all targets had the DS already assigned
|
||||
return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(),
|
||||
Collections.emptyList(), targetManagement);
|
||||
}
|
||||
|
||||
final List<List<Long>> targetIds = Lists.partition(
|
||||
targets.stream().map(Target::getId).collect(Collectors.toList()), Constants.MAX_ENTRIES_IN_STATEMENT);
|
||||
// split tIDs length into max entries in-statement because many database
|
||||
// have constraint of max entries in in-statements e.g. Oracle with
|
||||
// maximum 1000 elements, so we need to split the entries here and
|
||||
// execute multiple statements
|
||||
final List<List<Long>> targetEntitiesIdsChunks = Lists.partition(
|
||||
targetEntities.stream().map(Target::getId).collect(Collectors.toList()),
|
||||
Constants.MAX_ENTRIES_IN_STATEMENT);
|
||||
|
||||
// override all active actions and set them into canceling state, we
|
||||
// need to remember which one we have been switched to canceling state
|
||||
// because for targets which we have changed to canceling we don't want
|
||||
// to publish the new action update event.
|
||||
final Set<Long> targetIdsCancellList;
|
||||
|
||||
if (systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class)
|
||||
.getValue())) {
|
||||
targetIdsCancellList = Collections.emptySet();
|
||||
assignmentStrategy.closeActiveActions(targetIds);
|
||||
} else {
|
||||
targetIdsCancellList = assignmentStrategy.cancelActiveActions(targetIds);
|
||||
}
|
||||
|
||||
final Set<Long> cancelingTargetEntitiesIds = 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 the
|
||||
targetIds.forEach(tIds -> actionRepository.switchStatus(Status.CANCELED, tIds, false, Status.SCHEDULED));
|
||||
// cancels them
|
||||
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
|
||||
|
||||
// set assigned distribution set and TargetUpdateStatus
|
||||
final String currentUser;
|
||||
if (auditorProvider != null) {
|
||||
currentUser = auditorProvider.getCurrentAuditor();
|
||||
} else {
|
||||
currentUser = null;
|
||||
}
|
||||
|
||||
assignmentStrategy.updateTargetStatus(set, targetIds, currentUser);
|
||||
|
||||
final Map<String, JpaAction> targetIdsToActions = targets.stream()
|
||||
.map(trg -> createTargetAction(assignmentStrategy, targetsWithActionType, controllerIDs, targets,
|
||||
targetsWithActionMap, trg, set))
|
||||
.map(actionRepository::save)
|
||||
.collect(Collectors.toMap(action -> action.getTarget().getControllerId(), Function.identity()));
|
||||
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity,
|
||||
targetEntitiesIdsChunks);
|
||||
|
||||
final Map<String, JpaAction> controllerIdsToActions = createActions(targetsWithActionType, targetEntities,
|
||||
assignmentStrategy, controllerIDs, distributionSetEntity);
|
||||
// create initial action status when action is created so we remember
|
||||
// the initial running status because we will change the status
|
||||
// of the action itself and with this action status we have a nicer
|
||||
// action history.
|
||||
actionStatusRepository.save(targetIdsToActions.values().stream()
|
||||
.map(action -> assignmentStrategy.createActionStatus(action, actionMessage))
|
||||
.collect(Collectors.toList()));
|
||||
createActionsStatus(controllerIdsToActions.values(), assignmentStrategy, actionMessage);
|
||||
|
||||
// detaching as it is not necessary to persist the set itself
|
||||
entityManager.detach(set);
|
||||
// detaching as the entity has been updated by the JPQL query above
|
||||
targets.forEach(entityManager::detach);
|
||||
|
||||
assignmentStrategy.sendAssignmentEvents(set, targets, targetIdsCancellList, targetIdsToActions);
|
||||
detachEntitiesAndSendAssignmentEvents(distributionSetEntity, targetEntities, assignmentStrategy,
|
||||
cancelingTargetEntitiesIds, controllerIdsToActions);
|
||||
|
||||
return new DistributionSetAssignmentResult(
|
||||
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), targets.size(),
|
||||
controllerIDs.size() - targets.size(), Lists.newArrayList(targetIdsToActions.values()),
|
||||
targetManagement);
|
||||
targetEntities.stream().map(Target::getControllerId).collect(Collectors.toList()),
|
||||
targetEntities.size(), controllerIDs.size() - targetEntities.size(),
|
||||
Lists.newArrayList(controllerIdsToActions.values()), targetManagement);
|
||||
}
|
||||
|
||||
private JpaDistributionSet getAndValidateDsById(final Long dsID) {
|
||||
final JpaDistributionSet distributionSet = distributionSetRepository.findById(dsID)
|
||||
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, dsID));
|
||||
|
||||
if (!distributionSet.isComplete()) {
|
||||
throw new IncompleteDistributionSetException("Distribution set of type "
|
||||
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
|
||||
}
|
||||
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
private List<String> getControllerIdsForAssignmentAndCheckQuota(
|
||||
final Collection<TargetWithActionType> targetsWithActionType, final JpaDistributionSet distributionSet) {
|
||||
final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// enforce the 'max targets per manual assignment' quota
|
||||
if (!controllerIDs.isEmpty()) {
|
||||
assertMaxTargetsPerManualAssignmentQuota(distributionSet.getId(), controllerIDs.size());
|
||||
}
|
||||
|
||||
return controllerIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,9 +338,71 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
* @param requested
|
||||
* number of targets to check
|
||||
*/
|
||||
private void assertMaxTargetsPerManualAssignmentQuota(final Long id, final int requested) {
|
||||
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxTargetsPerManualAssignment(),
|
||||
Target.class, DistributionSet.class, null);
|
||||
private void assertMaxTargetsPerManualAssignmentQuota(final Long distributionSetId,
|
||||
final int requestedTargetsCount) {
|
||||
QuotaHelper.assertAssignmentQuota(distributionSetId, requestedTargetsCount,
|
||||
quotaManagement.getMaxTargetsPerManualAssignment(), Target.class, DistributionSet.class, null);
|
||||
}
|
||||
|
||||
private Set<Long> closeOrCancelActiveActions(final AbstractDsAssignmentStrategy assignmentStrategy,
|
||||
final List<List<Long>> targetIdsChunks) {
|
||||
if (isActionsAutocloseEnabled()) {
|
||||
assignmentStrategy.closeActiveActions(targetIdsChunks);
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return assignmentStrategy.cancelActiveActions(targetIdsChunks);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isActionsAutocloseEnabled() {
|
||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class)
|
||||
.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
|
||||
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
|
||||
}
|
||||
|
||||
private void setAssignedDistributionSetAndTargetUpdateStatus(final AbstractDsAssignmentStrategy assignmentStrategy,
|
||||
final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) {
|
||||
final String currentUser = auditorProvider != null ? auditorProvider.getCurrentAuditor() : null;
|
||||
assignmentStrategy.updateTargetStatus(set, targetIdsChunks, currentUser);
|
||||
}
|
||||
|
||||
private Map<String, JpaAction> createActions(final Collection<TargetWithActionType> targetsWithActionType,
|
||||
final List<JpaTarget> targets, final AbstractDsAssignmentStrategy assignmentStrategy,
|
||||
final List<String> controllerIDs, final JpaDistributionSet set) {
|
||||
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
|
||||
.collect(Collectors.toMap(TargetWithActionType::getControllerId, Function.identity()));
|
||||
|
||||
return targets.stream()
|
||||
.map(trg -> createTargetAction(assignmentStrategy, targetsWithActionType, controllerIDs, targets,
|
||||
targetsWithActionMap, trg, set))
|
||||
.map(actionRepository::save)
|
||||
.collect(Collectors.toMap(action -> action.getTarget().getControllerId(), Function.identity()));
|
||||
}
|
||||
|
||||
private void createActionsStatus(final Collection<JpaAction> actions,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy, final String actionMessage) {
|
||||
actionStatusRepository
|
||||
.save(actions.stream().map(action -> assignmentStrategy.createActionStatus(action, actionMessage))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void detachEntitiesAndSendAssignmentEvents(final JpaDistributionSet set, final List<JpaTarget> targets,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy, final Set<Long> targetIdsCancellList,
|
||||
final Map<String, JpaAction> controllerIdsToActions) {
|
||||
// detaching as it is not necessary to persist the set itself
|
||||
entityManager.detach(set);
|
||||
// detaching as the entity has been updated by the JPQL query above
|
||||
targets.forEach(entityManager::detach);
|
||||
|
||||
assignmentStrategy.sendAssignmentEvents(set, targets, targetIdsCancellList, controllerIdsToActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -439,11 +479,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
|
||||
private long startScheduledActionsByRolloutGroupParentInNewTransaction(final Long rolloutId,
|
||||
final Long distributionSetId, final Long rolloutGroupParentId, final int limit) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName("startScheduledActions-" + rolloutId);
|
||||
def.setReadOnly(false);
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return new TransactionTemplate(txManager, def).execute(status -> {
|
||||
return DeploymentHelper.runInNewTransaction(txManager, "startScheduledActions-" + rolloutId, status -> {
|
||||
final Page<Action> rolloutGroupActions = findActionsByRolloutAndRolloutGroupParent(rolloutId,
|
||||
rolloutGroupParentId, limit);
|
||||
|
||||
@@ -797,4 +833,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
}
|
||||
}
|
||||
|
||||
protected ActionRepository getActionRepository() {
|
||||
return actionRepository;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditio
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
@@ -158,7 +159,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
||||
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
|
||||
eventPublisher, virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy);
|
||||
this.database = database;
|
||||
@@ -361,7 +362,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
if (!rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
||||
rollout.setStatus(RolloutStatus.READY);
|
||||
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
||||
} else {
|
||||
} else {
|
||||
LOGGER.debug("rollout {} creation done. Switch to WAITING_FOR_APPROVAL.", rollout.getId());
|
||||
rollout.setStatus(RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
rolloutApprovalStrategy.onApprovalRequired(rollout);
|
||||
@@ -388,10 +389,12 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||
RolloutGroupStatus.READY, group);
|
||||
|
||||
final long targetsInGroupFilter = runInNewTransaction("countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
|
||||
final long targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
|
||||
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
|
||||
count -> targetManagement.countByRsqlAndNotInRolloutGroups(readyGroups, groupTargetFilter));
|
||||
final long expectedInGroup = Math.round(group.getTargetPercentage() / 100 * (double) targetsInGroupFilter);
|
||||
final long currentlyInGroup = runInNewTransaction("countRolloutTargetGroupByRolloutGroup",
|
||||
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
|
||||
"countRolloutTargetGroupByRolloutGroup",
|
||||
count -> rolloutTargetGroupRepository.countByRolloutGroup(group));
|
||||
|
||||
// Switch the Group status to READY, when there are enough Targets in
|
||||
@@ -413,8 +416,9 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
} while (targetsLeftToAdd > 0);
|
||||
|
||||
group.setStatus(RolloutGroupStatus.READY);
|
||||
group.setTotalTargets(runInNewTransaction("countRolloutTargetGroupByRolloutGroup",
|
||||
count -> rolloutTargetGroupRepository.countByRolloutGroup(group)).intValue());
|
||||
group.setTotalTargets(
|
||||
DeploymentHelper.runInNewTransaction(txManager, "countRolloutTargetGroupByRolloutGroup",
|
||||
count -> rolloutTargetGroupRepository.countByRolloutGroup(group)).intValue());
|
||||
return rolloutGroupRepository.save(group);
|
||||
|
||||
} catch (final TransactionException e) {
|
||||
@@ -426,7 +430,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
private Long assignTargetsToGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
|
||||
final String targetFilter, final long limit) {
|
||||
|
||||
return runInNewTransaction("assignTargetsToRolloutGroup", status -> {
|
||||
return DeploymentHelper.runInNewTransaction(txManager, "assignTargetsToRolloutGroup", status -> {
|
||||
final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit));
|
||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||
RolloutGroupStatus.READY, group);
|
||||
@@ -475,14 +479,14 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
|
||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
switch (decision) {
|
||||
case APPROVED:
|
||||
rollout.setStatus(RolloutStatus.READY);
|
||||
break;
|
||||
case DENIED:
|
||||
rollout.setStatus(RolloutStatus.APPROVAL_DENIED);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown approval decision: " + decision);
|
||||
case APPROVED:
|
||||
rollout.setStatus(RolloutStatus.READY);
|
||||
break;
|
||||
case DENIED:
|
||||
rollout.setStatus(RolloutStatus.APPROVAL_DENIED);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown approval decision: " + decision);
|
||||
}
|
||||
rollout.setApprovalDecidedBy(rolloutApprovalStrategy.getApprovalUser(rollout));
|
||||
if (remark != null) {
|
||||
@@ -577,7 +581,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
}
|
||||
|
||||
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) {
|
||||
return runInNewTransaction("createActionsForTargets", status -> {
|
||||
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
|
||||
final PageRequest pageRequest = new PageRequest(0, limit);
|
||||
final Rollout rollout = rolloutRepository.findOne(rolloutId);
|
||||
final RolloutGroup group = rolloutGroupRepository.findOne(groupId);
|
||||
@@ -608,7 +612,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
// current scheduled action to cancel. E.g. a new scheduled action is
|
||||
// created.
|
||||
final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList());
|
||||
actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED);
|
||||
deploymentManagement.cancelInactiveScheduledActionsForTargets(targetIds);
|
||||
targets.forEach(target -> {
|
||||
|
||||
assertActionsPerTargetQuota(target, 1);
|
||||
@@ -821,7 +825,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
}
|
||||
|
||||
try {
|
||||
rollouts.forEach(rolloutId -> runInNewTransaction(handlerId + "-" + rolloutId,
|
||||
rollouts.forEach(rolloutId -> DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId,
|
||||
status -> executeFittingHandler(rolloutId)));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactio
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.model.TenantMetaData;
|
||||
@@ -46,11 +47,8 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
@@ -207,12 +205,8 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
* @return the initial created {@link TenantMetaData}
|
||||
*/
|
||||
private TenantMetaData createInitialTenantMetaData(final String tenant) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName("initial-tenant-creation");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
def.setReadOnly(false);
|
||||
return systemSecurityContext
|
||||
.runAsSystemAsTenant(() -> new TransactionTemplate(txManager, def).execute(status -> {
|
||||
return systemSecurityContext.runAsSystemAsTenant(
|
||||
() -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> {
|
||||
final DistributionSetType defaultDsType = createStandardSoftwareDataSetup();
|
||||
return tenantMetaDataRepository.save(new JpaTenantMetaData(defaultDsType, tenant));
|
||||
}), tenant);
|
||||
|
||||
@@ -6,18 +6,25 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
package org.eclipse.hawkbit.repository.jpa.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
* Utility class for deployment related topics.
|
||||
@@ -29,31 +36,6 @@ public final class DeploymentHelper {
|
||||
// utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper method used only inside service level. As a result is no
|
||||
* additional security necessary.
|
||||
*
|
||||
* @param target
|
||||
* to update
|
||||
* @param status
|
||||
* of the target
|
||||
* @param setInstalledDate
|
||||
* to set
|
||||
* @param targetInfoRepository
|
||||
* for the operation
|
||||
*
|
||||
* @return updated target
|
||||
*/
|
||||
static JpaTarget updateTargetInfo(@NotNull final JpaTarget target, @NotNull final TargetUpdateStatus status,
|
||||
final boolean setInstalledDate) {
|
||||
target.setUpdateStatus(status);
|
||||
|
||||
if (setInstalledDate) {
|
||||
target.setInstallationDate(System.currentTimeMillis());
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called, when cancellation has been successful. It sets the
|
||||
* action to canceled, resets the meta data of the target and in case there
|
||||
@@ -65,10 +47,8 @@ public final class DeploymentHelper {
|
||||
* for the operation
|
||||
* @param targetRepository
|
||||
* for the operation
|
||||
* @param targetInfoRepository
|
||||
* for the operation
|
||||
*/
|
||||
static void successCancellation(final JpaAction action, final ActionRepository actionRepository,
|
||||
public static void successCancellation(final JpaAction action, final ActionRepository actionRepository,
|
||||
final TargetRepository targetRepository) {
|
||||
|
||||
// set action inactive
|
||||
@@ -81,11 +61,32 @@ public final class DeploymentHelper {
|
||||
|
||||
if (nextActiveActions.isEmpty()) {
|
||||
target.setAssignedDistributionSet(target.getInstalledDistributionSet());
|
||||
updateTargetInfo(target, TargetUpdateStatus.IN_SYNC, false);
|
||||
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||
} else {
|
||||
target.setAssignedDistributionSet(nextActiveActions.get(0).getDistributionSet());
|
||||
}
|
||||
|
||||
targetRepository.save(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the modifying action in new transaction
|
||||
*
|
||||
* @param txManager
|
||||
* transaction manager interface
|
||||
* @param transactionName
|
||||
* the name of the new transaction
|
||||
* @param action
|
||||
* the callback to execute in new tranaction
|
||||
*
|
||||
* @return the result of the action
|
||||
*/
|
||||
public static <T> T runInNewTransaction(@NotNull final PlatformTransactionManager txManager,
|
||||
final String transactionName, @NotNull final TransactionCallback<T> action) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName(transactionName);
|
||||
def.setReadOnly(false);
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return new TransactionTemplate(txManager, def).execute(action);
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,10 @@ import javax.validation.constraints.NotNull;
|
||||
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Helper class to check assignment quotas.
|
||||
*/
|
||||
@Validated
|
||||
public final class QuotaHelper {
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user