Introduce basic functionality for invalidation of distributionsets (#1179)

* Basic DS invalidation functionality

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add checks for valid/complete DS

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Stop rollouts + auto assignments when invalidating a DS

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add methods to count AAs + rollouts for invalidation

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Small refactoring for DS management

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add invalidation functionality to REST API

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix update stopped rollouts status

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add various tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Introduce countActionsForInvalidation

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix event tests with incomplete DS

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add H2 migration script

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix action count method

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix REST documentation tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Change flyway version number

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add lock for DS invalidation + adapt tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Move concurrency test to own class

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Handle possible InterruptedException

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix concurrency test

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Use one transaction for all invalidations

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add invalidate endpoint to REST docu

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Execute invalidation in transaction when actions are cancelled

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Check that distribution set is valid when editing/creating metadata

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Remove all changes in UI

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add DB migration files for all databases

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Implement review findings

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Move DS invalidation to own class to check permissions for single steps

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Move invalidation count methods to management classes

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix failing tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>
This commit is contained in:
Sebastian Firsching
2021-09-30 15:26:36 +02:00
committed by GitHub
parent b25e118e6c
commit 825cb64448
66 changed files with 2413 additions and 511 deletions

View File

@@ -64,6 +64,28 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*/
Page<Action> findByDistributionSetId(Pageable pageable, Long dsId);
/**
* Retrieves all active {@link Action}s which are referring the given
* {@link DistributionSet}.
*
* @param set
* the {@link DistributionSet} on which will be filtered
* @return the found {@link Action}s
*/
List<Action> findByDistributionSetAndActiveIsTrue(DistributionSet set);
/**
* Retrieves all active {@link Action}s which are referring the given
* {@link DistributionSet} and are not in the given state
*
* @param set
* the {@link DistributionSet} on which will be filtered
* @param status
* the state the actions should not have
* @return the found {@link Action}s
*/
List<Action> findByDistributionSetAndActiveIsTrueAndStatusIsNot(DistributionSet set, Status status);
/**
* Retrieves all {@link Action}s which are referring the given
* {@link Target}.
@@ -103,7 +125,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* Retrieves the active {@link Action}s with the highest weights that refer
* to the given {@link Target}. If {@link Action}s have the same weight they
* are ordered ascending by ID (oldest ones first).
*
*
* @param pageable
* pageable
* @param controllerId
@@ -117,7 +139,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Retrieves the active {@link Action}s with the lowest IDs (the oldest one)
* whose weight is null and that that refers to the given {@link Target}.
*
*
* @param pageable
* pageable
* @param controllerId
@@ -131,7 +153,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Checks if an active action exists for given
* {@link Target#getControllerId()}.
*
*
* @param controllerId
* of target to check
* @return <code>true</code> if an active action for the target exists.
@@ -264,10 +286,9 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
List<Action> findByExternalRefInAndActive(@Param("externalRefs") List<String> externalRefs,
@Param("active") boolean active);
/**
* Retrieves an {@link Action} that matches the queried externalRef.
*
*
* @param externalRef
* of the action. See {@link Action#getExternalRef()}
* @return the found {@link Action}
@@ -351,6 +372,27 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*/
Long countByDistributionSetId(Long distributionSet);
/**
* Counts all active {@link Action}s referring to the given DistributionSet.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @return the count of actions referring to the given distributionSet
*/
Long countByDistributionSetIdAndActiveIsTrue(Long distributionSet);
/**
* Counts all active {@link Action}s referring to the given DistributionSet
* that are not in a given state.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @param status
* the state the actions should not have
* @return the count of actions referring to the given distributionSet
*/
Long countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status);
/**
* Counts all actions referring to a given rollout and rolloutgroup which
* are currently not in the given status. An in-clause statement does not
@@ -396,7 +438,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all actions referring to a given rollout and status.
*
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @param status
@@ -409,7 +451,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @return {@code true} if actions for the given rollout exists, otherwise
@@ -421,7 +463,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @param status
@@ -471,7 +513,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Retrieves all actions for a specific rollout and in a specific status.
*
*
* @param pageable
* page parameters
* @param rolloutId
@@ -528,7 +570,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Deletes all actions with the given IDs.
*
*
* @param actionIDs
* the IDs of the actions to be deleted.
*/

View File

@@ -35,6 +35,7 @@ import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -43,7 +44,6 @@ import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEv
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.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;
@@ -67,6 +67,7 @@ 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.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
@@ -130,6 +131,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private final EntityManager entityManager;
private final DistributionSetManagement distributionSetManagement;
private final DistributionSetRepository distributionSetRepository;
private final TargetRepository targetRepository;
private final ActionStatusRepository actionStatusRepository;
@@ -146,6 +148,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private final RetryTemplate retryTemplate;
protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement,
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
@@ -156,6 +159,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
super(actionRepository, repositoryProperties);
this.entityManager = entityManager;
this.distributionSetRepository = distributionSetRepository;
this.distributionSetManagement = distributionSetManagement;
this.targetRepository = targetRepository;
this.actionStatusRepository = actionStatusRepository;
this.auditorProvider = auditorProvider;
@@ -254,8 +258,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
*
*
*
*
* In case the update was executed offline (i.e. not managed by hawkBit) the
* handling differs my means that:<br/>
* A. it ignores targets completely that are in
@@ -285,7 +289,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID);
final JpaDistributionSet distributionSetEntity = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(dsID);
final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.distinct().collect(Collectors.toList());
@@ -363,18 +368,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions);
}
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 void checkQuotaForAssignment(final Collection<DeploymentRequest> deploymentRequests) {
if (!deploymentRequests.isEmpty()) {
enforceMaxAssignmentsPerRequest(deploymentRequests.size());
@@ -656,8 +649,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private Specification<JpaAction> createSpecificationFor(final String controllerId, final String rsqlParam) {
final Specification<JpaAction> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer,
database);
final Specification<JpaAction> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class,
virtualPropertyReplacer, database);
return (root, query, cb) -> cb.and(spec.toPredicate(root, query, cb),
cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId));
}
@@ -779,6 +772,17 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return actionRepository.count();
}
@Override
public long countActionsByDistributionSetIdAndActiveIsTrue(final Long distributionSet) {
return actionRepository.countByDistributionSetIdAndActiveIsTrue(distributionSet);
}
@Override
public long countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(final Long distributionSet,
final Status status) {
return actionRepository.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, status);
}
@Override
public Slice<Action> findActionsByDistributionSet(final Pageable pageable, final long dsId) {
throwExceptionIfDistributionSetDoesNotExist(dsId);
@@ -832,7 +836,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
@Override
public boolean hasPendingCancellations(String controllerId) {
public boolean hasPendingCancellations(final String controllerId) {
return actionRepository.existsByTargetControllerIdAndStatusAndActiveIsTrue(controllerId,
Action.Status.CANCELING);
}
@@ -881,4 +885,21 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return template;
}
@Override
@Transactional
public void cancelActionsForDistributionSet(final CancelationType cancelationType, final DistributionSet set) {
actionRepository.findByDistributionSetAndActiveIsTrueAndStatusIsNot(set, Status.CANCELING).forEach(action -> {
final JpaAction jpaAction = (JpaAction) action;
cancelAction(jpaAction.getId());
LOG.debug("Action {} canceled", jpaAction.getId());
});
if (cancelationType == CancelationType.FORCE) {
actionRepository.findByDistributionSetAndActiveIsTrue(set).forEach(action -> {
final JpaAction jpaAction = (JpaAction) action;
forceQuitAction(jpaAction.getId());
LOG.debug("Action {} force canceled", jpaAction.getId());
});
}
}
}

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
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.StopRolloutException;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Jpa implementation for {@link DistributionSetInvalidationManagement}
*
*/
public class JpaDistributionSetInvalidationManagement implements DistributionSetInvalidationManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDistributionSetInvalidationManagement.class);
private final DistributionSetManagement distributionSetManagement;
private final RolloutManagement rolloutManagement;
private final DeploymentManagement deploymentManagement;
private final TargetFilterQueryManagement targetFilterQueryManagement;
private final PlatformTransactionManager txManager;
private final RepositoryProperties repositoryProperties;
private final TenantAware tenantAware;
private final LockRegistry lockRegistry;
protected JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement,
final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final PlatformTransactionManager txManager,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
this.distributionSetManagement = distributionSetManagement;
this.rolloutManagement = rolloutManagement;
this.deploymentManagement = deploymentManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.txManager = txManager;
this.repositoryProperties = repositoryProperties;
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
}
@Override
public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) {
LOG.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds());
final String tenant = tenantAware.getCurrentTenant();
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts())) {
final String handlerId = JpaRolloutManagement.createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId);
try {
if (!lock.tryLock(repositoryProperties.getDsInvalidationLockTimeout(), TimeUnit.SECONDS)) {
throw new StopRolloutException("Timeout while trying to invalidate distribution sets");
}
try {
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
} finally {
lock.unlock();
}
} catch (final InterruptedException e) {
LOG.error("InterruptedException while invalidating distribution sets {}!",
distributionSetInvalidation.getDistributionSetIds(), e);
Thread.currentThread().interrupt();
}
} else {
// no lock is needed as no rollout will be stopped
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
}
}
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
final String tenant) {
DeploymentHelper.runInNewTransaction(txManager, tenant + "-invalidateDS", status -> {
distributionSetInvalidation.getDistributionSetIds().forEach(setId -> invalidateDistributionSet(setId,
distributionSetInvalidation.getCancelationType(), distributionSetInvalidation.isCancelRollouts()));
return 0;
});
}
private void invalidateDistributionSet(final long setId, final CancelationType cancelationType,
final boolean cancelRollouts) {
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
distributionSetManagement.invalidate(set);
LOG.debug("Distribution set {} set to invalid", setId);
if (shouldRolloutsBeCanceled(cancelationType, cancelRollouts)) {
rolloutManagement.cancelRolloutsForDistributionSet(set);
}
if (cancelationType != CancelationType.NONE) {
deploymentManagement.cancelActionsForDistributionSet(cancelationType, set);
}
targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId);
}
private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
final boolean cancelRollouts) {
return cancelationType != CancelationType.NONE || cancelRollouts;
}
@Override
public DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation) {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds, distributionSetInvalidation.getCancelationType());
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
}
private long countRolloutsForInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(rolloutManagement::countByDistributionSetIdAndRolloutIsStoppable).sum();
}
private long countAutoAssignmentsForInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(targetFilterQueryManagement::countByAutoAssignDistributionSetId).sum();
}
private long countActionsForInvalidation(final Collection<Long> setIds, final CancelationType cancelationType) {
long affectedActionsByDSInvalidation = 0;
if (cancelationType == CancelationType.FORCE) {
affectedActionsByDSInvalidation = countActionsForForcedInvalidation(setIds);
} else if (cancelationType == CancelationType.SOFT) {
affectedActionsByDSInvalidation = countActionsForSoftInvalidation(setIds);
}
return affectedActionsByDSInvalidation;
}
private long countActionsForForcedInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(deploymentManagement::countActionsByDistributionSetIdAndActiveIsTrue).sum();
}
private long countActionsForSoftInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(distributionSet -> deploymentManagement
.countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum();
}
}

View File

@@ -32,6 +32,8 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
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.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -213,7 +215,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
public DistributionSet update(final DistributionSetUpdate u) {
final GenericDistributionSetUpdate update = (GenericDistributionSetUpdate) u;
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(update.getId());
final JpaDistributionSet set = (JpaDistributionSet) getValid(update.getId());
update.getName().ifPresent(set::setName);
update.getDescription().ifPresent(set::setDescription);
@@ -228,11 +230,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSetRepository.save(set);
}
private JpaDistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSet) get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long moduleId) {
return softwareModuleRepository.findById(moduleId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId));
@@ -315,7 +312,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
assertDistributionSetIsNotAssignedToTargets(setId);
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId);
final JpaDistributionSet set = (JpaDistributionSet) getValid(setId);
assertSoftwareModuleQuota(setId, modules.size());
@@ -329,7 +326,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSet unassignSoftwareModule(final long setId, final long moduleId) {
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId);
final JpaDistributionSet set = (JpaDistributionSet) getValid(setId);
final JpaSoftwareModule module = findSoftwareModuleAndThrowExceptionIfNotFound(moduleId);
assertDistributionSetIsNotAssignedToTargets(setId);
@@ -531,7 +528,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
* of the DS to touch
*/
private JpaDistributionSet touch(final Long distId) {
return touch(get(distId).orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distId)));
return touch(getValid(distId));
}
@Override
@@ -595,7 +592,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private static List<Specification<JpaDistributionSet>> buildDistributionSetSpecifications(
final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = Lists.newArrayListWithExpectedSize(8);
final List<Specification<JpaDistributionSet>> specList = Lists.newArrayListWithExpectedSize(9);
Specification<JpaDistributionSet> spec;
@@ -609,6 +606,11 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
specList.add(spec);
}
if (distributionSetFilter.getIsValid() != null) {
spec = DistributionSetSpecification.isValid(distributionSetFilter.getIsValid());
specList.add(spec);
}
if (distributionSetFilter.getType() != null) {
spec = DistributionSetSpecification.byType(distributionSetFilter.getType());
specList.add(spec);
@@ -781,8 +783,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
public Page<DistributionSet> findByRsqlAndTag(final Pageable pageable, final String rsqlParam, final long tagId) {
throwEntityNotFoundExceptionIfDsTagDoesNotExist(tagId);
final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetFields.class,
virtualPropertyReplacer, database);
final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam,
DistributionSetFields.class, virtualPropertyReplacer, database);
return convertDsPage(findByCriteriaAPI(pageable, Arrays.asList(spec, DistributionSetSpecification.hasTag(tagId),
DistributionSetSpecification.isDeleted(false))), pageable);
@@ -797,8 +799,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Override
public Page<DistributionSet> findByRsql(final Pageable pageable, final String rsqlParam) {
final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetFields.class,
virtualPropertyReplacer, database);
final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam,
DistributionSetFields.class, virtualPropertyReplacer, database);
return convertDsPage(
findByCriteriaAPI(pageable, Arrays.asList(spec, DistributionSetSpecification.isDeleted(false))),
@@ -815,4 +817,41 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSetRepository.existsById(id);
}
@Override
public DistributionSet getOrElseThrowException(final long id) {
return get(id).orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, id));
}
@Override
public DistributionSet getValidAndComplete(final long id) {
final DistributionSet distributionSet = getValid(id);
if (!distributionSet.isComplete()) {
throw new IncompleteDistributionSetException("Distribution set of type "
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
}
return distributionSet;
}
@Override
public DistributionSet getValid(final long id) {
final DistributionSet distributionSet = getOrElseThrowException(id);
if (!distributionSet.isValid()) {
throw new InvalidDistributionSetException("Distribution set of type " + distributionSet.getType().getKey()
+ " is invalid: " + distributionSet.getId());
}
return distributionSet;
}
@Override
@Transactional
public void invalidate(final DistributionSet set) {
final JpaDistributionSet jpaSet = (JpaDistributionSet) set;
jpaSet.invalidate();
distributionSetRepository.save(jpaSet);
}
}

View File

@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
@@ -155,6 +156,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
case RUNNING:
handleRunningRollout((JpaRollout) rollout);
break;
case STOPPING:
handleStopRollout((JpaRollout) rollout);
break;
default:
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
break;
@@ -244,6 +248,39 @@ public class JpaRolloutExecutor implements RolloutExecutor {
sendRolloutGroupDeletedEvents(rollout);
}
private void handleStopRollout(final JpaRollout rollout) {
LOGGER.debug("handleStopRollout called for {}", rollout.getId());
// clean up all scheduled actions
final Slice<JpaAction> scheduledActions = findScheduledActionsByRollout(rollout);
deleteScheduledActions(rollout, scheduledActions);
// avoid another scheduler round and re-check if all scheduled actions
// has been cleaned up. we flush first to ensure that the we include the
// deletion above
entityManager.flush();
final boolean hasScheduledActionsLeft = actionRepository.countByRolloutIdAndStatus(rollout.getId(),
Status.SCHEDULED) > 0;
if (hasScheduledActionsLeft) {
return;
}
rolloutGroupRepository.findByRolloutAndStatusNotIn(rollout,
Arrays.asList(RolloutGroupStatus.FINISHED, RolloutGroupStatus.ERROR)).forEach(rolloutGroup -> {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup);
});
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId)
.collect(Collectors.toList());
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(new RolloutStoppedEvent(
tenantAware.getCurrentTenant(), eventPublisherHolder.getApplicationId(), rollout.getId(), groupIds)));
}
private void handleReadyRollout(final Rollout rollout) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
LOGGER.debug(

View File

@@ -100,7 +100,12 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING,
RolloutStatus.DELETING, RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING);
RolloutStatus.DELETING, RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING,
RolloutStatus.STOPPING);
private static final List<RolloutStatus> ROLLOUT_STATUS_STOPPABLE = Arrays.asList(RolloutStatus.RUNNING,
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
@Autowired
private RolloutRepository rolloutRepository;
@@ -126,8 +131,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
private final Database database;
public JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final RolloutGroupManagement rolloutGroupManagement,
public JpaRolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
@@ -151,7 +156,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
@Override
public Page<Rollout> findByRsql(final Pageable pageable, final String rsqlParam, final boolean deleted) {
final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add(RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted));
return JpaRolloutHelper.convertPage(findByCriteriaAPI(pageable, specList), pageable);
@@ -400,7 +406,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
final String tenant = tenantAware.getCurrentTenant();
final String handlerId = tenant + "-rollout";
final String handlerId = createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) {
return;
@@ -414,6 +420,10 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
}
}
public static String createRolloutLockKey(final String tenant) {
return tenant + "-rollout";
}
private long handleRollout(final long rolloutId) {
final JpaRollout rollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
@@ -451,6 +461,11 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return rolloutRepository.count(JpaRolloutHelper.likeNameOrDescription(searchText, false));
}
@Override
public long countByDistributionSetIdAndRolloutIsStoppable(final long setId) {
return rolloutRepository.countByDistributionSetIdAndStatusIn(setId, ROLLOUT_STATUS_STOPPABLE);
}
@Override
public Slice<Rollout> findByFiltersWithDetailedStatus(final Pageable pageable, final String searchText,
final boolean deleted) {
@@ -483,8 +498,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
// null
rollout.setStartAt(update.getStartAt().orElse(null));
update.getSet().ifPresent(setId -> {
final DistributionSet set = distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
rollout.setDistributionSet(set);
});
@@ -596,7 +610,20 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
}
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), tenantAware);
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()),
tenantAware);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set) {
// stop all rollouts for this distribution set
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
jpaRollout.setStatus(RolloutStatus.STOPPING);
rolloutRepository.save(jpaRollout);
LOGGER.debug("Rollout {} stopped", jpaRollout.getId());
});
}
}

View File

@@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
@@ -72,7 +71,7 @@ import cz.jirutka.rsql.parser.RSQLParserException;
public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaTargetFilterQueryManagement.class);
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetManagement targetManagement;
@@ -147,6 +146,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return targetFilterQueryRepository.count();
}
@Override
public long countByAutoAssignDistributionSetId(final long autoAssignDistributionSetId) {
return targetFilterQueryRepository.countByAutoAssignDistributionSetId(autoAssignDistributionSetId);
}
private static Page<TargetFilterQuery> convertPage(final Page<JpaTargetFilterQuery> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
@@ -165,8 +169,8 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
public Page<TargetFilterQuery> findByRsql(final Pageable pageable, final String rsqlFilter) {
List<Specification<JpaTargetFilterQuery>> specList = Collections.emptyList();
if (!StringUtils.isEmpty(rsqlFilter)) {
specList = Collections.singletonList(
RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer, database));
specList = Collections.singletonList(RSQLUtility.buildRsqlSpecification(rsqlFilter,
TargetFilterQueryFields.class, virtualPropertyReplacer, database));
}
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
}
@@ -185,13 +189,13 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final String rsqlFilter) {
final List<Specification<JpaTargetFilterQuery>> specList = Lists.newArrayListWithExpectedSize(2);
final DistributionSet distributionSet = findDistributionSetAndThrowExceptionIfNotFound(setId);
final DistributionSet distributionSet = distributionSetManagement.getOrElseThrowException(setId);
specList.add(TargetFilterQuerySpecification.byAutoAssignDS(distributionSet));
if (!StringUtils.isEmpty(rsqlFilter)) {
specList.add(
RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer, database));
specList.add(RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class,
virtualPropertyReplacer, database));
}
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
}
@@ -264,8 +268,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
// specify an
// auto-assign distribution set when creating a target filter query
assertMaxTargetsQuota(targetFilterQuery.getQuery());
final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId());
verifyDistributionSetAndThrowExceptionIfNotValid(ds);
final JpaDistributionSet ds = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(update.getDsId());
verifyDistributionSetAndThrowExceptionIfDeleted(ds);
targetFilterQuery.setAutoAssignDistributionSet(ds);
targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername());
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
@@ -274,9 +279,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return targetFilterQueryRepository.save(targetFilterQuery);
}
private static void verifyDistributionSetAndThrowExceptionIfNotValid(final DistributionSet distributionSet) {
if (!distributionSet.isComplete() || distributionSet.isDeleted()) {
throw new InvalidAutoAssignDistributionSetException();
private static void verifyDistributionSetAndThrowExceptionIfDeleted(final DistributionSet distributionSet) {
if (distributionSet.isDeleted()) {
throw new EntityNotFoundException(DistributionSet.class, distributionSet.getId());
}
}
@@ -292,11 +297,6 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return actionType;
}
private JpaDistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSet) distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
private JpaTargetFilterQuery findTargetFilterQueryOrThrowExceptionIfNotFound(final Long queryId) {
return targetFilterQueryRepository.findById(queryId)
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, queryId));
@@ -317,4 +317,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
QuotaHelper.assertAssignmentQuota(targetManagement.countByRsql(query),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class);
}
@Override
@Transactional
public void cancelAutoAssignmentForDistributionSet(final long setId) {
targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionType(setId);
LOGGER.debug("Auto assignments for distribution sets {} deactivated", setId);
}
}

View File

@@ -82,8 +82,8 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupAct
import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
import org.eclipse.hawkbit.repository.jpa.rsql.DefaultRsqlVisitorFactory;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -238,13 +238,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
@Bean
TargetBuilder targetBuilder(final TargetTypeManagement targetTypeManagement){
TargetBuilder targetBuilder(final TargetTypeManagement targetTypeManagement) {
return new JpaTargetBuilder(targetTypeManagement);
}
/**
* @param dsTypeManagement
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* for loading
* {@link TargetType#getCompatibleDistributionSetTypes()}
* @return TargetTypeBuilder bean
*/
@Bean
@@ -434,7 +435,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Override
@Bean
public PlatformTransactionManager transactionManager(
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new MultiTenantJpaTransactionManager();
}
@@ -485,12 +486,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
DistributionSetTypeManagement distributionSetTypeManagement(
final DistributionSetTypeRepository distributionSetTypeRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository,
final TargetTypeRepository targetTypeRepository,
final DistributionSetRepository distributionSetRepository, final TargetTypeRepository targetTypeRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties,
final QuotaManagement quotaManagement) {
return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository,
distributionSetRepository, targetTypeRepository, virtualPropertyReplacer, properties.getDatabase(), quotaManagement);
distributionSetRepository, targetTypeRepository, virtualPropertyReplacer, properties.getDatabase(),
quotaManagement);
}
/**
@@ -541,13 +542,15 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetTypeRepository targetTypeRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) {
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository, targetMetadataRepository,
rolloutGroupRepository, distributionSetRepository, targetFilterQueryRepository, targetTagRepository,
eventPublisherHolder, tenantAware, afterCommit, virtualPropertyReplacer, properties.getDatabase());
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware,
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties) {
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository,
targetMetadataRepository, rolloutGroupRepository, distributionSetRepository,
targetFilterQueryRepository, targetTagRepository, eventPublisherHolder, tenantAware, afterCommit,
virtualPropertyReplacer, properties.getDatabase());
}
/**
@@ -718,17 +721,17 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@ConditionalOnMissingBean
DeploymentManagement deploymentManagement(final EntityManager entityManager,
final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository,
final TargetRepository targetRepository, final ActionStatusRepository actionStatusRepository,
final AuditorAware<String> auditorProvider, final EventPublisherHolder eventPublisherHolder,
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager,
final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
final JpaProperties properties, final RepositoryProperties repositoryProperties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository,
actionStatusRepository, auditorProvider, eventPublisherHolder, afterCommit, virtualPropertyReplacer,
txManager, tenantConfigurationManagement, quotaManagement, systemSecurityContext, tenantAware,
properties.getDatabase(), repositoryProperties);
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetManagement,
distributionSetRepository, targetRepository, actionStatusRepository, auditorProvider,
eventPublisherHolder, afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement,
quotaManagement, systemSecurityContext, tenantAware, properties.getDatabase(), repositoryProperties);
}
/**
@@ -813,7 +816,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoAssignScheduler} bean.
*
*
* Note: does not activate in test profile, otherwise it is hard to test the
* auto assign functionality.
*
@@ -843,12 +846,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoActionCleanup} bean.
*
*
* @param deploymentManagement
* Deployment management service
* @param configManagement
* Tenant configuration service
*
*
* @return a new {@link AutoActionCleanup} bean
*/
@Bean
@@ -859,7 +862,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoCleanupScheduler} bean.
*
*
* @param systemManagement
* to find all tenants
* @param systemSecurityContext
@@ -868,7 +871,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* to lock the tenant for auto assignment
* @param cleanupTasks
* a list of cleanup tasks
*
*
* @return a new {@link AutoCleanupScheduler} bean
*/
@Bean
@@ -883,10 +886,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link RolloutScheduler} bean.
*
*
* Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality.
*
*
* @param systemManagement
* to find all tenants
* @param rolloutManagement
@@ -906,7 +909,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* Creates the {@link RsqlVisitorFactory} bean.
*
*
* @return A new {@link RsqlVisitorFactory} bean.
*/
@Bean
@@ -917,7 +920,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* Obtains the {@link RsqlVisitorFactoryHolder} bean.
*
*
* @return The {@link RsqlVisitorFactoryHolder} singleton.
*/
@Bean
@@ -925,4 +928,22 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return RsqlVisitorFactoryHolder.getInstance();
}
/**
* {@link JpaDistributionSetInvalidationManagement} bean.
*
* @return a new {@link JpaDistributionSetInvalidationManagement}
*/
@Bean
@ConditionalOnMissingBean
JpaDistributionSetInvalidationManagement distributionSetInvalidationManagement(
final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement,
final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final PlatformTransactionManager txManager,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
return new JpaDistributionSetInvalidationManagement(distributionSetManagement, rolloutManagement,
deploymentManagement, targetFilterQueryManagement, txManager, repositoryProperties, tenantAware,
lockRegistry);
}
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
@@ -33,7 +34,7 @@ public interface RolloutGroupRepository
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in the
* order of creating them. ID ASC.
*
*
* @param rollout
* the rollout the rolloutgroups belong to
* @return the rollout groups belonging to a rollout ordered by ID ASC.
@@ -43,7 +44,7 @@ public interface RolloutGroupRepository
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in a
* specific {@link RolloutGroupStatus}.
*
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param status
@@ -58,7 +59,7 @@ public interface RolloutGroupRepository
* the spring-data, so this is specific usecase regarding to the
* rollout-management to find out rolloutgroups which are in specific
* states.
*
*
* @param rolloutId
* the ID of the rollout the rolloutgroup belong to
* @param rolloutGroupStatus1
@@ -74,10 +75,10 @@ public interface RolloutGroupRepository
@Param("status2") RolloutGroupStatus rolloutGroupStatus2);
/**
*
*
* Counts all rollout-groups refering to a given {@link Rollout} by its ID
* and groups which not having the given status.
*
*
* @param rolloutId
* the ID of the rollout refering the groups
* @param status1
@@ -97,7 +98,7 @@ public interface RolloutGroupRepository
/**
* Retrieves all {@link RolloutGroup} for a specific parent in a specific
* status. Retrieves the child rolloutgroup for a specific status.
*
*
* @param rolloutGroupId
* the rolloutgroupId to find the parents
* @param status
@@ -111,7 +112,7 @@ public interface RolloutGroupRepository
/**
* Updates all {@link RolloutGroup#getStatus()} of children for given
* parent.
*
*
* @param parent
* the parent rolloutgroup
* @param status
@@ -125,7 +126,7 @@ public interface RolloutGroupRepository
/**
* Retrieves all {@link RolloutGroup} for a specific rollout and status not
* having ordered by ID DESC, latest top.
*
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param notStatus
@@ -135,9 +136,22 @@ public interface RolloutGroupRepository
*/
List<JpaRolloutGroup> findByRolloutAndStatusNotOrderByIdDesc(JpaRollout rollout, RolloutGroupStatus notStatus);
/**
* Retrieves all {@link RolloutGroup}s for a specific rollout and status not
* having.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param status
* the status which the rolloutgroup should not have
* @return rolloutgroup referring to a rollout and not having a specific
* status.
*/
List<JpaRolloutGroup> findByRolloutAndStatusNotIn(JpaRollout rollout, Collection<RolloutGroupStatus> status);
/**
* Retrieves all {@link RolloutGroup} for a specific rollout.
*
*
* @param rolloutId
* the ID of the rollout to find the rollout groups
* @param page
@@ -148,10 +162,10 @@ public interface RolloutGroupRepository
/**
* Counts all {@link RolloutGroup} for a specific rollout.
*
*
* @param rolloutId
* the ID of the rollout to find the rollout groups
*
*
* @return the amount of found {@link RolloutGroup}s.
*/
long countByRolloutId(Long rolloutId);

View File

@@ -15,6 +15,7 @@ import java.util.Optional;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
@@ -33,7 +34,7 @@ public interface RolloutRepository
/**
* Retrieves all {@link Rollout} for given status.
*
*
* @param status
* the status of the rollouts to find
* @return the list of {@link Rollout} for specific status
@@ -44,7 +45,7 @@ public interface RolloutRepository
/**
* Retrieves all {@link Rollout} for a specific {@code name}
*
*
* @param name
* the rollout name
* @return {@link Rollout} for specific name
@@ -64,4 +65,28 @@ public interface RolloutRepository
@Transactional
@Query("DELETE FROM JpaRollout t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
/**
* Retrieves all {@link Rollout}s for a specific {@link DistributionSet} in
* a given {@link RolloutStatus}.
*
* @param set
* the distribution set
* @param status
* the status of the rollout
* @return {@link Rollout} for specific distribution set
*/
List<Rollout> findByDistributionSetAndStatusIn(DistributionSet set, Collection<RolloutStatus> status);
/**
* Counts all {@link Rollout}s for a specific {@link DistributionSet} in a
* given {@link RolloutStatus}.
*
* @param distributionSetId
* the distribution set
* @param status
* the status of the rollout
* @return the count
*/
long countByDistributionSetIdAndStatusIn(long distributionSetId, Collection<RolloutStatus> status);
}

View File

@@ -32,7 +32,7 @@ public interface TargetFilterQueryRepository
/**
* Find customer target filter by name
*
*
* @param name
* @return custom target filter
*/
@@ -56,6 +56,16 @@ public interface TargetFilterQueryRepository
@Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL, d.autoAssignActionType = NULL where d.autoAssignDistributionSet in :ids")
void unsetAutoAssignDistributionSetAndActionType(@Param("ids") Long... dsIds);
/**
* Counts all target filters that have a given auto assign distribution set
* assigned.
*
* @param autoAssignDistributionSetId
* the id of the distribution set
* @return the count
*/
long countByAutoAssignDistributionSetId(long autoAssignDistributionSetId);
/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety
* reasons (this is a "delete everything" query after all) we add the tenant

View File

@@ -11,9 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.AbstractRolloutUpdateCreate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.DistributionSet;
public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate> implements RolloutCreate {
private final DistributionSetManagement distributionSetManagement;
@@ -28,7 +26,7 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
rollout.setName(name);
rollout.setDescription(description);
rollout.setDistributionSet(findDistributionSetAndThrowExceptionIfNotFound(set));
rollout.setDistributionSet(distributionSetManagement.getValidAndComplete(set));
rollout.setTargetFilterQuery(targetFilterQuery);
rollout.setStartAt(startAt);
rollout.setWeight(weight);
@@ -43,9 +41,4 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
return rollout;
}
private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
}

View File

@@ -11,11 +11,9 @@ package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.AbstractTargetFilterQueryUpdateCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
/**
@@ -35,16 +33,11 @@ public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateC
public JpaTargetFilterQuery build() {
return new JpaTargetFilterQuery(name, query,
getAutoAssignDistributionSetId().map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null),
getAutoAssignDistributionSetId().map(distributionSetManagement::getValidAndComplete).orElse(null),
getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null),
weight);
}
private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
private static boolean isAutoAssignActionTypeValid(final ActionType actionType) {
if (!TargetFilterQuery.ALLOWED_AUTO_ASSIGN_ACTION_TYPES.contains(actionType)) {
throw new InvalidAutoAssignActionTypeException();

View File

@@ -116,6 +116,9 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
@Column(name = "complete")
private boolean complete;
@Column(name = "valid")
private boolean valid;
/**
* Default constructor.
*/
@@ -145,6 +148,7 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
super(name, version, description);
this.requiredMigrationStep = requiredMigrationStep;
this.valid = true;
this.type = type;
if (moduleList != null) {
moduleList.forEach(this::addModule);
@@ -327,6 +331,15 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
return complete;
}
@Override
public boolean isValid() {
return valid;
}
public void invalidate() {
this.valid = false;
}
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
publishEventWithEventPublisher(

View File

@@ -93,7 +93,8 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10"),
@ConversionValue(objectValue = "WAITING_FOR_APPROVAL", dataValue = "11"),
@ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12") })
@ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12"),
@ConversionValue(objectValue = "STOPPING", dataValue = "13") })
@Convert("rolloutstatus")
@NotNull
private RolloutStatus status = RolloutStatus.CREATING;

View File

@@ -45,7 +45,7 @@ public final class DistributionSetSpecification {
/**
* {@link Specification} for retrieving {@link DistributionSet}s by its
* DELETED attribute.
*
*
* @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored
@@ -59,7 +59,7 @@ public final class DistributionSetSpecification {
/**
* {@link Specification} for retrieving {@link DistributionSet}s by its
* COMPLETED attribute.
*
*
* @param isCompleted
* TRUE/FALSE are compared to the attribute COMPLETED. If NULL
* the attribute is ignored
@@ -70,6 +70,20 @@ public final class DistributionSetSpecification {
}
/**
* {@link Specification} for retrieving {@link DistributionSet}s by its
* VALID attribute.
*
* @param isValid
* TRUE/FALSE are compared to the attribute VALID. If NULL the
* attribute is ignored
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> isValid(final Boolean isValid) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> get(JpaDistributionSet_.valid), isValid);
}
/**
* {@link Specification} for retrieving {@link DistributionSet} with given
* {@link DistributionSet#getId()}.
@@ -111,7 +125,7 @@ public final class DistributionSetSpecification {
/**
* {@link Specification} for retrieving {@link DistributionSet}s by "like
* name or like description or like version".
*
*
* @param subString
* to be filtered on
* @return the {@link DistributionSet} {@link Specification}
@@ -126,7 +140,7 @@ public final class DistributionSetSpecification {
/**
* {@link Specification} for retrieving {@link DistributionSet}s by "like
* name and like version".
*
*
* @param name
* to be filtered on
* @param version
@@ -142,7 +156,7 @@ public final class DistributionSetSpecification {
/**
* {@link Specification} for retrieving {@link DistributionSet}s by "has at
* least one of the given tag names".
*
*
* @param tagNames
* to be filtered on
* @param selectDSWithNoTag
@@ -187,7 +201,7 @@ public final class DistributionSetSpecification {
/**
* returns query criteria {@link Specification} comparing case insensitive
* "NAME == AND VERSION ==".
*
*
* @param name
* to be filtered on
* @param version

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;