Refactor DS Management (#2591)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-08-05 15:45:41 +03:00
committed by GitHub
parent 049add05ac
commit 6aa8ccaa9c
39 changed files with 365 additions and 776 deletions

View File

@@ -588,7 +588,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
systemSecurityContext.runAsSystem(() ->
distributionSetManagement.findByNameAndVersion(distributionName, version).map(
distributionSet -> deploymentManagement.offlineAssignedDistributionSets(
List.of(Map.entry(controllerId, distributionSet.getId())), controllerId))
controllerId, List.of(Map.entry(controllerId, distributionSet.getId()))))
.orElseThrow(() ->
new EntityNotFoundException(DistributionSet.class, Map.entry(distributionName, version))));

View File

@@ -180,40 +180,41 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
public List<DistributionSetAssignmentResult> assignDistributionSets(
final String initiatedBy, final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
return assignDistributionSets0(initiatedBy, deploymentRequests, actionMessage);
}
private List<DistributionSetAssignmentResult> assignDistributionSets0(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
private List<DistributionSetAssignmentResult> assignDistributionSets0(
final String initiatedBy, final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(deploymentRequests);
return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
}
@Override
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(final Collection<Entry<String, Long>> assignments,
final String initiatedBy) {
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final String initiatedBy, final Collection<Entry<String, Long>> assignments) {
final Collection<Entry<String, Long>> distinctAssignments = assignments.stream().distinct().toList();
enforceMaxAssignmentsPerRequest(distinctAssignments.size());
final List<DeploymentRequest> deploymentRequests = distinctAssignments.stream()
.map(entry -> DeploymentManagement.deploymentRequest(entry.getKey(), entry.getValue()).build()).toList();
return assignDistributionSets(auditorAware.getCurrentAuditor().orElse(tenantAware.getCurrentUsername()), deploymentRequests, null,
return assignDistributionSets(
auditorAware.getCurrentAuditor().orElse(tenantAware.getCurrentUsername()), deploymentRequests, null,
offlineDsAssignmentStrategy);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(final Collection<Entry<String, Long>> assignments) {
return offlineAssignedDistributionSets(assignments, tenantAware.getCurrentUsername());
return offlineAssignedDistributionSets(tenantAware.getCurrentUsername(), assignments);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action cancelAction(final long actionId) {
return cancelAction0(actionId);
}
@@ -452,7 +453,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
startScheduledActions(groupScheduledActions.getContent());
return groupScheduledActions.getTotalElements();
}
}) > 0) ;
}) > 0);
}
@Override
@@ -483,7 +484,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
@Override
@Transactional(readOnly = false)
@Transactional
public int deleteActionsByStatusAndLastModifiedBefore(final Set<Status> status, final long lastModified) {
if (status.isEmpty()) {
return 0;
@@ -590,8 +591,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return template;
}
private List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage, final AbstractDsAssignmentStrategy strategy) {
private List<DistributionSetAssignmentResult> assignDistributionSets(
final String initiatedBy, final List<DeploymentRequest> deploymentRequests, final String actionMessage,
final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> validatedRequests = validateAndFilterRequestForAssignments(deploymentRequests);
final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests);
@@ -720,10 +722,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private DistributionSetAssignmentResult assignDistributionSetToTargets(
final String initiatedBy, final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
JpaDistributionSet distributionSet = distributionSetManagement.getValidAndComplete(dsId);
if (distributionSetManagement.isImplicitLockApplicable(distributionSet)) {
// without new transaction DS changed event is not thrown
distributionSet = DeploymentHelper.runInNewTransaction(txManager, "Implicit lock", status -> distributionSetManagement.lock(dsId));
final JpaDistributionSet dsValidAndComplete = distributionSetManagement.getValidAndComplete(dsId);
final JpaDistributionSet distributionSet;
if (distributionSetManagement.shouldLockImplicitly(dsValidAndComplete)) {
// implicitly lock, for some reason no update happen if lock in same transaction
distributionSet = DeploymentHelper.runInNewTransaction(
txManager, "lockDistributionSet-" + dsId,
status -> distributionSetManagement.lock(dsValidAndComplete));
} else {
distributionSet = dsValidAndComplete;
}
final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId).distinct().toList();
@@ -766,8 +773,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
// not active before and the manual assignment which has been done cancels them
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity, targetEntitiesIdsChunks);
final Map<TargetWithActionType, JpaAction> assignedActions = createActions(targetsWithActionType, targetEntities, distributionSetEntity,
assignmentStrategy, initiatedBy);
final Map<TargetWithActionType, JpaAction> assignedActions =
createActions(targetsWithActionType, targetEntities, distributionSetEntity, assignmentStrategy, initiatedBy);
// 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.
@@ -820,7 +827,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return persistedActions;
}
private void createActionsStatus(final Map<TargetWithActionType, JpaAction> actions, final AbstractDsAssignmentStrategy assignmentStrategy,
private void createActionsStatus(
final Map<TargetWithActionType, JpaAction> actions, final AbstractDsAssignmentStrategy assignmentStrategy,
final String actionMessage) {
actionStatusRepository.saveAll(actions.entrySet().stream().map(entry -> {
final JpaAction action = entry.getValue();

View File

@@ -132,10 +132,10 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
private void invalidateDistributionSet(final long setId, final CancelationType cancelationType, final boolean cancelRollouts) {
final DistributionSet distributionSet = distributionSetManagement.getOrElseThrowException(setId);
if (!distributionSet.isComplete()) {
throw new IncompleteDistributionSetException("Distribution set of type "
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
throw new IncompleteDistributionSetException(
"Distribution set of type " + distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
}
((DistributionSetManagement)distributionSetManagement).invalidate(distributionSet);
distributionSetManagement.invalidate(distributionSet);
log.debug("Distribution set {} marked as invalid.", setId);
// rollout cancellation should only be permitted with UPDATE_ROLLOUT permission

View File

@@ -13,7 +13,6 @@ import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_D
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -26,12 +25,10 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import jakarta.persistence.EntityManager;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
@@ -39,12 +36,10 @@ import org.eclipse.hawkbit.repository.exception.DeletedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
@@ -52,16 +47,11 @@ import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Statistic;
import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -70,13 +60,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProp
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@Service
@@ -86,10 +73,8 @@ public class JpaDistributionSetManagement
implements DistributionSetManagement<JpaDistributionSet> {
private final DistributionSetTagManagement<JpaDistributionSetTag> distributionSetTagManagement;
private final DistributionSetTypeManagement<JpaDistributionSetType> distributionSetTypeManagement;
private final SoftwareModuleRepository softwareModuleRepository;
private final DistributionSetTagRepository distributionSetTagRepository;
private final TargetRepository targetRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final ActionRepository actionRepository;
private final QuotaManagement quotaManagement;
@@ -101,10 +86,8 @@ public class JpaDistributionSetManagement
final DistributionSetRepository jpaRepository,
final EntityManager entityManager,
final DistributionSetTagManagement<JpaDistributionSetTag> distributionSetTagManagement,
final DistributionSetTypeManagement<JpaDistributionSetType> distributionSetTypeManagement,
final SoftwareModuleRepository softwareModuleRepository,
final DistributionSetTagRepository distributionSetTagRepository,
final TargetRepository targetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final ActionRepository actionRepository,
final QuotaManagement quotaManagement,
@@ -113,10 +96,8 @@ public class JpaDistributionSetManagement
final RepositoryProperties repositoryProperties) {
super(jpaRepository, entityManager);
this.distributionSetTagManagement = distributionSetTagManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement;
this.softwareModuleRepository = softwareModuleRepository;
this.distributionSetTagRepository = distributionSetTagRepository;
this.targetRepository = targetRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.actionRepository = actionRepository;
this.quotaManagement = quotaManagement;
@@ -133,11 +114,11 @@ public class JpaDistributionSetManagement
if (Boolean.TRUE.equals(update.getLocked())) {
if (!distributionSet.isLocked()) {
lockSoftwareModules(distributionSet);
distributionSet.lock();
distributionSet.setLocked(true);
}
} else if (Boolean.FALSE.equals(update.getLocked())) {
if (distributionSet.isLocked()) {
distributionSet.unlock();
distributionSet.setLocked(false);
}
}
@@ -173,16 +154,80 @@ public class JpaDistributionSetManagement
return jpaRepository.findOne(jpaRepository.byIdSpec(id), JpaDistributionSet_.GRAPH_DISTRIBUTION_SET_DETAIL);
}
@Override
public JpaDistributionSet getOrElseThrowException(final long id) {
return getById(id);
}
// implicitly lock a distribution set if not already locked and implicit lock is enabled and not to skip
@Override
@Transactional
public void invalidate(final JpaDistributionSet distributionSet) {
distributionSet.invalidate();
jpaRepository.save(distributionSet);
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public boolean shouldLockImplicitly(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
if (jpaDistributionSet.isLocked()) {
// already locked
return false;
}
if (Boolean.FALSE.equals(tenantConfigHelper.getConfigValue(IMPLICIT_LOCK_ENABLED, Boolean.class))) {
// implicit lock disabled
return false;
}
final List<String> skipForTags = repositoryProperties.getSkipImplicitLockForTags();
if (!ObjectUtils.isEmpty(skipForTags)) {
final Set<DistributionSetTag> tags = jpaDistributionSet.getTags();
if (!ObjectUtils.isEmpty(tags)) {
for (final DistributionSetTag tag : tags) {
if (skipForTags.contains(tag.getName())) {
// has a skip tag
return false;
}
}
}
}
// finally - implicitly lock
return true;
}
@Override
public JpaDistributionSet getOrElseThrowException(final long id) {
return getById(id);
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public JpaDistributionSet lock(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
if (distributionSet.isLocked()) {
return jpaDistributionSet;
} else {
if (!jpaDistributionSet.isComplete()) {
throw new IncompleteDistributionSetException("Could not be locked while incomplete!");
}
lockSoftwareModules(jpaDistributionSet);
jpaDistributionSet.setLocked(true);
return jpaRepository.save(jpaDistributionSet);
}
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public JpaDistributionSet unlock(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
if (jpaDistributionSet.isLocked()) {
jpaDistributionSet.setLocked(false);
return jpaRepository.save(jpaDistributionSet);
} else {
return jpaDistributionSet;
}
}
@Override
@Transactional
public JpaDistributionSet invalidate(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
jpaDistributionSet.invalidate();
return jpaRepository.save(jpaDistributionSet);
}
@Override
@@ -245,51 +290,6 @@ public class JpaDistributionSetManagement
});
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public JpaDistributionSet lock(final long id) {
final JpaDistributionSet distributionSet = getById(id);
if (distributionSet.isLocked()) {
return distributionSet;
} else {
lockSoftwareModules(distributionSet);
distributionSet.lock();
return jpaRepository.save(distributionSet);
}
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public JpaDistributionSet unlock(final long id) {
final JpaDistributionSet distributionSet = getById(id);
if (distributionSet.isLocked()) {
distributionSet.unlock();
return jpaRepository.save(distributionSet);
} else {
return distributionSet;
}
}
@Override
public Optional<JpaDistributionSet> findByAction(final long actionId) {
return actionRepository
.findById(actionId)
.map(action -> {
if (!targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))) {
throw new InsufficientPermissionException("Target not accessible (or not found)!");
}
return jpaRepository
.findOne(DistributionSetSpecification.byIdFetch(action.getDistributionSet().getId()))
.orElseThrow(() ->
new InsufficientPermissionException("DistributionSet not accessible (or not found)!"));
})
.or(() -> {
throw new EntityNotFoundException(Action.class, actionId);
});
}
@Override
public Optional<JpaDistributionSet> findByNameAndVersion(final String distributionName, final String version) {
return jpaRepository.findOne(DistributionSetSpecification.equalsNameAndVersionIgnoreCase(distributionName, version));
@@ -311,35 +311,6 @@ public class JpaDistributionSetManagement
return distributionSet;
}
@Override
public JpaDistributionSet getValid(final long id) {
return getValid0(id);
}
@Override
public Slice<JpaDistributionSet> findByCompleted(final Boolean complete, final Pageable pageReq) {
final List<Specification<JpaDistributionSet>> specifications = buildSpecsByComplete(complete);
return JpaManagementHelper.findAllWithoutCountBySpec(jpaRepository, specifications, pageReq);
}
@Override
public long countByCompleted(final Boolean complete) {
return JpaManagementHelper.countBySpec(jpaRepository, buildSpecsByComplete(complete));
}
@Override
public Slice<JpaDistributionSet> findByDistributionSetFilter(final DistributionSetFilter distributionSetFilter, final Pageable pageable) {
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(distributionSetFilter);
return JpaManagementHelper.findAllWithoutCountBySpec(jpaRepository, specList, pageable);
}
@Override
public long countByDistributionSetFilter(@NotNull final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(distributionSetFilter);
return JpaManagementHelper.countBySpec(jpaRepository, specList);
}
@Override
public Page<JpaDistributionSet> findByTag(final long tagId, final Pageable pageable) {
assertDsTagExists(tagId);
@@ -357,20 +328,6 @@ public class JpaDistributionSetManagement
pageable);
}
@Override
public long countByTypeId(final long typeId) {
if (!distributionSetTypeManagement.exists(typeId)) {
throw new EntityNotFoundException(DistributionSetType.class, typeId);
}
return jpaRepository.count(DistributionSetSpecification.byType(typeId));
}
@Override
public boolean isInUse(final long id) {
assertDistributionSetExists(id);
return actionRepository.countByDistributionSetId(id) > 0;
}
@Override
public List<Statistic> countRolloutsByStatusForDistributionSet(final Long id) {
assertDistributionSetExists(id);
@@ -400,70 +357,6 @@ public class JpaDistributionSetManagement
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, String.class, DistributionSet.class);
}
// check if it shall implicitly lock a distribution set
boolean isImplicitLockApplicable(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = (JpaDistributionSet) distributionSet;
if (jpaDistributionSet.isLocked()) {
// already locked
return false;
}
if (Boolean.FALSE.equals(tenantConfigHelper.getConfigValue(IMPLICIT_LOCK_ENABLED, Boolean.class))) {
// implicit lock disabled
return false;
}
final List<String> skipForTags = repositoryProperties.getSkipImplicitLockForTags();
if (!ObjectUtils.isEmpty(skipForTags)) {
final Set<DistributionSetTag> tags = jpaDistributionSet.getTags();
if (!ObjectUtils.isEmpty(tags)) {
for (final DistributionSetTag tag : tags) {
if (skipForTags.contains(tag.getName())) {
// has a skip tag
return false;
}
}
}
}
// finally - implicit lock is applicable
return true;
}
private static List<Specification<JpaDistributionSet>> buildDistributionSetSpecifications(
final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>(10);
if (distributionSetFilter.getIsComplete() != null) {
specList.add(DistributionSetSpecification.isCompleted(distributionSetFilter.getIsComplete()));
}
if (distributionSetFilter.getIsDeleted() != null) {
specList.add(DistributionSetSpecification.isDeleted(distributionSetFilter.getIsDeleted()));
}
if (distributionSetFilter.getIsValid() != null) {
specList.add(DistributionSetSpecification.isValid(distributionSetFilter.getIsValid()));
}
if (distributionSetFilter.getTypeId() != null) {
specList.add(DistributionSetSpecification.byType(distributionSetFilter.getTypeId()));
}
if (!ObjectUtils.isEmpty(distributionSetFilter.getSearchText())) {
final String[] dsFilterNameAndVersionEntries = JpaManagementHelper
.getFilterNameAndVersionEntries(distributionSetFilter.getSearchText().trim());
specList.add(DistributionSetSpecification.likeNameAndVersion(dsFilterNameAndVersionEntries[0], dsFilterNameAndVersionEntries[1]));
}
if (hasTagsFilterActive(distributionSetFilter)) {
specList.add(DistributionSetSpecification.hasTags(
distributionSetFilter.getTagNames(), distributionSetFilter.getSelectDSWithNoTag()));
}
return specList;
}
private static boolean hasTagsFilterActive(final DistributionSetFilter distributionSetFilter) {
final boolean isNoTagActive = Boolean.TRUE.equals(distributionSetFilter.getSelectDSWithNoTag());
final boolean isAtLeastOneTagActive = !CollectionUtils.isEmpty(distributionSetFilter.getTagNames());
return isNoTagActive || isAtLeastOneTagActive;
}
private static Collection<Long> notFound(final Collection<Long> distributionSetIds, final List<JpaDistributionSet> foundDistributionSets) {
final Map<Long, JpaDistributionSet> foundDistributionSetMap = foundDistributionSets.stream()
.collect(Collectors.toMap(JpaDistributionSet::getId, Function.identity()));
@@ -507,15 +400,6 @@ public class JpaDistributionSetManagement
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId));
}
private List<Specification<JpaDistributionSet>> buildSpecsByComplete(final Boolean complete) {
final List<Specification<JpaDistributionSet>> specifications = new ArrayList<>();
specifications.add(DistributionSetSpecification.isNotDeleted());
if (complete != null) {
specifications.add(DistributionSetSpecification.isCompleted(complete));
}
return specifications;
}
private void assertSoftwareModuleQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxSoftwareModulesPerDistributionSet(),
SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId);
@@ -544,6 +428,14 @@ public class JpaDistributionSetManagement
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, id));
}
private JpaDistributionSet toJpaDistributionSet(final DistributionSet distributionSet) {
if (distributionSet instanceof JpaDistributionSet jpaDistributionSet) {
return entityManager.merge(jpaDistributionSet); // if from
} else {
return getById(distributionSet.getId());
}
}
private void assertDistributionSetExists(final long id) {
if (!jpaRepository.existsById(id)) {
throw new EntityNotFoundException(DistributionSet.class, id);

View File

@@ -43,39 +43,9 @@ public class JpaDistributionSetTagManagement
extends AbstractJpaRepositoryManagement<JpaDistributionSetTag, DistributionSetTagManagement.Create, DistributionSetTagManagement.Update, DistributionSetTagRepository, DistributionSetTagFields>
implements DistributionSetTagManagement<JpaDistributionSetTag> {
private final DistributionSetRepository distributionSetRepository;
protected JpaDistributionSetTagManagement(
final DistributionSetTagRepository distributionSetTagRepository,
final EntityManager entityManager,
final DistributionSetRepository distributionSetRepository) {
final EntityManager entityManager) {
super(distributionSetTagRepository, entityManager);
this.distributionSetRepository = distributionSetRepository;
}
@Override
public Optional<JpaDistributionSetTag> findByName(final String name) {
return jpaRepository.findByNameEquals(name);
}
@Override
public Page<JpaDistributionSetTag> findByDistributionSet(final long distributionSetId, final Pageable pageable) {
if (!distributionSetRepository.existsById(distributionSetId)) {
throw new EntityNotFoundException(DistributionSet.class, distributionSetId);
}
return JpaManagementHelper.findAllWithCountBySpec(jpaRepository,
Collections.singletonList(TagSpecification.ofDistributionSet(distributionSetId)), pageable
);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void delete(final String tagName) {
final JpaDistributionSetTag dsTag = jpaRepository
.findOne(DistributionSetTagSpecifications.byName(tagName))
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName));
jpaRepository.delete(dsTag);
}
}

View File

@@ -564,8 +564,8 @@ public class JpaRolloutManagement implements RolloutManagement {
rollout.setTotalTargets(totalTargets);
}
if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId());
if (distributionSetManagement.shouldLockImplicitly(distributionSet)) {
distributionSetManagement.lock(distributionSet);
}
if (rollout.getWeight().isEmpty()) {

View File

@@ -33,7 +33,6 @@ import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeExcep
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
@@ -164,11 +163,10 @@ class JpaTargetFilterQueryManagement
} else {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
assertMaxTargetsQuota(targetFilterQuery.getQuery(), targetFilterQuery.getName(), update.dsId());
final JpaDistributionSet distributionSet = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(update.dsId());
if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId());
DistributionSet distributionSet = distributionSetManagement.getValidAndComplete(update.dsId());
if (distributionSetManagement.shouldLockImplicitly(distributionSet)) {
distributionSet = distributionSetManagement.lock(distributionSet);
}
targetFilterQuery.setAutoAssignDistributionSet(distributionSet);

View File

@@ -20,6 +20,7 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
@@ -29,6 +30,7 @@ import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities
@Setter
@Getter
@ToString(callSuper = true)
@MappedSuperclass
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")

View File

@@ -16,12 +16,18 @@ import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
/**
* Extension for {@link NamedEntity} that are versioned.
*/
@Setter
@Getter
@ToString(callSuper = true)
@MappedSuperclass
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")
@@ -50,13 +56,4 @@ public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEn
AbstractJpaNamedVersionedEntity() {
// Default constructor needed for JPA entities
}
@Override
public String getVersion() {
return version;
}
public void setVersion(final String version) {
this.version = version;
}
}

View File

@@ -124,6 +124,7 @@ public class JpaDistributionSet
@Column(name = "complete")
private boolean complete;
@Setter
@Column(name = "locked")
private boolean locked;
@@ -213,31 +214,18 @@ public class JpaDistributionSet
return tags.remove(tag);
}
public void lock() {
if (!isComplete()) {
throw new IncompleteDistributionSetException("Could not be locked while incomplete!");
}
locked = true;
}
public void unlock() {
locked = false;
}
public void invalidate() {
this.valid = false;
}
@Override
public void fireCreateEvent() {
publishEventWithEventPublisher(
new DistributionSetCreatedEvent(this));
publishEventWithEventPublisher(new DistributionSetCreatedEvent(this));
}
@Override
public void fireUpdateEvent() {
publishEventWithEventPublisher(
new DistributionSetUpdatedEvent(this, complete));
publishEventWithEventPublisher(new DistributionSetUpdatedEvent(this, complete));
if (deleted) {
publishEventWithEventPublisher(new DistributionSetDeletedEvent(getTenant(), getId(), getClass()));

View File

@@ -19,7 +19,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaStatistic;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
@@ -65,16 +64,6 @@ public interface DistributionSetRepository extends BaseEntityRepository<JpaDistr
@Query(value = "SELECT COUNT(f.autoAssignDistributionSet) FROM JpaTargetFilterQuery f WHERE f.autoAssignDistributionSet.id = :dsId GROUP BY f.autoAssignDistributionSet")
Long countAutoAssignmentsForDistributionSet(@Param("dsId") Long dsId);
/**
* Finds {@link DistributionSet}s where given {@link SoftwareModule} is assigned.
* <p/>
* No access control applied.
*
* @param moduleId to search for
* @return {@link List} of found {@link DistributionSet}s
*/
Long countByModulesId(Long moduleId);
/**
* Finds {@link DistributionSet}s based on given ID that are assigned yet to an {@link Action}, i.e. in use.
* <p/>

View File

@@ -30,14 +30,6 @@ import org.springframework.transaction.annotation.Transactional;
public interface DistributionSetTagRepository
extends BaseEntityRepository<JpaDistributionSetTag> {
/**
* find {@link DistributionSetTag} by its name.
*
* @param tagName to filter on
* @return the {@link DistributionSetTag} if found, otherwise null
*/
Optional<JpaDistributionSetTag> findByNameEquals(String tagName);
/**
* Returns all instances of the type.
*
@@ -58,4 +50,4 @@ public interface DistributionSetTagRepository
@Transactional
@Query("DELETE FROM JpaDistributionSetTag t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
}
}

View File

@@ -38,17 +38,6 @@ public interface DistributionSetTypeRepository extends BaseEntityRepository<JpaD
*/
long countByElementsSmType(JpaSoftwareModuleType softwareModuleType);
/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety reasons (this is a "delete everything" query after all) we add
* the tenant manually to query even if this will by done by {@link EntityManager} anyhow. The DB should take care of optimizing this away.
*
* @param tenant to delete data from
*/
@Modifying
@Transactional
@Query("DELETE FROM JpaDistributionSetType t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
/**
* Counts the {@link SoftwareModuleType}s which are associated with the addressed {@link DistributionSetType}.
* <p/>
@@ -59,4 +48,15 @@ public interface DistributionSetTypeRepository extends BaseEntityRepository<JpaD
*/
@Query("SELECT COUNT (e.smType) FROM DistributionSetTypeElement e WHERE e.dsType.id = :id")
long countSmTypesById(@Param("id") Long id);
/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety reasons (this is a "delete everything" query after all) we add
* the tenant manually to query even if this will by done by {@link EntityManager} anyhow. The DB should take care of optimizing this away.
*
* @param tenant to delete data from
*/
@Modifying
@Transactional
@Query("DELETE FROM JpaDistributionSetType t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
}