[#1580] Software Module & Distribution Set lock: implicit (#1649)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-02-18 23:01:55 +02:00
committed by GitHub
parent 94576bd6fe
commit 9e76223a91
34 changed files with 630 additions and 293 deletions

View File

@@ -329,10 +329,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy,
final Long dsID, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback = retryContext -> assignDistributionSetToTargets(
initiatedBy, dsID, targetsWithActionType, actionMessage, assignmentStrategy);
final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback =
retryContext -> assignDistributionSetToTargets(
initiatedBy, dsId, targetsWithActionType, actionMessage, assignmentStrategy);
return retryTemplate.execute(retryCallback);
}
@@ -351,7 +352,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
*
* @param initiatedBy
* the username of the user who initiated the assignment
* @param dsID
* @param dsId
* the ID of the distribution set to assign
* @param targetsWithActionType
* a list of all targets and their action type
@@ -365,12 +366,20 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsID,
private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsId,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSet =
(JpaDistributionSet) distributionSetManagement.getValidAndComplete(dsId);
// implicit lock
if (!distributionSet.isLocked()) {
// without new transaction DS changed event is not thrown
DeploymentHelper.runInNewTransaction(txManager, "Implicit lock", status -> {
distributionSetManagement.lock(distributionSet.getId());
return null;
});
}
final JpaDistributionSet distributionSetEntity = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(dsID);
final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.distinct().toList();
@@ -381,19 +390,19 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
.flatMap(List::stream).map(JpaTarget::getControllerId).toList();
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds,
distributionSetEntity.getId());
distributionSet.getId());
if (targetEntities.isEmpty()) {
return allTargetsAlreadyAssignedResult(distributionSetEntity, existingTargetIds.size());
return allTargetsAlreadyAssignedResult(distributionSet, existingTargetIds.size());
}
final List<TargetWithActionType> existingTargetsWithActionType = targetsWithActionType.stream()
.filter(target -> existingTargetIds.contains(target.getControllerId())).toList();
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(initiatedBy,
existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSetEntity,
existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSet,
targetEntities);
return buildAssignmentResult(distributionSetEntity, assignedActions, existingTargetsWithActionType.size());
return buildAssignmentResult(distributionSet, assignedActions, existingTargetsWithActionType.size());
}
private DistributionSetAssignmentResult allTargetsAlreadyAssignedResult(

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.StopRolloutException;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
@@ -94,7 +95,6 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
// no lock is needed as no rollout will be stopped
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
}
}
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
@@ -108,21 +108,25 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
private void invalidateDistributionSet(final long setId, final CancelationType cancelationType,
final boolean cancelRollouts) {
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
distributionSetManagement.invalidate(set);
final DistributionSet distributionSet = distributionSetManagement.getOrElseThrowException(setId);
if (!distributionSet.isComplete()) {
throw new IncompleteDistributionSetException("Distribution set of type "
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
}
distributionSetManagement.invalidate(distributionSet);
log.debug("Distribution set {} marked as invalid.", setId);
// rollout cancellation should only be permitted with UPDATE_ROLLOUT permission
if (shouldRolloutsBeCanceled(cancelationType, cancelRollouts)) {
log.debug("Cancel rollouts after ds invalidation. ID: {}", setId);
rolloutManagement.cancelRolloutsForDistributionSet(set);
rolloutManagement.cancelRolloutsForDistributionSet(distributionSet);
}
// Do run as system to ensure all actions (even invisible) are canceled due to invalidation.
systemSecurityContext.runAsSystem(() -> {
if (cancelationType != CancelationType.NONE) {
log.debug("Cancel actions after ds invalidation. ID: {}", setId);
deploymentManagement.cancelActionsForDistributionSet(cancelationType, set);
deploymentManagement.cancelActionsForDistributionSet(cancelationType, distributionSet);
}
log.debug("Cancel auto assignments after ds invalidation. ID: {}", setId);

View File

@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.builder.DistributionSetCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.GenericDistributionSetUpdate;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.exception.DeletedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
@@ -697,6 +698,13 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
public void lock(final long id) {
final JpaDistributionSet distributionSet = getById(id);
if (!distributionSet.isLocked()) {
distributionSet.getModules().forEach(module -> {
if (!module.isLocked()) {
final JpaSoftwareModule jpaSoftwareModule = (JpaSoftwareModule)module;
jpaSoftwareModule.lock();
softwareModuleRepository.save(jpaSoftwareModule);
}
});
distributionSet.lock();
distributionSetRepository.save(distributionSet);
}
@@ -780,6 +788,10 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
}
if (distributionSet.isDeleted()) {
throw new DeletedException(DistributionSet.class, id);
}
return distributionSet;
}

View File

@@ -49,6 +49,7 @@ import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
@@ -205,22 +206,29 @@ public class JpaRolloutManagement implements RolloutManagement {
private JpaRollout createRollout(final JpaRollout rollout) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
long totalTargets;
String errMsg;
final JpaDistributionSet distributionSet = (JpaDistributionSet) rollout.getDistributionSet();
final long totalTargets;
final String errMsg;
if (RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
totalTargets = targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()),
rollout.getDistributionSet().getType().getId());
distributionSet.getType().getId());
errMsg = "No failed targets in Rollout";
} else {
totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(),
rollout.getDistributionSet().getType().getId());
distributionSet.getType().getId());
errMsg = "Rollout does not match any existing targets";
}
if (totalTargets == 0) {
throw new ValidationException(errMsg);
}
rollout.setTotalTargets(totalTargets);
// implicit lock
if (!distributionSet.isLocked()) {
distributionSetManagement.lock(distributionSet.getId());
}
if (rollout.getWeight().isEmpty()) {
rollout.setWeight(repositoryProperties.getActionWeightIfAbsent());
}
@@ -526,7 +534,6 @@ public class JpaRolloutManagement implements RolloutManagement {
rollout.setStartAt(update.getStartAt().orElse(null));
update.getSet().ifPresent(setId -> {
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
rollout.setDistributionSet(set);
});
if (rolloutApprovalStrategy.isApprovalNeeded(rollout)) {

View File

@@ -276,11 +276,14 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
} else {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
assertMaxTargetsQuota(targetFilterQuery.getQuery(), targetFilterQuery.getName(), update.getDsId());
final JpaDistributionSet ds = (JpaDistributionSet) distributionSetManagement
final JpaDistributionSet distributionSet = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(update.getDsId());
verifyDistributionSetAndThrowExceptionIfDeleted(ds);
// implicit lock
if (!distributionSet.isLocked()) {
distributionSetManagement.lock(distributionSet.getId());
}
targetFilterQuery.setAutoAssignDistributionSet(ds);
targetFilterQuery.setAutoAssignDistributionSet(distributionSet);
contextAware.getCurrentContext().ifPresent(targetFilterQuery::setAccessControlContext);
targetFilterQuery.setAutoAssignInitiatedBy(contextAware.getCurrentUsername());
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
@@ -299,12 +302,6 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
.isConfirmationFlowEnabled();
}
private static void verifyDistributionSetAndThrowExceptionIfDeleted(final DistributionSet distributionSet) {
if (distributionSet.isDeleted()) {
throw new EntityNotFoundException(DistributionSet.class, distributionSet.getId());
}
}
private static ActionType sanitizeAutoAssignActionType(final ActionType actionType) {
if (actionType == null) {
return ActionType.FORCED;