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;

View File

@@ -8,10 +8,13 @@
*/
package org.eclipse.hawkbit.repository.event.remote.entity;
import java.util.Collections;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.junit.jupiter.api.Test;
import io.qameta.allure.Description;
@@ -34,8 +37,11 @@ public class RolloutEventTest extends AbstractRemoteEntityEventTest<Rollout> {
@Override
protected Rollout createEntity() {
testdataFactory.createTarget("12345");
final DistributionSet ds = distributionSetManagement.create(entityFactory.distributionSet()
.create().name("incomplete").version("2").description("incomplete").type("os"));
final SoftwareModule module = softwareModuleManagement.create(
entityFactory.softwareModule().create().name("swm").version("2").description("desc").type("os"));
final DistributionSet ds = distributionSetManagement
.create(entityFactory.distributionSet().create().name("complete").version("2").description("complete")
.type("os").modules(Collections.singletonList(module.getId())));
return rolloutManagement.create(
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds),

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.UUID;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -17,6 +18,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.junit.jupiter.api.Test;
import io.qameta.allure.Description;
@@ -75,9 +77,11 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<Rollout
@Override
protected RolloutGroup createEntity() {
testdataFactory.createTarget(UUID.randomUUID().toString());
final DistributionSet ds = distributionSetManagement.create(entityFactory.distributionSet().create()
.name("incomplete").version("2").description("incomplete").type("os"));
final SoftwareModule module = softwareModuleManagement.create(
entityFactory.softwareModule().create().name("swm").version("2").description("desc").type("os"));
final DistributionSet ds = distributionSetManagement
.create(entityFactory.distributionSet().create().name("complete").version("2").description("complete")
.type("os").modules(Collections.singletonList(module.getId())));
final Rollout entity = rolloutManagement.create(
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds),

View File

@@ -0,0 +1,79 @@
/**
* 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 static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collections;
import org.awaitility.Awaitility;
import org.eclipse.hawkbit.repository.exception.StopRolloutException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
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.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.TestPropertySource;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
/**
* Test class testing the invalidation of a {@link DistributionSet} while the
* handle rollouts is ongoing.
*
*/
@Feature("Component Tests - Repository")
@Story("Concurrent Distribution Set invalidation")
@TestPropertySource(properties = "hawkbit.server.repository.dsInvalidationLockTimeout=1")
public class ConcurrentDistributionSetInvalidationTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verify that a large rollout causes a timeout when trying to invalidate a distribution set")
public void verifyInvalidateDistributionSetWithLargeRolloutThrowsException() throws Exception {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTargets(10000, "verifyInvalidateDistributionSetWithLargeRolloutThrowsException");
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "50")
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, "80")
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
final Rollout rollout = rolloutManagement.create(entityFactory.rollout().create()
.name("verifyInvalidateDistributionSetWithLargeRolloutThrowsException").description("desc")
.targetFilterQuery("name==*").set(distributionSet).actionType(ActionType.FORCED), 20, conditions);
final String tenant = tenantAware.getCurrentTenant();
// run in new Thread so that the invalidation can be executed in
// parallel
final Thread handleRolloutsThread = new Thread(() -> {
tenantAware.runAsTenant(tenant, () -> systemSecurityContext.runAsSystem(() -> {
rolloutManagement.handleRollouts();
return 0;
}));
});
handleRolloutsThread.start();
// wait until at least one RolloutGroup is created, as this means that
// the thread has started and has acquired the lock
Awaitility.await().until(() -> tenantAware.runAsTenant(tenant, () -> systemSecurityContext
.runAsSystem(() -> rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getSize() > 0)));
assertThatExceptionOfType(StopRolloutException.class)
.as("Invalidation of distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true)));
}
}

View File

@@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException
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.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -844,8 +845,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
/**
* test a simple deployment by calling the
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} and
* checking the active action and the action history of the targets.
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
* and checking the active action and the action history of the targets.
*
*/
@Test
@@ -1088,9 +1089,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
}
/**
* test the deletion of {@link DistributionSet}s including exception in case of
* {@link Target}s are assigned by {@link Target#getAssignedDistributionSet()}
* or {@link Target#getInstalledDistributionSet()}
* test the deletion of {@link DistributionSet}s including exception in case
* of {@link Target}s are assigned by
* {@link Target#getAssignedDistributionSet()} or
* {@link Target#getInstalledDistributionSet()}
*/
@Test
@Description("Deletes distribution set. Expected behaviour is that a soft delete is performed "
@@ -1357,12 +1359,36 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(distributionSetRepository.findAll()).hasSize(1);
}
@Test
@Description("Tests that an exception is thrown when a target is assigned to an incomplete distribution set")
public void verifyAssignTargetsToIncompleteDistribution() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
final Target target = testdataFactory.createTarget();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> assignDistributionSet(distributionSet, target));
}
@Test
@Description("Tests that an exception is thrown when a target is assigned to an invalidated distribution set")
public void verifyAssignTargetsToInvalidDistribution() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
final Target target = testdataFactory.createTarget();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> assignDistributionSet(distributionSet, target));
}
/**
* Helper methods that creates 2 lists of targets and a list of distribution
* sets.
* <p>
* <b>All created distribution sets are assigned to all targets of the target
* list deployedTargets.</b>
* <b>All created distribution sets are assigned to all targets of the
* target list deployedTargets.</b>
*
* @param undeployedTargetPrefix
* prefix to be used as target controller prefix
@@ -1371,7 +1397,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
* @param deployedTargetPrefix
* prefix to be used as target controller prefix
* @param noOfDeployedTargets
* number of targets to which the created distribution sets assigned
* number of targets to which the created distribution sets
* assigned
* @param noOfDistributionSets
* number of distribution sets
* @param distributionSetPrefix

View File

@@ -0,0 +1,304 @@
/**
* 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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collections;
import java.util.List;
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.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.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
/**
* Test class testing the functionality of invalidating a
* {@link DistributionSet}
*
*/
@Feature("Component Tests - Repository")
@Story("Distribution set invalidation management")
class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verify invalidation of distribution sets that only removes distribution sets from auto assignments")
void verifyInvalidateDistributionSetStopAutoAssignment() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopAutoAssignment");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 0);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutManagement.handleRollouts();
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isNotIn(RolloutStatus.STOPPING, RolloutStatus.FINISHED);
for (final Target target : invalidationTestData.getTargets()) {
// if status is pending, the assignment has not been canceled
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING);
}
}
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments and stops rollouts")
void verifyInvalidateDistributionSetStopRollouts() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopRollouts");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
true);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 1);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutManagement.handleRollouts();
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isEqualTo(RolloutStatus.FINISHED);
assertNoScheduledActionsExist(invalidationTestData.getRollout());
assertRolloutGroupsAreFinished(invalidationTestData.getRollout());
for (final Target target : invalidationTestData.getTargets()) {
// if status is pending, the assignment has not been canceled
assertThat(
targetRepository.findById(invalidationTestData.getTargets().get(0).getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING);
}
}
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and force cancels assignments")
void verifyInvalidateDistributionSetStopAllAndForceCancel() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopAllAndForceCancel");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.FORCE,
true);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 5, 1);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutManagement.handleRollouts();
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isEqualTo(RolloutStatus.FINISHED);
assertNoScheduledActionsExist(invalidationTestData.getRollout());
assertRolloutGroupsAreFinished(invalidationTestData.getRollout());
for (final Target target : invalidationTestData.getTargets()) {
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELED);
}
}
private void assertNoScheduledActionsExist(final Rollout rollout) {
assertThat(
actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Status.SCHEDULED).getTotalElements())
.isZero();
}
private void assertRolloutGroupsAreFinished(final Rollout rollout) {
assertThat(rolloutGroupRepository.findByRolloutId(rollout.getId(), PAGE))
.allMatch(rolloutGroup -> rolloutGroup.getStatus().equals(RolloutGroupStatus.FINISHED));
}
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and cancels assignments")
void verifyInvalidateDistributionSetStopAll() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopAll");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT,
true);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 5, 1);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isIn(RolloutStatus.STOPPING, RolloutStatus.FINISHED);
for (final Target target : invalidationTestData.getTargets()) {
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELING);
}
}
@Test
@Description("Verify that invalidating an incomplete distribution set throws an exception")
void verifyInvalidateIncompleteDistributionSetThrowsException() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true)));
}
@Test
@Description("Verify that invalidating an invalidated distribution set throws an exception")
void verifyInvalidateInvalidatedDistributionSetThrowsException() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true)));
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY and UPDATE_REPOSITORY is not allowed to invalidate a distribution set")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY" })
void verifyInvalidateWithReadAndUpdateRepoAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext
.runAsSystem(() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAuthority"));
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Insufficient permission exception expected")
.isThrownBy(() -> distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()),
CancelationType.NONE, false)));
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY, UPDATE_REPOSITORY and UPDATE_TARGET is allowed to invalidate a distribution set only without canceling rollouts")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY", "UPDATE_TARGET" })
void verifyInvalidateWithReadAndUpdateRepoAndUpdateTargetAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext.runAsSystem(
() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAndUpdateTargetAuthority"));
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Insufficient permission exception expected")
.isThrownBy(() -> distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()),
CancelationType.SOFT, true)));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY, UPDATE_REPOSITORY, UPDATE_ROLLOUT and UPDATE_TARGET is allowed to invalidate a distribution")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY", "UPDATE_TARGET", "UPDATE_ROLLOUT" })
void verifyInvalidateWithReadAndUpdateRepoAndUpdateTargetAndUpdateRolloutAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext.runAsSystem(
() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAndUpdateTargetAuthority"));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT,
true));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
}
private InvalidationTestData createInvalidationTestData(final String testName) {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(5, testName);
assignDistributionSet(distributionSet, targets);
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(entityFactory.targetFilterQuery()
.create().name(testName).query("name==*").autoAssignDistributionSet(distributionSet));
final Rollout rollout = testdataFactory.createRolloutByVariables(testName, "desc", 2, "name==*",
distributionSet, "50", "80");
return new InvalidationTestData(distributionSet, targets, targetFilterQuery, rollout);
}
private static class InvalidationTestData {
private final DistributionSet distributionSet;
private final List<Target> targets;
private final TargetFilterQuery targetFilterQuery;
private final Rollout rollout;
public InvalidationTestData(final DistributionSet distributionSet, final List<Target> targets,
final TargetFilterQuery targetFilterQuery, final Rollout rollout) {
super();
this.distributionSet = distributionSet;
this.targets = targets;
this.targetFilterQuery = targetFilterQuery;
this.rollout = rollout;
}
public DistributionSet getDistributionSet() {
return distributionSet;
}
public List<Target> getTargets() {
return targets;
}
public TargetFilterQuery getTargetFilterQuery() {
return targetFilterQuery;
}
public Rollout getRollout() {
return rollout;
}
}
private void assertDistributionSetInvalidationCount(
final DistributionSetInvalidationCount distributionSetInvalidationCount,
final long expectedAutoAssignmentCount, final long expectedActionCount, final long expectedRolloutCount) {
assertThat(distributionSetInvalidationCount.getAutoAssignmentCount()).isEqualTo(expectedAutoAssignmentCount);
assertThat(distributionSetInvalidationCount.getActionCount()).isEqualTo(expectedActionCount);
assertThat(distributionSetInvalidationCount.getRolloutsCount()).isEqualTo(expectedRolloutCount);
}
}

View File

@@ -31,16 +31,20 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreated
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
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.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -174,6 +178,13 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(set.getId(),
entityFactory.generateDsMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata");
verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.getValidAndComplete(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.getValid(NOT_EXIST_IDL), "DistributionSet");
}
@Test
@@ -493,6 +504,16 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(ds.isRequiredMigrationStep()).isTrue();
}
@Test
@Description("Verifies that an exception is thrown when trying to update an invalid distribution set")
public void updateInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
.update(entityFactory.distributionSet().update(distributionSet.getId()).name("new_name")));
}
@Test
@Description("Verifies the enforcement of the software module quota per distribution set.")
public void assignSoftwareModulesUntilQuotaIsExceeded() {
@@ -533,6 +554,33 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verifies that an exception is thrown when trying to assign software modules to an invalidated distribution set.")
public void verifyAssignSoftwareModulesToInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleOs();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.assignSoftwareModules(distributionSet.getId(),
Collections.singletonList(softwareModule.getId())));
}
@Test
@Description("Verifies that an exception is thrown when trying to unassign a software module from an invalidated distribution set.")
public void verifyUnassignSoftwareModulesToInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleOs();
distributionSetManagement.assignSoftwareModules(distributionSet.getId(),
Collections.singletonList(softwareModule.getId()));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(distributionSet.getId()), CancelationType.NONE, false));
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
.unassignSoftwareModule(distributionSet.getId(), softwareModule.getId()));
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that metadata for a distribution set can be updated.")
@@ -937,6 +985,24 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(distributionSetManagement.findByCompleted(PAGE, true).getTotalElements()).isEqualTo(1);
}
@Test
@Description("Deletes an invalid distribution set")
public void deleteInvalidDistributionSet() {
final DistributionSet set = testdataFactory.createAndInvalidateDistributionSet();
assertThat(distributionSetRepository.findById(set.getId())).isNotEmpty();
distributionSetManagement.delete(set.getId());
assertThat(distributionSetRepository.findById(set.getId())).isEmpty();
}
@Test
@Description("Deletes an incomplete distribution set")
public void deleteIncompleteDistributionSet() {
final DistributionSet set = testdataFactory.createIncompleteDistributionSet();
assertThat(distributionSetRepository.findById(set.getId())).isNotEmpty();
distributionSetManagement.delete(set.getId());
assertThat(distributionSetRepository.findById(set.getId())).isEmpty();
}
@Test
@Description("Queries and loads the metadata related to a given software module.")
public void findAllDistributionSetMetadataByDsId() {
@@ -1013,4 +1079,54 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(collect).containsAll(searchIds);
}
@Test
@Description("Verify that an exception is thrown when trying to get an invalid distribution set")
public void verifyGetValid() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.getValid(distributionSet.getId()));
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSet.getId()));
}
@Test
@Description("Verify that an exception is thrown when trying to get an incomplete distribution set")
public void verifyGetValidAndComplete() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSet.getId()));
}
@Test
@Description("Verify that an exception is thrown when trying to create or update metadata for an invalid distribution set.")
public void createMetadataForInvalidDistributionSet() {
final String knownKey1 = "myKnownKey1";
final String knownKey2 = "myKnownKey2";
final String knownValue = "myKnownValue";
final String knownUpdateValue = "knownUpdateValue";
final DistributionSet ds = testdataFactory.createDistributionSet();
distributionSetManagement.createMetaData(ds.getId(),
Collections.singletonList(entityFactory.generateDsMetadata(knownKey1, knownValue)));
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Arrays.asList(ds.getId()), CancelationType.NONE, false));
// assert that no new metadata can be created
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.createMetaData(ds.getId(),
Collections.singletonList(entityFactory.generateDsMetadata(knownKey2, knownValue))));
// assert that an existing metadata can not be updated
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
.updateMetaData(ds.getId(), entityFactory.generateDsMetadata(knownKey1, knownUpdateValue)));
}
}

View File

@@ -46,6 +46,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
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.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -72,7 +74,6 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
@@ -243,8 +244,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(firstGroup.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
// verify other groups are scheduled
final List<RolloutGroup> scheduledGroups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId()).getContent();
final List<RolloutGroup> scheduledGroups = rolloutGroupManagement
.findByRollout(new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.getContent();
scheduledGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)
.as("group which should be in scheduled state is in " + group.getStatus() + " state"));
// verify that the first group actions has been started and are in state
@@ -436,8 +438,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(errorGroup.get(0).getStatus()).isEqualTo(RolloutGroupStatus.ERROR);
// all other groups should still be in scheduled state
final List<RolloutGroup> scheduleGroups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId()).getContent();
final List<RolloutGroup> scheduleGroups = rolloutGroupManagement
.findByRollout(new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.getContent();
scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED));
}
@@ -470,8 +473,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED);
// all other groups should still be in scheduled state
final List<RolloutGroup> scheduleGroups = rolloutGroupManagement.findByRollout(
new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId()).getContent();
final List<RolloutGroup> scheduleGroups = rolloutGroupManagement
.findByRollout(new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.getContent();
scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED));
// resume the rollout again after it gets paused by error action
@@ -521,8 +525,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// verify all groups are in finished state
rolloutGroupManagement
.findByRollout(new OffsetBasedPageRequest(0, 100, Sort.by(Direction.ASC, "id")),
createdRollout.getId())
.findByRollout(new OffsetBasedPageRequest(0, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.FINISHED));
// verify that rollout itself is in finished state
@@ -1823,6 +1826,56 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(actions).allMatch(action -> !action.getWeight().isPresent());
}
@Test
@Description("Verifies that an exception is thrown when trying to create a rollout with an invalidated distribution set.")
public void createRolloutWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> testdataFactory.createRolloutByVariables("createRolloutWithInvalidDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80"));
}
@Test
@Description("Verifies that an exception is thrown when trying to create a rollout with an incomplete distribution set.")
public void createRolloutWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> testdataFactory.createRolloutByVariables("createRolloutWithIncompleteDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80"));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a rollout with an invalidated distribution set.")
public void updateRolloutWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTarget();
final Rollout rollout = testdataFactory.createRolloutByVariables("updateRolloutWithInvalidDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80");
final DistributionSet invalidDistributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> rolloutManagement
.update(entityFactory.rollout().update(rollout.getId()).set(invalidDistributionSet.getId())));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a rollout with an incomplete distribution set.")
public void updateRolloutWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTarget();
final Rollout rollout = testdataFactory.createRolloutByVariables("updateRolloutWithIncompleteDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80");
final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception").isThrownBy(() -> rolloutManagement.update(
entityFactory.rollout().update(rollout.getId()).set(incompleteDistributionSet.getId())));
}
private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage,
final String targetFilter) {
return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc")

View File

@@ -23,14 +23,17 @@ import javax.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action;
@@ -51,7 +54,7 @@ import io.qameta.allure.Story;
/**
* Test class for {@link TargetFilterQueryManagement}.
*
*
*/
@Feature("Component Tests - Repository")
@Story("Target Filter Query Management")
@@ -252,7 +255,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
.create(entityFactory.distributionSet().create().name("incomplete").version("1")
.type(testdataFactory.findOrCreateDefaultTestDsType()));
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class)
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(incompleteDistributionSet.getId())));
}
@@ -263,7 +266,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs"));
distributionSetManagement.delete(softDeletedDs.getId());
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class)
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(softDeletedDs.getId())));
}
@@ -502,4 +505,60 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
assertThat(targetFilterQueryManagement.get(filterId).get().getAutoAssignWeight().get())
.isEqualTo(Action.WEIGHT_MIN);
}
@Test
@Description("Verifies that an exception is thrown when trying to create a target filter with an invalidated distribution set.")
public void createTargetFilterWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("createTargetFilterWithInvalidDistributionSet")
.query("name==*").autoAssignDistributionSet(distributionSet)));
}
@Test
@Description("Verifies that an exception is thrown when trying to create a target filter with an incomplete distribution set.")
public void createTargetFilterWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("createTargetFilterWithIncompleteDistributionSet").query("name==*")
.autoAssignDistributionSet(distributionSet)));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a target filter with an invalidated distribution set.")
public void updateAutoAssignDsWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("updateAutoAssignDsWithInvalidDistributionSet")
.query("name==*").autoAssignDistributionSet(distributionSet));
final DistributionSet invalidDistributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
.ds(invalidDistributionSet.getId())));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a target filter with an incomplete distribution set.")
public void updateAutoAssignDsWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("updateAutoAssignDsWithIncompleteDistributionSet")
.query("name==*").autoAssignDistributionSet(distributionSet));
final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
.ds(incompleteDistributionSet.getId())));
}
}

View File

@@ -15,7 +15,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
import org.eclipse.hawkbit.repository.model.Action;
@@ -105,11 +105,11 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final DistributionSet setB = testdataFactory.createDistributionSet("dsB");
// target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId())
.ds(setA.getId()));
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(entityFactory
.targetFilterQuery()
.updateAutoAssign(targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId())
.ds(setA.getId()));
final String targetDsAIdPref = "targ";
final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref,
@@ -159,7 +159,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches first bunch of targets, that should
// fail
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy(() -> {
assertThatExceptionOfType(IncompleteDistributionSetException.class).isThrownBy(() -> {
final Long filterId = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*"))
.getId();
@@ -221,10 +221,10 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet, final List<Target> targets) {
final Set<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet());
actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId())
.stream().filter(a -> targetIds.contains(a.getTarget().getControllerId()))
.forEach(a -> assertThat(a.getInitiatedBy()).as(
"Action should be initiated by the user who initiated the auto assignment")
actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()).stream()
.filter(a -> targetIds.contains(a.getTarget().getControllerId()))
.forEach(a -> assertThat(a.getInitiatedBy())
.as("Action should be initiated by the user who initiated the auto assignment")
.isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy()));
}