Rollouts can be deleted (#436)

* Management UI

Signed-off-by: Melanie Retter <melanie.retter@bosch-si.com>

* Repository

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* Optimisations and scheduler deleting enabled

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Melanie Retter
2017-02-18 07:19:28 +01:00
committed by Kai Zimmermann
parent 804522f966
commit 5628d625e8
159 changed files with 3029 additions and 1737 deletions

View File

@@ -195,6 +195,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* the status which the actions should not have
* @return the found list of {@link Action}s
*/
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
@Query("SELECT a FROM JpaAction a WHERE a.active = true AND a.distributionSet.requiredMigrationStep = false AND a.target IN ?1 AND a.status != ?2")
List<JpaAction> findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(
Collection<Long> targetIds, Action.Status notStatus);
@@ -264,6 +265,44 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*/
Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status);
/**
* Counts all actions referring to a given rollout and status.
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @param status
* the status the actions should have
* @return the count of actions referring to a rollout and are in a given
* status
*/
Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status);
/**
* 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
* {@code false}
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId")
boolean existsByRolloutId(@Param("rolloutId") Long rolloutId);
/**
* 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
* the action is not to be in
* @return {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status")
boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status);
/**
* Retrieving all actions referring to a given rollout with a specific
* action as parent reference and a specific status.
@@ -281,6 +320,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
Page<Action> findByRolloutAndRolloutGroupParentAndStatus(Pageable pageable, JpaRollout rollout,
JpaRolloutGroup rolloutGroupParent, Status actionStatus);
@@ -296,19 +336,22 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
Page<Action> findByRolloutAndRolloutGroupParentIsNullAndStatus(Pageable pageable, JpaRollout rollout,
Status actionStatus);
/**
* Retrieves all actions for a specific rollout and in a specific status.
*
*
* @param pageable
* page parameters
* @param rolloutId
* the rollout the actions beglong to
* @param actionStatus
* the status of the actions
* @return the actions referring a specific rollout an in a specific status
*/
List<Action> findByRolloutIdAndStatus(Long rolloutId, Status actionStatus);
Page<JpaAction> findByRolloutIdAndStatus(Pageable pageable, Long rolloutId, Status actionStatus);
/**
* Get list of objects which has details of status and count of targets in
@@ -354,4 +397,15 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId);
/**
* Deletes all actions with the given IDs.
*
* @param actionIDs
* the IDs of the actions to be deleted.
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM JpaAction a WHERE a.id IN ?1")
void deleteByIdIn(final Collection<Long> actionIDs);
}

View File

@@ -12,7 +12,6 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
@@ -72,11 +71,11 @@ public interface DistributionSetRepository
* Finds {@link DistributionSet}s where given {@link SoftwareModule} is
* assigned.
*
* @param module
* @param moduleId
* to search for
* @return {@link List} of found {@link DistributionSet}s
*/
Long countByModules(JpaSoftwareModule module);
Long countByModulesId(Long moduleId);
/**
* Finds {@link DistributionSet}s based on given ID that are assigned yet to

View File

@@ -17,7 +17,6 @@ import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
@@ -104,23 +103,16 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public boolean clearArtifactBinary(final Long artifactId) {
return clearArtifactBinary(localArtifactRepository.findById(artifactId)
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, artifactId)));
}
public boolean clearArtifactBinary(final String sha1Hash, final Long moduleId) {
private boolean clearArtifactBinary(final JpaArtifact existing) {
for (final Artifact lArtifact : localArtifactRepository.findBySha1Hash(existing.getSha1Hash())) {
if (!lArtifact.getSoftwareModule().isDeleted()
&& Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) {
return false;
}
if (localArtifactRepository.existsWithSha1HashAndSoftwareModuleIdIsNot(sha1Hash, moduleId)) {
// there are still other artifacts that need the binary
return false;
}
try {
LOG.debug("deleting artifact from repository {}", existing.getSha1Hash());
artifactRepository.deleteBySha1(existing.getSha1Hash());
LOG.debug("deleting artifact from repository {}", sha1Hash);
artifactRepository.deleteBySha1(sha1Hash);
return true;
} catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e);
@@ -134,7 +126,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
final JpaArtifact existing = (JpaArtifact) findArtifact(id)
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, id));
clearArtifactBinary(existing);
clearArtifactBinary(existing.getSha1Hash(), existing.getSoftwareModule().getId());
((JpaSoftwareModule) existing.getSoftwareModule()).removeArtifact(existing);
softwareModuleRepository.save((JpaSoftwareModule) existing.getSoftwareModule());
@@ -148,6 +140,8 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
public Optional<Artifact> findByFilenameAndSoftwareModule(final String filename, final Long softwareModuleId) {
throwExceptionIfSoftwareModuleDoesNotExist(softwareModuleId);
return localArtifactRepository.findFirstByFilenameAndSoftwareModuleId(filename, softwareModuleId);
}
@@ -163,13 +157,20 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
public Page<Artifact> findArtifactBySoftwareModule(final Pageable pageReq, final Long swId) {
throwExceptionIfSoftwareModuleDoesNotExist(swId);
return localArtifactRepository.findBySoftwareModuleId(pageReq, swId);
}
private void throwExceptionIfSoftwareModuleDoesNotExist(final Long swId) {
if (!softwareModuleRepository.exists(swId)) {
throw new EntityNotFoundException(SoftwareModule.class, swId);
}
}
@Override
public DbArtifact loadArtifactBinary(final String sha1Hash) {
return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash))
.orElseThrow(() -> new ArtifactBinaryNotFoundException(sha1Hash));
public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash) {
return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash));
}
private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,

View File

@@ -46,6 +46,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -85,6 +86,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired
private TargetRepository targetRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TargetManagement targetManagement;
@@ -140,6 +144,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Override
public Optional<Action> getActionForDownloadByTargetAndSoftwareModule(final String controllerId,
final Long moduleId) {
throwExceptionIfTargetDoesNotExist(controllerId);
throwExceptionIfSoftwareModuleDoesNotExist(moduleId);
final List<Action> action = actionRepository.findActionByTargetAndSoftwareModule(controllerId, moduleId);
if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) {
@@ -149,26 +156,40 @@ public class JpaControllerManagement implements ControllerManagement {
return Optional.ofNullable(action.get(0));
}
private void throwExceptionIfTargetDoesNotExist(final String controllerId) {
if (!targetRepository.existsByControllerId(controllerId)) {
throw new EntityNotFoundException(Target.class, controllerId);
}
}
private void throwExceptionIfTargetDoesNotExist(final Long targetId) {
if (!targetRepository.exists(targetId)) {
throw new EntityNotFoundException(Target.class, targetId);
}
}
private void throwExceptionIfSoftwareModuleDoesNotExist(final Long moduleId) {
if (!softwareModuleRepository.exists(moduleId)) {
throw new EntityNotFoundException(SoftwareModule.class, moduleId);
}
}
@Override
public boolean hasTargetArtifactAssigned(final String controllerId, final String sha1Hash) {
final Optional<Target> target = targetRepository.findByControllerId(controllerId);
if (!target.isPresent()) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target.get(), sha1Hash)) > 0;
throwExceptionIfTargetDoesNotExist(controllerId);
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(controllerId, sha1Hash)) > 0;
}
@Override
public boolean hasTargetArtifactAssigned(final Long targetId, final String sha1Hash) {
final Target target = targetRepository.findOne(targetId);
if (target == null) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, sha1Hash)) > 0;
throwExceptionIfTargetDoesNotExist(targetId);
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(targetId, sha1Hash)) > 0;
}
@Override
public Optional<Action> findOldestActiveActionByTarget(final String controllerId) {
throwExceptionIfTargetDoesNotExist(controllerId);
// used in favorite to findFirstByTargetAndActiveOrderByIdAsc due to
// DATAJPA-841 issue.
return actionRepository.findFirstByTargetControllerIdAndActive(new Sort(Direction.ASC, "id"), controllerId,

View File

@@ -225,7 +225,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final List<JpaTarget> targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
.map(ids -> targetRepository
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId())))
.flatMap(t -> t.stream()).collect(Collectors.toList());
.flatMap(List::stream).collect(Collectors.toList());
if (targets.isEmpty()) {
// detaching as it is not necessary to persist the set itself
@@ -456,6 +456,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final RolloutGroup rolloutGroupParent, final int limit) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("startScheduledActions");
def.setReadOnly(false);
def.setIsolationLevel(Isolation.READ_UNCOMMITTED.value());
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(status -> {
final Page<Action> rolloutGroupActions = findActionsByRolloutAndRolloutGroupParent(rollout,
@@ -599,24 +601,38 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Override
public List<Action> findActiveActionsByTarget(final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.findByActiveAndTarget(controllerId, true);
}
@Override
public List<Action> findInActiveActionsByTarget(final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.findByActiveAndTarget(controllerId, false);
}
@Override
public Long countActionsByTarget(final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.countByTargetControllerId(controllerId);
}
@Override
public Long countActionsByTarget(final String rsqlParam, final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.count(createSpecificationFor(controllerId, rsqlParam));
}
private void throwExceptionIfTargetFoesNotExist(final String controllerId) {
if (!targetRepository.existsByControllerId(controllerId)) {
throw new EntityNotFoundException(Target.class, controllerId);
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)

View File

@@ -38,6 +38,7 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
@@ -48,7 +49,6 @@ import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Isolation;
@@ -65,6 +65,9 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private ActionRepository actionRepository;
@@ -115,9 +118,19 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
@Override
public Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable pageable) {
if (!rolloutRepository.exists(rolloutId)) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
final Page<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, pageable);
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId())
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(RolloutGroup::getId)
.collect(Collectors.toList());
if (rolloutGroupIds.isEmpty()) {
// groups might already deleted, so return empty list.
return new PageImpl<>(Collections.emptyList());
}
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRolloutGroup(
rolloutGroupIds);
@@ -192,8 +205,9 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
}
@Override
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final PageRequest pageRequest,
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final Pageable pageRequest,
final Long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
@@ -218,11 +232,14 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
.setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1]))
.collect(Collectors.toList());
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
}
@Override
public Long countTargetsOfRolloutsGroup(@NotNull final Long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<RolloutTargetGroup> countQueryFrom = countQuery.from(RolloutTargetGroup.class);
@@ -231,4 +248,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return entityManager.createQuery(countQuery).getSingleResult();
}
private void throwExceptionIfRolloutGroupDoesNotExist(final Long rolloutGroupId) {
if (!rolloutGroupRepository.exists(rolloutGroupId)) {
throw new EntityNotFoundException(RolloutGroup.class, rolloutGroupId);
}
}
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2015 Bosch Software Innovations 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.Collections;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
/**
* A collection of static helper methods for the {@link JpaRolloutManagement}
*/
final class JpaRolloutHelper {
private JpaRolloutHelper() {
}
/**
* In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions.
*
* @param create
* group to check
* @param conditions
* default conditions and actions
*/
static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create,
final RolloutGroupConditions conditions) {
final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build();
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
/**
* Builds a {@link Specification} to search a rollout by name or
* description.
*
* @param searchText
* search string
* @param isDeleted
* <code>true</code> if deleted rollouts should be included in
* the search. Otherwise <code>false</code>
* @return criteria specification with a query for name or description of a
* rollout
*/
static Specification<JpaRollout> likeNameOrDescription(final String searchText, final boolean isDeleted) {
return (rolloutRoot, query, criteriaBuilder) -> {
final String searchTextToLower = searchText.toLowerCase();
return criteriaBuilder.and(criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
searchTextToLower)),
criteriaBuilder.equal(rolloutRoot.get(JpaRollout_.deleted), isDeleted));
};
}
static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
}
}

View File

@@ -8,22 +8,23 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.validation.ConstraintDeclarationException;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.AbstractRolloutManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
@@ -31,6 +32,7 @@ import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -43,6 +45,8 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -58,7 +62,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -71,87 +77,87 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
/**
* JPA implementation of {@link RolloutManagement}.
*/
@Validated
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class JpaRolloutManagement implements RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
public class JpaRolloutManagement extends AbstractRolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
/**
* Max amount of targets that are handled in one transaction.
*/
private static final int TRANSACTION_TARGETS = 1000;
private static final int TRANSACTION_TARGETS = 5_000;
/**
* Maximum amount of actions that are deleted in one transaction.
*/
private static final int TRANSACTION_ACTIONS = 5_000;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
@Autowired
private RolloutGroupManagement rolloutGroupManagement;
@Autowired
private DistributionSetManagement distributionSetManagement;
@Autowired
private ActionRepository actionRepository;
@Autowired
private ApplicationContext context;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private NoCountPagingRepository criteriaNoCountDao;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private VirtualPropertyReplacer virtualPropertyReplacer;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Override
public Page<Rollout> findAll(final Pageable pageable) {
return RolloutHelper.convertPage(rolloutRepository.findAll(pageable), pageable);
JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
eventPublisher, virtualPropertyReplacer, txManager, tenantAware, lockRegistry);
}
@Override
public Page<Rollout> findAllByPredicate(final String rsqlParam, final Pageable pageable) {
final Specification<JpaRollout> specification = RSQLUtility.parse(rsqlParam, RolloutFields.class,
virtualPropertyReplacer);
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) {
final Specification<JpaRollout> spec = RolloutSpecification.isDeleted(deleted);
return JpaRolloutHelper.convertPage(rolloutRepository.findAll(spec, pageable), pageable);
}
final Page<JpaRollout> findAll = rolloutRepository.findAll(specification, pageable);
return RolloutHelper.convertPage(findAll, pageable);
@Override
public Page<Rollout> findAllByPredicate(final String rsqlParam, final Pageable pageable, final boolean deleted) {
final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add(RSQLUtility.parse(rsqlParam, RolloutFields.class, virtualPropertyReplacer));
specList.add(RolloutSpecification.isDeleted(deleted));
return JpaRolloutHelper.convertPage(findByCriteriaAPI(pageable, specList), pageable);
}
/**
* Executes findAll with the given {@link Rollout} {@link Specification}s.
*/
private Page<JpaRollout> findByCriteriaAPI(final Pageable pageable,
final List<Specification<JpaRollout>> specList) {
if (specList == null || specList.isEmpty()) {
return rolloutRepository.findAll(pageable);
}
return rolloutRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable);
}
@Override
@@ -225,8 +231,7 @@ public class JpaRolloutManagement implements RolloutManagement {
group.setTargetPercentage(1.0F / (amountOfGroups - i) * 100);
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
}
savedRollout.setRolloutGroupsCreated(amountOfGroups);
@@ -240,7 +245,7 @@ public class JpaRolloutManagement implements RolloutManagement {
// Preparing the groups
final List<RolloutGroup> groups = groupList.stream()
.map(group -> RolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.collect(Collectors.toList());
groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions);
@@ -261,7 +266,7 @@ public class JpaRolloutManagement implements RolloutManagement {
if (srcGroup.getTargetFilterQuery() != null) {
group.setTargetFilterQuery(srcGroup.getTargetFilterQuery());
} else {
group.setTargetFilterQuery("");
group.setTargetFilterQuery(StringUtils.EMPTY);
}
group.setSuccessCondition(srcGroup.getSuccessCondition());
@@ -277,25 +282,20 @@ public class JpaRolloutManagement implements RolloutManagement {
group.setErrorActionExp(srcGroup.getErrorActionExp());
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
}
savedRollout.setRolloutGroupsCreated(groups.size());
return rolloutRepository.save(savedRollout);
}
private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group) {
afterCommit
.afterCommit(() -> eventPublisher.publishEvent(new RolloutGroupCreatedEvent(group, context.getId())));
private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
afterCommit.afterCommit(() -> eventPublisher
.publishEvent(new RolloutGroupCreatedEvent(group, rollout.getId(), context.getId())));
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void fillRolloutGroupsWithTargets(final Long rolloutId) {
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
private void handleCreateRollout(final JpaRollout rollout) {
LOGGER.debug("handleCreateRollout called for rollout {}", rollout.getId());
final List<RolloutGroup> rolloutGroups = RolloutHelper.getOrderedGroups(rollout);
int readyGroups = 0;
@@ -317,6 +317,7 @@ public class JpaRolloutManagement implements RolloutManagement {
// When all groups are ready the rollout status can be changed to be
// ready, too.
if (readyGroups == rolloutGroups.size()) {
LOGGER.debug("rollout {} creatin done. Switch to READY.", rollout.getId());
rollout.setStatus(RolloutStatus.READY);
rollout.setLastCheck(0);
rollout.setTotalTargets(totalTargets);
@@ -372,17 +373,10 @@ public class JpaRolloutManagement implements RolloutManagement {
}
}
private int runInNewCountingTransaction(final String transactionName, final TransactionCallback<Integer> action) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName(transactionName);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(action);
}
private Integer assignTargetsToGroupInNewTransaction(final Rollout rollout, final RolloutGroup group,
final String targetFilter, final long limit) {
return runInNewCountingTransaction("assignTargetsToRolloutGroup", status -> {
return runInNewTransaction("assignTargetsToRolloutGroup", status -> {
final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit));
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout,
RolloutGroupStatus.READY, group);
@@ -399,19 +393,6 @@ public class JpaRolloutManagement implements RolloutManagement {
targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target)));
}
private long calculateRemainingTargets(final List<RolloutGroup> groups, final String targetFilter,
final Long createdAt) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
final RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets);
return totalTargets - validation.getTargetsInGroups();
}
@Override
@Async
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
@@ -427,67 +408,12 @@ public class JpaRolloutManagement implements RolloutManagement {
groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets));
}
private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
final Map<String, Long> targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(), targetManagement::countTargetByTargetFilterQuery));
long unusedTargetsCount = 0;
for (int i = 0; i < groups.size(); i++) {
final RolloutGroup group = groups.get(i);
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(baseFilter, group);
RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage());
final long targetsInGroupFilter = targetFilterCounts.get(groupTargetFilter);
final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i,
targetFilterCounts);
final long realTargetsInGroup;
// Assume that targets which were not used in the previous groups
// are used in this group
if (overlappingTargets > 0 && unusedTargetsCount > 0) {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets + unusedTargetsCount;
unusedTargetsCount = 0;
} else {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
}
final long reducedTargetsInGroup = Math
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
groupTargetCounts.add(reducedTargetsInGroup);
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
}
return new RolloutGroupsValidation(totalTargets, groupTargetCounts);
}
private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List<RolloutGroup> groups,
final RolloutGroup group, final int groupIndex, final Map<String, Long> targetFilterCounts) {
// there can't be overlapping targets in the first group
if (groupIndex == 0) {
return 0;
}
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter,
previousGroups, group);
if (targetFilterCounts.containsKey(overlappingTargetsFilter)) {
return targetFilterCounts.get(overlappingTargetsFilter);
} else {
final long overlappingTargets = targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter);
targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets);
return overlappingTargets;
}
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout startRollout(final Long rolloutId) {
LOGGER.debug("startRollout called for rollout {}", rolloutId);
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
RolloutHelper.checkIfRolloutCanStarted(rollout, rollout);
rollout.setStatus(RolloutStatus.STARTING);
@@ -496,6 +422,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private void startFirstRolloutGroup(final Rollout rollout) {
LOGGER.debug("startFirstRolloutGroup called for rollout {}", rollout.getId());
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
@@ -516,7 +443,6 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private boolean ensureAllGroupsAreScheduled(final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
final List<JpaRolloutGroup> groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout,
@@ -566,7 +492,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private Integer createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) {
return runInNewCountingTransaction("createActionsForTargets", status -> {
return runInNewTransaction("createActionsForTargets", status -> {
final PageRequest pageRequest = new PageRequest(0, limit);
final Rollout rollout = rolloutRepository.findOne(rolloutId);
final RolloutGroup group = rolloutGroupRepository.findOne(groupId);
@@ -618,7 +544,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Modifying
public void pauseRollout(final Long rolloutId) {
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
if (rollout.getStatus() != RolloutStatus.RUNNING) {
if (!RolloutStatus.RUNNING.equals(rollout.getStatus())) {
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ rollout.getStatus().name().toLowerCase());
}
@@ -644,39 +570,27 @@ public class JpaRolloutManagement implements RolloutManagement {
rolloutRepository.save(rollout);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkRunningRollouts(final long delayBetweenChecks) {
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.RUNNING);
if (rolloutsToCheck.isEmpty()) {
return;
private void handleRunningRollout(final JpaRollout rollout) {
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.RUNNING);
if (rolloutGroupsRunning.isEmpty()) {
// no running rollouts, probably there was an error
// somewhere at the latest group. And the latest group has
// been switched from running into error state. So we need
// to find the latest group which
executeLatestRolloutGroup(rollout);
} else {
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size());
executeRolloutGroups(rollout, rolloutGroupsRunning);
}
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
for (final JpaRollout rollout : rolloutsToCheck) {
LOGGER.debug("Checking rollout {}", rollout);
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.RUNNING);
if (rolloutGroupsRunning.isEmpty()) {
// no running rollouts, probably there was an error
// somewhere at the latest group. And the latest group has
// been switched from running into error state. So we need
// to find the latest group which
executeLatestRolloutGroup(rollout);
} else {
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size());
executeRolloutGroups(rollout, rolloutGroupsRunning);
}
if (isRolloutComplete(rollout)) {
LOGGER.info("Rollout {} is finished, setting finished status", rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
}
if (isRolloutComplete(rollout)) {
LOGGER.info("Rollout {} is finished, setting FINISHED status", rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
}
}
@@ -741,7 +655,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private boolean isRolloutComplete(final JpaRollout rollout) {
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutIdAndStatusOrStatus(rollout.getId(),
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
return groupsActiveLeft == 0;
}
@@ -798,90 +712,170 @@ public class JpaRolloutManagement implements RolloutManagement {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkCreatingRollouts(final long delayBetweenChecks) {
final List<Long> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.CREATING)
.stream().map(Rollout::getId).collect(Collectors.toList());
if (rolloutsToCheck.isEmpty()) {
// No transaction, will be created per handled rollout
@Transactional(propagation = Propagation.NEVER)
public void handleRollouts() {
rolloutRepository
.findByStatusIn(Lists.newArrayList(RolloutStatus.CREATING, RolloutStatus.DELETING,
RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING))
.forEach(this::handleRollout);
}
private void handleRollout(final Long rolloutId) {
LOGGER.debug("handleRollout called for rollout {}", rolloutId);
final String tenant = tenantAware.getCurrentTenant();
final String handlerId = tenant + "-rollout-" + rolloutId;
final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) {
return;
}
LOGGER.info("Found {} creating rollouts to check", rolloutsToCheck.size());
try {
runInNewTransaction(handlerId, status -> executeFittingHandler(rolloutId));
} finally {
lock.unlock();
}
}
rolloutsToCheck.forEach(this::fillRolloutGroupsWithTargets);
private int executeFittingHandler(final Long rolloutId) {
final JpaRollout rollout = rolloutRepository.findOne(rolloutId);
switch (rollout.getStatus()) {
case CREATING:
handleCreateRollout(rollout);
break;
case DELETING:
handleDeleteRollout(rollout);
break;
case READY:
handleReadyRollout(rollout);
break;
case STARTING:
handleStartingRollout(rollout);
break;
case RUNNING:
handleRunningRollout(rollout);
break;
default:
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
break;
}
return 0;
}
private void handleStartingRollout(final Rollout rollout) {
LOGGER.debug("handleStartingRollout called for rollout {}", rollout.getId());
if (ensureAllGroupsAreScheduled(rollout)) {
startFirstRolloutGroup(rollout);
}
}
private void handleReadyRollout(final Rollout rollout) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
LOGGER.debug(
"handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING",
rollout.getId());
startRollout(rollout.getId());
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkStartingRollouts(final long delayBetweenChecks) {
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks,
RolloutStatus.STARTING);
if (rolloutsToCheck.isEmpty()) {
public void deleteRollout(final long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.findOne(rolloutId);
if (RolloutStatus.DELETING.equals(jpaRollout.getStatus())) {
return;
}
LOGGER.info("Found {} starting rollouts to check", rolloutsToCheck.size());
rolloutsToCheck.forEach(rollout -> {
if (ensureAllGroupsAreScheduled(rollout)) {
startFirstRolloutGroup(rollout);
}
});
jpaRollout.setStatus(RolloutStatus.DELETING);
rolloutRepository.save(jpaRollout);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkReadyRollouts(final long delayBetweenChecks) {
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.READY);
if (rolloutsToCheck.isEmpty()) {
private void handleDeleteRollout(final JpaRollout rollout) {
LOGGER.debug("handleDeleteRollout called for {}", rollout.getId());
// check if there are actions beyond schedule
boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(),
Status.SCHEDULED);
if (hardDeleteRolloutGroups) {
LOGGER.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId());
hardDeleteRollout(rollout);
return;
}
// 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
final boolean hasScheduledActionsLeft = findScheduledActionsByRollout(rollout).getNumberOfElements() > 0;
if (hasScheduledActionsLeft) {
return;
}
LOGGER.info("Found {} ready rollouts to check", rolloutsToCheck.size());
final long now = System.currentTimeMillis();
rolloutsToCheck.forEach(rollout -> {
if (rollout.getStartAt() != null && rollout.getStartAt() <= now) {
startRollout(rollout.getId());
}
});
}
private List<JpaRollout> getRolloutsToCheckForStatus(final long delayBetweenChecks, final RolloutStatus status) {
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, status);
if (updated == 0) {
// nothing to check, maybe another instance already checked in
// between
LOGGER.debug("No rollouts starting check necessary for current scheduled check {}, next check at {}",
lastCheck, lastCheck + delayBetweenChecks);
return Collections.emptyList();
// only hard delete the rollout if no actions are left for the rollout.
// In case actions are left, they are probably are running or were
// running before, so only soft delete.
hardDeleteRolloutGroups = !actionRepository.existsByRolloutId(rollout.getId());
if (hardDeleteRolloutGroups) {
hardDeleteRollout(rollout);
return;
}
return rolloutRepository.findByLastCheckAndStatus(lastCheck, status);
// set soft delete
rollout.setStatus(RolloutStatus.DELETED);
rollout.setDeleted(true);
rolloutRepository.save(rollout);
}
private void hardDeleteRollout(final JpaRollout rollout) {
rolloutRepository.delete(rollout);
}
private void deleteScheduledActions(final JpaRollout rollout, final Slice<JpaAction> scheduledActions) {
final boolean hasScheduledActions = scheduledActions.getNumberOfElements() > 0;
if (hasScheduledActions) {
try {
final Iterable<JpaAction> iterable = scheduledActions::iterator;
final List<Long> actionIds = StreamSupport.stream(iterable.spliterator(), false).map(Action::getId)
.collect(Collectors.toList());
actionRepository.deleteByIdIn(actionIds);
afterCommit.afterCommit(() -> eventPublisher.publishEvent(
new RolloutUpdatedEvent(rollout, EventPublisherHolder.getInstance().getApplicationId())));
} catch (final RuntimeException e) {
LOGGER.error("Exception during deletion of actions of rollout {}", rollout, e);
}
}
}
private Slice<JpaAction> findScheduledActionsByRollout(final JpaRollout rollout) {
return actionRepository.findByRolloutIdAndStatus(new PageRequest(0, TRANSACTION_ACTIONS), rollout.getId(),
Status.SCHEDULED);
}
@Override
public Long countRolloutsAll() {
return rolloutRepository.count();
return rolloutRepository.count(RolloutSpecification.isDeleted(false));
}
@Override
public Long countRolloutsAllByFilters(final String searchText) {
return rolloutRepository.count(RolloutHelper.likeNameOrDescription(searchText));
return rolloutRepository.count(JpaRolloutHelper.likeNameOrDescription(searchText, false));
}
@Override
public Slice<Rollout> findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText) {
final Specification<JpaRollout> specs = RolloutHelper.likeNameOrDescription(searchText);
final Slice<JpaRollout> findAll = criteriaNoCountDao.findAll(specs, pageable, JpaRollout.class);
public Slice<Rollout> findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText,
final boolean deleted) {
final Slice<JpaRollout> findAll = findByCriteriaAPI(pageable,
Lists.newArrayList(JpaRolloutHelper.likeNameOrDescription(searchText, deleted)));
setRolloutStatusDetails(findAll);
return RolloutHelper.convertPage(findAll, pageable);
return JpaRolloutHelper.convertPage(findAll, pageable);
}
@Override
@@ -917,10 +911,12 @@ public class JpaRolloutManagement implements RolloutManagement {
}
@Override
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable pageable) {
final Page<JpaRollout> rollouts = rolloutRepository.findAll(pageable);
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable pageable, final boolean deleted) {
Page<JpaRollout> rollouts;
final Specification<JpaRollout> spec = RolloutSpecification.isDeleted(deleted);
rollouts = rolloutRepository.findAll(spec, pageable);
setRolloutStatusDetails(rollouts);
return RolloutHelper.convertPage(rollouts, pageable);
return JpaRolloutHelper.convertPage(rollouts, pageable);
}
@Override
@@ -940,8 +936,12 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rolloutIds) {
final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutId(rolloutIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
if (!rolloutIds.isEmpty()) {
final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutId(rolloutIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
}
return null;
}
private void setRolloutStatusDetails(final Slice<JpaRollout> rollouts) {
@@ -949,10 +949,12 @@ public class JpaRolloutManagement implements RolloutManagement {
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
rolloutIds);
for (final Rollout rollout : rollouts) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus);
if (allStatesForRollout != null) {
rollouts.forEach(rollout -> {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
});
}
}

View File

@@ -209,8 +209,8 @@ public class JpaSoftwareManagement implements SoftwareManagement {
return softwareModuleRepository.findOneByNameAndVersionAndTypeId(name, version, typeId);
}
private boolean isUnassigned(final JpaSoftwareModule bsmMerged) {
return distributionSetRepository.countByModules(bsmMerged) <= 0;
private boolean isUnassigned(final Long moduleId) {
return distributionSetRepository.countByModulesId(moduleId) <= 0;
}
private Slice<JpaSoftwareModule> findSwModuleByCriteriaAPI(final Pageable pageable,
@@ -225,7 +225,7 @@ public class JpaSoftwareManagement implements SoftwareManagement {
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
for (final Artifact localArtifact : swModule.getArtifacts()) {
artifactManagement.clearArtifactBinary(localArtifact.getId());
artifactManagement.clearArtifactBinary(localArtifact.getSha1Hash(), swModule.getId());
}
}
@@ -246,12 +246,9 @@ public class JpaSoftwareManagement implements SoftwareManagement {
// delete binary data of artifacts
deleteGridFsArtifacts(swModule);
if (isUnassigned(swModule)) {
softwareModuleRepository.delete(swModule);
if (isUnassigned(swModule.getId())) {
softwareModuleRepository.delete(swModule.getId());
} else {
assignedModuleIds.add(swModule.getId());
}
});

View File

@@ -218,8 +218,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant);
targetRepository.deleteByTenantIgnoreCase(tenant);
targetFilterQueryRepository.deleteByTenantIgnoreCase(tenant);
actionRepository.deleteByTenantIgnoreCase(tenant);
rolloutGroupRepository.deleteByTenantIgnoreCase(tenant);
rolloutRepository.deleteByTenantIgnoreCase(tenant);
artifactRepository.deleteByTenantIgnoreCase(tenant);
targetTagRepository.deleteByTenantIgnoreCase(tenant);

View File

@@ -607,7 +607,7 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = create.build();
if (targetRepository.findByControllerId(target.getControllerId()).isPresent()) {
if (targetRepository.existsByControllerId(target.getControllerId())) {
throw new EntityAlreadyExistsException();
}

View File

@@ -13,9 +13,11 @@ import java.util.Optional;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -54,6 +56,21 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
*/
List<Artifact> findBySha1Hash(String sha1Hash);
/**
* Verifies if an artifact exists that has given hash and is still related
* to a {@link SoftwareModule} other than a given one and not
* {@link SoftwareModule#isDeleted()}.
*
* @param sha1
* to search for
* @param moduleId
* to ignore in relationship check
*
* @return <code>true</code> if such an artifact exists
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaArtifact a WHERE a.sha1Hash = :sha1 AND a.softwareModule.id != :moduleId AND a.softwareModule.deleted = 0")
boolean existsWithSha1HashAndSoftwareModuleIdIsNot(@Param("sha1") String sha1, @Param("moduleId") Long moduleId);
/**
* Searches for a {@link Artifact} based on given gridFsFileName.
*

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa;
import java.util.Map;
import java.util.concurrent.Executor;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@@ -23,7 +24,6 @@ import org.eclipse.hawkbit.repository.ReportManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutProperties;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TagManagement;
@@ -53,6 +53,7 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.jpa.rollout.RolloutScheduler;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -69,18 +70,23 @@ import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -101,7 +107,7 @@ import com.google.common.collect.Maps;
@EnableAspectJAutoProxy
@Configuration
@ComponentScan
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, RolloutProperties.class,
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class,
TenantConfigurationProperties.class })
@EnableScheduling
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
@@ -115,7 +121,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean
@ConditionalOnMissingBean
public RsqlValidationOracle rsqlValidationOracle() {
RsqlValidationOracle rsqlValidationOracle() {
return new RsqlParserValidationOracle();
}
@@ -127,7 +133,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return DistributionSetBuilder bean
*/
@Bean
public DistributionSetBuilder distributionSetBuilder(final DistributionSetManagement distributionSetManagement,
DistributionSetBuilder distributionSetBuilder(final DistributionSetManagement distributionSetManagement,
final SoftwareManagement softwareManagement) {
return new JpaDistributionSetBuilder(distributionSetManagement, softwareManagement);
}
@@ -140,7 +146,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return DistributionSetTypeBuilder bean
*/
@Bean
public DistributionSetTypeBuilder distributionSetTypeBuilder(final SoftwareManagement softwareManagement) {
DistributionSetTypeBuilder distributionSetTypeBuilder(final SoftwareManagement softwareManagement) {
return new JpaDistributionSetTypeBuilder(softwareManagement);
}
@@ -150,7 +156,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return SoftwareModuleBuilder bean
*/
@Bean
public SoftwareModuleBuilder softwareModuleBuilder(final SoftwareManagement softwareManagement) {
SoftwareModuleBuilder softwareModuleBuilder(final SoftwareManagement softwareManagement) {
return new JpaSoftwareModuleBuilder(softwareManagement);
}
@@ -160,7 +166,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return RolloutBuilder bean
*/
@Bean
public RolloutBuilder rolloutBuilder(final DistributionSetManagement distributionSetManagement) {
RolloutBuilder rolloutBuilder(final DistributionSetManagement distributionSetManagement) {
return new JpaRolloutBuilder(distributionSetManagement);
}
@@ -171,8 +177,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return TargetFilterQueryBuilder bean
*/
@Bean
public TargetFilterQueryBuilder targetFilterQueryBuilder(
final DistributionSetManagement distributionSetManagement) {
TargetFilterQueryBuilder targetFilterQueryBuilder(final DistributionSetManagement distributionSetManagement) {
return new JpaTargetFilterQueryBuilder(distributionSetManagement);
}
@@ -182,7 +187,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* e.g. JPA entities.
*/
@Bean
public SystemSecurityContextHolder systemSecurityContextHolder() {
SystemSecurityContextHolder systemSecurityContextHolder() {
return SystemSecurityContextHolder.getInstance();
}
@@ -192,7 +197,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* directly, e.g. JPA entities.
*/
@Bean
public TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
return TenantConfigurationManagementHolder.getInstance();
}
@@ -203,7 +208,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* entities.
*/
@Bean
public SystemManagementHolder systemManagementHolder() {
SystemManagementHolder systemManagementHolder() {
return SystemManagementHolder.getInstance();
}
@@ -214,7 +219,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* entities.
*/
@Bean
public TenantAwareHolder tenantAwareHolder() {
TenantAwareHolder tenantAwareHolder() {
return TenantAwareHolder.getInstance();
}
@@ -225,7 +230,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* injection
*/
@Bean
public SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
return SecurityTokenGeneratorHolder.getInstance();
}
@@ -233,7 +238,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return the singleton instance of the {@link EntityInterceptorHolder}
*/
@Bean
public EntityInterceptorHolder entityInterceptorHolder() {
EntityInterceptorHolder entityInterceptorHolder() {
return EntityInterceptorHolder.getInstance();
}
@@ -243,7 +248,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* {@link AfterTransactionCommitExecutorHolder}
*/
@Bean
public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() {
AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() {
return AfterTransactionCommitExecutorHolder.getInstance();
}
@@ -261,7 +266,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return {@link ExceptionMappingAspectHandler} aspect bean
*/
@Bean
public ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() {
ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() {
return new ExceptionMappingAspectHandler();
}
@@ -305,7 +310,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public SystemManagement systemManagement() {
SystemManagement systemManagement() {
return new JpaSystemManagement();
}
@@ -316,7 +321,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public ReportManagement reportManagement() {
ReportManagement reportManagement() {
return new JpaReportManagement();
}
@@ -327,7 +332,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public DistributionSetManagement distributionSetManagement() {
DistributionSetManagement distributionSetManagement() {
return new JpaDistributionSetManagement();
}
@@ -338,7 +343,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TenantStatsManagement tenantStatsManagement() {
TenantStatsManagement tenantStatsManagement() {
return new JpaTenantStatsManagement();
}
@@ -349,7 +354,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TenantConfigurationManagement tenantConfigurationManagement() {
TenantConfigurationManagement tenantConfigurationManagement() {
return new JpaTenantConfigurationManagement();
}
@@ -360,7 +365,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TargetManagement targetManagement() {
TargetManagement targetManagement() {
return new JpaTargetManagement();
}
@@ -378,7 +383,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TargetFilterQueryManagement targetFilterQueryManagement(
TargetFilterQueryManagement targetFilterQueryManagement(
final TargetFilterQueryRepository targetFilterQueryRepository,
final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement) {
@@ -393,7 +398,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TagManagement tagManagement() {
TagManagement tagManagement() {
return new JpaTagManagement();
}
@@ -404,19 +409,21 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public SoftwareManagement softwareManagement() {
SoftwareManagement softwareManagement() {
return new JpaSoftwareManagement();
}
/**
* {@link JpaRolloutManagement} bean.
*
* @return a new {@link RolloutManagement}
*/
@Bean
@ConditionalOnMissingBean
public RolloutManagement rolloutManagement() {
return new JpaRolloutManagement();
RolloutManagement rolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement,
distributionSetManagement, context, eventPublisher, virtualPropertyReplacer, txManager, tenantAware,
lockRegistry);
}
/**
@@ -426,7 +433,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public RolloutGroupManagement rolloutGroupManagement() {
RolloutGroupManagement rolloutGroupManagement() {
return new JpaRolloutGroupManagement();
}
@@ -437,7 +444,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public DeploymentManagement deploymentManagement() {
DeploymentManagement deploymentManagement() {
return new JpaDeploymentManagement();
}
@@ -448,7 +455,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public ControllerManagement controllerManagement() {
ControllerManagement controllerManagement() {
return new JpaControllerManagement();
}
@@ -460,7 +467,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean
@ConditionalOnMissingBean
public ArtifactManagement artifactManagement() {
ArtifactManagement artifactManagement() {
return new JpaArtifactManagement();
}
@@ -482,7 +489,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public EventEntityManagerHolder eventEntityManagerHolder() {
EventEntityManagerHolder eventEntityManagerHolder() {
return EventEntityManagerHolder.getInstance();
}
@@ -497,7 +504,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) {
EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) {
return new JpaEventEntityManager(aware, entityManager);
}
@@ -516,7 +523,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public AutoAssignChecker autoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
AutoAssignChecker autoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final PlatformTransactionManager transactionManager) {
return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement,
@@ -525,6 +532,9 @@ 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.
*
* @param tenantAware
* to run as specific tenant
@@ -534,14 +544,49 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* to run as system
* @param autoAssignChecker
* to run a check as tenant
* @param lockRegistry
* to lock the tenant for auto assigment
* @return a new {@link AutoAssignChecker}
*/
@Bean
@ConditionalOnMissingBean
// don't active the auto assign scheduler in test, otherwise it is hard to
// test
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.autoassign.scheduler", name = "enabled", matchIfMissing = true)
public AutoAssignScheduler autoAssignScheduler(final TenantAware tenantAware,
final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext,
final AutoAssignChecker autoAssignChecker) {
return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker);
AutoAssignScheduler autoAssignScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker,
final LockRegistry lockRegistry) {
return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker,
lockRegistry);
}
/**
* {@link RolloutScheduler} bean.
*
* Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality.
*
* @param tenantAware
* to run as specific tenant
* @param systemManagement
* to find all tenants
* @param rolloutManagement
* to run the rollout handler
* @param systemSecurityContext
* to run as system
* @param threadPoolExecutor
* to execute the handlers in parallel
* @return a new {@link RolloutScheduler} bean.
*/
@Bean
@ConditionalOnMissingBean
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.rollout.scheduler", name = "enabled", matchIfMissing = true)
RolloutScheduler rolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext,
@Qualifier("asyncExecutor") final Executor threadPoolExecutor) {
return new RolloutScheduler(tenantAware, systemManagement, rolloutManagement, systemSecurityContext,
threadPoolExecutor);
}
}

View File

@@ -60,8 +60,8 @@ public interface RolloutGroupRepository
* rollout-management to find out rolloutgroups which are in specific
* states.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param rolloutId
* the ID of the rollout the rolloutgroup belong to
* @param rolloutGroupStatus1
* the status of the rollout groups
* @param rolloutGroupStatus2
@@ -69,22 +69,45 @@ public interface RolloutGroupRepository
* @return the count of rollout groups belonging to a rollout in specific
* status
*/
@Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)")
Long countByRolloutAndStatusOrStatus(@Param("rollout") JpaRollout rollout,
@Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout.id = :rolloutId and (r.status = :status1 or r.status = :status2)")
Long countByRolloutIdAndStatusOrStatus(@Param("rolloutId") long rolloutId,
@Param("status1") RolloutGroupStatus rolloutGroupStatus1,
@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
* the status which the groups should not have
* @param status2
* the status which the groups should not have
* @param status2
* the status which the groups should not have
* @return count of rollout-groups referning a rollout and not in the given
* states
*/
long countByRolloutIdAndStatusNotAndStatusNotAndStatusNot(@Param("rolloutId") long rolloutId,
@Param("status1") JpaRolloutGroup.RolloutGroupStatus status1,
@Param("status2") JpaRolloutGroup.RolloutGroupStatus status2,
@Param("status3") JpaRolloutGroup.RolloutGroupStatus status3);
/**
* Retrieves all {@link RolloutGroup} for a specific parent in a specific
* status. Retrieves the child rolloutgroup for a specific status.
*
* @param rolloutGroup
* the parent rolloutgroup
* @param rolloutGroupId
* the rolloutgroupId to find the parents
* @param status
* the status of the rolloutgroups
* @return The child {@link RolloutGroup}s in a specific status
*/
List<JpaRolloutGroup> findByParentAndStatus(JpaRolloutGroup rolloutGroup, RolloutGroupStatus status);
@Query("SELECT g FROM JpaRolloutGroup g WHERE g.parent.id=:rolloutGroupId and g.status=:status")
List<JpaRolloutGroup> findByParentIdAndStatus(@Param("rolloutGroupId") long rolloutGroupId,
@Param("status") RolloutGroupStatus status);
/**
* Updates all {@link RolloutGroup#getStatus()} of children for given
@@ -124,4 +147,8 @@ public interface RolloutGroupRepository
*/
Page<JpaRolloutGroup> findByRolloutId(final Long rolloutId, Pageable page);
@Modifying
@Query("DELETE FROM JpaRolloutGroup g where g.id in :rolloutGroupIds")
void deleteByIds(@Param("rolloutGroupIds") List<Long> rolloutGroups);
}

View File

@@ -1,352 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations 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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
/**
* A collection of static helper methods for the {@link JpaRolloutManagement}
*/
final class RolloutHelper {
private RolloutHelper() {
}
/**
* Verifies that the required success condition and action are actually set.
*
* @param conditions
* input conditions and actions
*/
static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) {
if (conditions.getSuccessCondition() == null) {
throw new ConstraintViolationException("Rollout group is missing success condition");
}
if (conditions.getSuccessAction() == null) {
throw new ConstraintViolationException("Rollout group is missing success action");
}
}
/**
* Verifies that the group has the required success condition and action and
* a falid target percentage.
*
* @param group
* the input group
* @return the verified group
*/
static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) {
if (group.getTargetPercentage() < 1F || group.getTargetPercentage() > 100F) {
throw new ConstraintViolationException("Target percentage has to be between 1 and 100");
}
if (group.getSuccessCondition() == null) {
throw new ConstraintViolationException("Rollout group is missing success condition");
}
if (group.getSuccessAction() == null) {
throw new ConstraintViolationException("Rollout group is missing success action");
}
return group;
}
/**
* In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions.
*
* @param create
* group to check
* @param conditions
* default conditions and actions
*/
static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create,
final RolloutGroupConditions conditions) {
final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build();
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
/**
* Verify if the supplied amount of groups is in range
*
* @param amountGroup
* amount of groups
*/
static void verifyRolloutGroupParameter(final int amountGroup) {
if (amountGroup <= 0) {
throw new ConstraintViolationException("the amountGroup must be greater than zero");
} else if (amountGroup > 500) {
throw new ConstraintViolationException("the amountGroup must not be greater than 500");
}
}
/**
* Verify that the supplied percentage is in range
*
* @param percentage
* the percentage
*/
static void verifyRolloutGroupTargetPercentage(final float percentage) {
if (percentage <= 0) {
throw new ConstraintViolationException("the percentage must be greater than zero");
} else if (percentage > 100) {
throw new ConstraintViolationException("the percentage must not be greater than 100");
}
}
/**
* Modifies the target filter query to only match targets that were created
* after the Rollout.
*
* @param rollout
* Rollout to derive the filter from
* @return resulting target filter query
*/
static String getTargetFilterQuery(final Rollout rollout) {
return getTargetFilterQuery(rollout.getTargetFilterQuery(), rollout.getCreatedAt());
}
/**
* @param targetFilter
* the target filter tp be extended
* @param createdAt
* timestamp
* @return a target filter query that only matches targets that were created
* after the provided timestamp.
*/
static String getTargetFilterQuery(final String targetFilter, final Long createdAt) {
if (createdAt != null) {
return targetFilter + ";createdat=le=" + createdAt.toString();
}
return targetFilter;
}
/**
* Verifies that the Rollout is in the required status.
*
* @param rollout
* the Rollout
* @param status
* the Status
*/
static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) {
if (!rollout.getStatus().equals(status)) {
throw new RolloutIllegalStateException("Rollout is not in status " + status.toString());
}
}
/**
* Filters the groups of a Rollout to match a specific status and adds a
* group to the result.
*
* @param rollout
* the rollout
* @param status
* the required status for the groups
* @param group
* the group to add
* @return list of groups
*/
static List<Long> getGroupsByStatusIncludingGroup(final Rollout rollout,
final RolloutGroup.RolloutGroupStatus status, final RolloutGroup group) {
return rollout.getRolloutGroups().stream()
.filter(innerGroup -> innerGroup.getStatus().equals(status) || innerGroup.equals(group))
.map(RolloutGroup::getId).collect(Collectors.toList());
}
/**
* Returns the groups of a rollout by their Ids order
*
* @param rollout
* the rollout
* @return ordered list of groups
*/
static List<RolloutGroup> getOrderedGroups(final Rollout rollout) {
return rollout.getRolloutGroups().stream().sorted((group1, group2) -> {
if (group1.getId() < group2.getId()) {
return -1;
}
if (group1.getId() > group2.getId()) {
return 1;
}
return 0;
}).collect(Collectors.toList());
}
/**
* Creates an RSQL expression that matches all targets in the provided
* groups. Links all target filter queries with OR.
*
* @param groups
* the rollout groups
* @return RSQL string without base filter of the Rollout. Can be an empty
* string.
*/
static String getAllGroupsTargetFilter(final List<RolloutGroup> groups) {
if (groups.stream().anyMatch(group -> StringUtils.isEmpty(group.getTargetFilterQuery()))) {
return "";
}
return "(" + groups.stream().map(RolloutGroup::getTargetFilterQuery).distinct().sorted()
.collect(Collectors.joining("),(")) + ")";
}
/**
* Creates an RSQL Filter that matches all targets that are in the provided
* group and in the provided groups.
*
* @param baseFilter
* the base filter from the rollout
* @param groups
* the rollout groups
* @param group
* the target group
* @return RSQL string without base filter of the Rollout. Can be an empty
* string.
*/
static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List<RolloutGroup> groups,
final RolloutGroup group) {
final String groupFilter = group.getTargetFilterQuery();
// when any previous group has the same filter as the target group the
// overlap is 100%
if (isTargetFilterInGroups(groupFilter, groups)) {
return concatAndTargetFilters(baseFilter, groupFilter);
}
final String previousGroupFilters = getAllGroupsTargetFilter(groups);
if (StringUtils.isNotEmpty(previousGroupFilters)) {
if (StringUtils.isNotEmpty(groupFilter)) {
return concatAndTargetFilters(baseFilter, groupFilter, previousGroupFilters);
} else {
return concatAndTargetFilters(baseFilter, previousGroupFilters);
}
}
if (StringUtils.isNotEmpty(groupFilter)) {
return concatAndTargetFilters(baseFilter, groupFilter);
} else {
return baseFilter;
}
}
private static boolean isTargetFilterInGroups(final String groupFilter, final List<RolloutGroup> groups) {
return StringUtils.isNotEmpty(groupFilter)
&& groups.stream().anyMatch(prevGroup -> StringUtils.isNotEmpty(prevGroup.getTargetFilterQuery())
&& prevGroup.getTargetFilterQuery().equals(groupFilter));
}
private static String concatAndTargetFilters(String... filters) {
return "(" + Arrays.stream(filters).collect(Collectors.joining(");(")) + ")";
}
/**
* @param baseFilter
* the base filter from the rollout
* @param group
* group for which the filter string should be created
* @return the final target filter query for a rollout group
*/
static String getGroupTargetFilter(final String baseFilter, final RolloutGroup group) {
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
return baseFilter;
} else {
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
}
}
/**
* Verifies that no targets are left
*
* @param targetCount
* the count of left targets
*/
static void verifyRemainingTargets(final long targetCount) {
if (targetCount > 0) {
throw new ConstraintViolationException(
"Rollout groups don't match all targets that are targeted by the rollout");
}
if (targetCount != 0) {
throw new ConstraintViolationException("Rollout groups target count verification failed");
}
}
/**
* @param searchText
* search string
* @return criteria specification with a query for name or description of a
* rollout
*/
static Specification<JpaRollout> likeNameOrDescription(final String searchText) {
return (rolloutRoot, query, criteriaBuilder) -> {
final String searchTextToLower = searchText.toLowerCase();
return criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
searchTextToLower));
};
}
static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
if (!(Rollout.RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
+ rollout.getStatus().name().toLowerCase());
}
}
static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
}
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -15,11 +16,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -30,36 +28,15 @@ public interface RolloutRepository
extends BaseEntityRepository<JpaRollout, Long>, JpaSpecificationExecutor<JpaRollout> {
/**
* Updates the {@code lastCheck} field of the {@link Rollout} for rollouts
* in a specific status and only if the {@code lastCheck} is overdue.
* Retrieves all {@link Rollout} for given status.
*
* @param lastCheck
* the time in milliseconds to set to the lastCheck column
* @param delay
* the delay between last checks
* @param status
* the status which the rollout should have to update the last
* check field
* @return the count of the updated rows. Zero if no row has been updated
* the status of the rollouts to find
* @return the list of {@link Rollout} for specific status
*/
@Modifying
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
@Query("UPDATE JpaRollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status")
int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay,
@Param("status") final RolloutStatus status);
/**
* Retrieves all {@link Rollout} for a specific {@code lastCheck} time and
* for a specific status.
*
* @param lastCheck
* the lastCheck time to find the specific rollout.
* @param status
* the status of the rollout to find
* @return the list of {@link Rollout} for specific lastCheck time and
* status
*/
List<JpaRollout> findByLastCheckAndStatus(long lastCheck, RolloutStatus status);
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT sm.id FROM JpaRollout sm WHERE sm.status IN ?1")
List<Long> findByStatusIn(Collection<RolloutStatus> status);
/**
* Retrieves all {@link Rollout} for a specific {@code name}

View File

@@ -47,6 +47,15 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
@EntityGraph(value = "Target.detail", type = EntityGraphType.LOAD)
Optional<Target> findByControllerId(String controllerID);
/**
* Checks if target with given id exists.
*
* @param controllerId to check
* @return <code>true</code> if target with given id exists
*/
@Query("SELECT CASE WHEN COUNT(t)>0 THEN 'true' ELSE 'false' END FROM JpaTarget t WHERE t.controllerId=:controllerId")
boolean existsByControllerId(@Param("controllerId") String controllerId);
/**
* Deletes the {@link Target}s with the given target IDs.
*

View File

@@ -97,6 +97,7 @@ public class AutoAssignChecker {
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void check() {
LOGGER.debug("Auto assigned check call");
final PageRequest pageRequest = new PageRequest(0, PAGE_SIZE);

View File

@@ -9,27 +9,24 @@
package org.eclipse.hawkbit.repository.jpa.autoassign;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.eclipse.hawkbit.repository.AutoAssignProperties;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Scheduled;
/**
* Scheduler to check target filters for auto assignment of distribution sets
*/
// don't active the auto assign scheduler in test, otherwise it is hard to test
@Profile("!test")
@EnableConfigurationProperties(AutoAssignProperties.class)
public class AutoAssignScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignScheduler.class);
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:2000}";
private final TenantAware tenantAware;
private final SystemManagement systemManagement;
@@ -38,6 +35,8 @@ public class AutoAssignScheduler {
private final AutoAssignChecker autoAssignChecker;
private final LockRegistry lockRegistry;
/**
* Instantiates a new AutoAssignScheduler
*
@@ -49,13 +48,17 @@ public class AutoAssignScheduler {
* to run as system
* @param autoAssignChecker
* to run a check as tenant
* @param lockRegistry
* to acquire a lock per tenant
*/
public AutoAssignScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker) {
final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker,
final LockRegistry lockRegistry) {
this.tenantAware = tenantAware;
this.systemManagement = systemManagement;
this.systemSecurityContext = systemSecurityContext;
this.autoAssignChecker = autoAssignChecker;
this.lockRegistry = lockRegistry;
}
/**
@@ -64,29 +67,38 @@ public class AutoAssignScheduler {
* tenant the auto assignments defined in the target filter queries
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER)
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void autoAssignScheduler() {
LOGGER.debug("auto assign schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant separately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking target filter queries for tenants: {}", tenants.size());
for (final String tenant : tenants) {
systemSecurityContext.runAsSystem(() -> executeAutoAssign());
}
private Object executeAutoAssign() {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant separately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking target filter queries for tenants: {}", tenants.size());
for (final String tenant : tenants) {
final Lock lock = lockRegistry.obtain(tenant + "-autoassign");
if (!lock.tryLock()) {
return null;
}
try {
tenantAware.runAsTenant(tenant, () -> {
autoAssignChecker.check();
return null;
});
} finally {
lock.unlock();
}
return null;
});
}
return null;
}
}

View File

@@ -43,12 +43,12 @@ public class DistributionSetTypeElement implements Serializable {
@MapsId("dsType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_dstype"))
@JoinColumn(name = "distribution_set_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_element"))
private JpaDistributionSetType dsType;
@MapsId("smType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "software_module_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
@JoinColumn(name = "software_module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
private JpaSoftwareModuleType smType;
public DistributionSetTypeElement() {

View File

@@ -20,10 +20,10 @@ import javax.persistence.Embeddable;
public class DistributionSetTypeElementCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "distribution_set_type", nullable = false)
@Column(name = "distribution_set_type", nullable = false, updatable = false)
private Long dsType;
@Column(name = "software_module_type", nullable = false)
@Column(name = "software_module_type", nullable = false, updatable = false)
private Long smType;
/**

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
@@ -34,6 +33,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -58,13 +58,13 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "distribution_set", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds"))
@NotNull
private JpaDistributionSet distributionSet;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "target", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target"))
@NotNull
private JpaTarget target;
@@ -79,20 +79,20 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Column(name = "forced_time")
private long forcedTime;
@Column(name = "status")
@Column(name = "status", nullable = false)
@NotNull
private Status status;
@CascadeOnDelete
@OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY, cascade = {
CascadeType.REMOVE })
private List<ActionStatus> actionStatus;
@OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY)
private List<JpaActionStatus> actionStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup"))
@JoinColumn(name = "rolloutgroup", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup"))
private JpaRolloutGroup rolloutGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
@JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
private JpaRollout rollout;
@Override
@@ -185,13 +185,15 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
.publishEvent(new ActionCreatedEvent(this, BaseEntity.getIdOrNull(rollout),
BaseEntity.getIdOrNull(rolloutGroup), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
.publishEvent(new ActionUpdatedEvent(this, BaseEntity.getIdOrNull(rollout),
BaseEntity.getIdOrNull(rolloutGroup), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override

View File

@@ -54,11 +54,11 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
private Long occurredAt;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "action", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action"))
@JoinColumn(name = "action", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action"))
@NotNull
private JpaAction action;
@Column(name = "status")
@Column(name = "status", nullable = false, updatable = false)
@NotNull
private Status status;

View File

@@ -12,6 +12,7 @@ import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
@@ -49,7 +50,7 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
@NotEmpty
private String filename;
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST })
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY)
@JoinColumn(name = "software_module", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_assigned_sm"))
private JpaSoftwareModule softwareModule;

View File

@@ -16,7 +16,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
@@ -45,9 +44,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
@@ -82,40 +79,38 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
@CascadeOnDelete
@ManyToMany(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinTable(name = "sp_ds_module", joinColumns = {
@JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "module_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) })
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "module_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) })
private Set<SoftwareModule> modules;
@CascadeOnDelete
@ManyToMany(targetEntity = JpaDistributionSetTag.class)
@JoinTable(name = "sp_ds_dstag", joinColumns = {
@JoinColumn(name = "ds", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "TAG", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) })
@JoinColumn(name = "ds", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "TAG", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) })
private Set<DistributionSetTag> tags;
@Column(name = "deleted")
private boolean deleted;
@OneToMany(mappedBy = "assignedDistributionSet", targetEntity = JpaTarget.class, fetch = FetchType.LAZY)
private List<Target> assignedToTargets;
private List<JpaTarget> assignedToTargets;
@OneToMany(mappedBy = "autoAssignDistributionSet", targetEntity = JpaTargetFilterQuery.class, fetch = FetchType.LAZY)
private List<TargetFilterQuery> autoAssignFilters;
@OneToMany(mappedBy = "installedDistributionSet", targetEntity = JpaTargetInfo.class, fetch = FetchType.LAZY)
private List<TargetInfo> installedAtTargets;
private List<JpaTargetInfo> installedAtTargets;
@OneToMany(mappedBy = "distributionSet", targetEntity = JpaAction.class, fetch = FetchType.LAZY)
private List<Action> actions;
private List<JpaAction> actions;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class, cascade = {
CascadeType.REMOVE })
@JoinColumn(name = "ds_id", insertable = false, updatable = false)
@OneToMany(mappedBy = "distributionSet", fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class)
private List<DistributionSetMetadata> metadata;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetType.class)
@JoinColumn(name = "ds_id", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = JpaDistributionSetType.class)
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds"))
@NotNull
private DistributionSetType type;

View File

@@ -32,8 +32,8 @@ public class JpaDistributionSetMetadata extends JpaMetaData implements Distribut
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds"))
private JpaDistributionSet distributionSet;
public JpaDistributionSetMetadata() {

View File

@@ -19,7 +19,6 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@@ -29,6 +28,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.util.CollectionUtils;
@@ -49,12 +49,12 @@ import org.springframework.util.CollectionUtils;
public class JpaDistributionSetType extends AbstractJpaNamedEntity implements DistributionSetType {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = DistributionSetTypeElement.class, cascade = {
CascadeType.ALL }, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "distribution_set_type", insertable = false, updatable = false)
@CascadeOnDelete
@OneToMany(mappedBy = "dsType", targetEntity = DistributionSetTypeElement.class, cascade = {
CascadeType.PERSIST }, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DistributionSetTypeElement> elements;
@Column(name = "type_key", nullable = false, length = 64)
@Column(name = "type_key", nullable = false, updatable = false, length = 64)
@Size(max = 64)
@NotEmpty
private String key;

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
@@ -28,6 +29,7 @@ import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -35,7 +37,14 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
import org.hibernate.validator.constraints.NotEmpty;
/**
@@ -49,25 +58,41 @@ import org.hibernate.validator.constraints.NotEmpty;
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities
@SuppressWarnings("squid:S2160")
@ObjectTypeConverter(name = "rolloutstatus", objectType = Rollout.RolloutStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "CREATING", dataValue = "0"),
@ConversionValue(objectValue = "READY", dataValue = "1"),
@ConversionValue(objectValue = "PAUSED", dataValue = "2"),
@ConversionValue(objectValue = "STARTING", dataValue = "3"),
@ConversionValue(objectValue = "STOPPED", dataValue = "4"),
@ConversionValue(objectValue = "RUNNING", dataValue = "5"),
@ConversionValue(objectValue = "FINISHED", dataValue = "6"),
@ConversionValue(objectValue = "ERROR_CREATING", dataValue = "7"),
@ConversionValue(objectValue = "ERROR_STARTING", dataValue = "8"),
@ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10") })
public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, EventAwareEntity {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = JpaRolloutGroup.class)
@JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup"))
private List<RolloutGroup> rolloutGroups;
private static final String DELETED_PROPERTY = "deleted";
@CascadeOnDelete
@OneToMany(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, mappedBy = "rollout")
private List<JpaRolloutGroup> rolloutGroups;
@Column(name = "target_filter", length = 1024, nullable = false)
@Size(max = 1024)
@NotEmpty
private String targetFilterQuery;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "distribution_set", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds"))
@NotNull
private JpaDistributionSet distributionSet;
@Column(name = "status")
@Column(name = "status", nullable = false)
@Convert("rolloutstatus")
@NotNull
private RolloutStatus status = RolloutStatus.CREATING;
@Column(name = "last_check")
@@ -87,6 +112,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Column(name = "rollout_groups_created")
private int rolloutGroupsCreated;
@Column(name = "deleted")
private boolean deleted;
@Column(name = "start_at")
private Long startAt;
@@ -142,7 +170,7 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
return startAt;
}
public void setStartAt(Long startAt) {
public void setStartAt(final Long startAt) {
this.startAt = startAt;
}
@@ -196,9 +224,8 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Override
public String toString() {
return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery
+ ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck
+ ", getName()=" + getName() + ", getId()=" + getId() + "]";
return "Rollout [ targetFilterQuery=" + targetFilterQuery + ", distributionSet=" + distributionSet + ", status="
+ status + ", lastCheck=" + lastCheck + ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
@Override
@@ -211,11 +238,35 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new RolloutUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
if (isSoftDeleted(descriptorEvent)) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
}
private static boolean isSoftDeleted(final DescriptorEvent event) {
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
final List<DirectToFieldChangeRecord> changes = changeSet.getChanges().stream()
.filter(record -> record instanceof DirectToFieldChangeRecord)
.map(record -> (DirectToFieldChangeRecord) record).collect(Collectors.toList());
return changes.stream().filter(record -> DELETED_PROPERTY.equals(record.getAttribute())
&& Boolean.parseBoolean(record.getNewValue().toString())).count() > 0;
}
@Override
public void fireDeleteEvent(final DescriptorEvent descriptorEvent) {
// there is no rollout deletion event
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public boolean isDeleted() {
return deleted;
}
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
}

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
@@ -60,18 +61,20 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout"))
@JoinColumn(name = "rollout", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout"))
private JpaRollout rollout;
@Column(name = "status")
@Column(name = "status", nullable = false)
@Convert("rolloutgroupstatus")
private RolloutGroupStatus status = RolloutGroupStatus.CREATING;
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class)
@JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false)
@CascadeOnDelete
@OneToMany(mappedBy = "rolloutGroup", fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class)
private List<RolloutTargetGroup> rolloutTargetGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rolloutgroup"))
private JpaRolloutGroup parent;
@Column(name = "success_condition", nullable = false)
@@ -287,8 +290,7 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutGroupUpdatedEvent(this,
EventPublisherHolder.getInstance().getApplicationId()));
this.getRollout().getId(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
@@ -36,11 +37,13 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
/**
* Base Software Module that is supported by OS level provisioning mechanism on
@@ -60,8 +63,10 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implements SoftwareModule, EventAwareEntity {
private static final long serialVersionUID = 1L;
private static final String DELETED_PROPERTY = "deleted";
@ManyToOne
@JoinColumn(name = "module_type", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type"))
@JoinColumn(name = "module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type"))
@NotNull
private JpaSoftwareModuleType type;
@@ -77,13 +82,12 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, mappedBy = "softwareModule", cascade = {
CascadeType.ALL }, targetEntity = JpaArtifact.class)
private List<Artifact> artifacts;
CascadeType.PERSIST }, targetEntity = JpaArtifact.class, orphanRemoval = true)
private List<JpaArtifact> artifacts;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaSoftwareModuleMetadata.class)
@JoinColumn(name = "sw_id", insertable = false, updatable = false)
private List<SoftwareModuleMetadata> metadata;
@OneToMany(mappedBy = "softwareModule", fetch = FetchType.LAZY, targetEntity = JpaSoftwareModuleMetadata.class)
private List<JpaSoftwareModuleMetadata> metadata;
/**
* Default constructor.
@@ -116,12 +120,12 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
public void addArtifact(final Artifact artifact) {
if (null == artifacts) {
artifacts = new ArrayList<>(4);
artifacts.add(artifact);
artifacts.add((JpaArtifact) artifact);
return;
}
if (!artifacts.contains(artifact)) {
artifacts.add(artifact);
artifacts.add((JpaArtifact) artifact);
}
}
@@ -206,6 +210,21 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
new SoftwareModuleUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
if (isSoftDeleted(descriptorEvent)) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new SoftwareModuleDeletedEvent(
getTenant(), getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
}
private static boolean isSoftDeleted(final DescriptorEvent event) {
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
final List<DirectToFieldChangeRecord> changes = changeSet.getChanges().stream()
.filter(record -> record instanceof DirectToFieldChangeRecord)
.map(record -> (DirectToFieldChangeRecord) record).collect(Collectors.toList());
return changes.stream().filter(record -> DELETED_PROPERTY.equals(record.getAttribute())
&& Boolean.parseBoolean(record.getNewValue().toString())).count() > 0;
}
@Override

View File

@@ -32,8 +32,8 @@ public class JpaSoftwareModuleMetadata extends JpaMetaData implements SoftwareMo
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
@ManyToOne(optional = false, targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinColumn(name = "sw_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
private SoftwareModule softwareModule;
public JpaSoftwareModuleMetadata() {

View File

@@ -87,22 +87,21 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
@CascadeOnDelete
@ManyToMany(targetEntity = JpaTargetTag.class)
@JoinTable(name = "sp_target_target_tag", joinColumns = {
@JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target")) }, inverseJoinColumns = {
@JoinColumn(name = "tag", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag")) })
@JoinColumn(name = "target", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target")) }, inverseJoinColumns = {
@JoinColumn(name = "tag", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag")) })
private Set<TargetTag> tags;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = {
CascadeType.REMOVE }, targetEntity = JpaAction.class)
@JoinColumn(name = "target", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_act_hist_targ"))
private List<Action> actions;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, targetEntity = JpaAction.class)
private List<JpaAction> actions;
@ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = JpaDistributionSet.class)
@JoinColumn(name = "assigned_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_assign_ds"))
private JpaDistributionSet assignedDistributionSet;
@CascadeOnDelete
@OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class)
@OneToOne(cascade = { CascadeType.PERSIST,
CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class)
@PrimaryKeyJoinColumn
private JpaTargetInfo targetInfo;
@@ -110,14 +109,13 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
* the security token of the target which allows if enabled to authenticate
* with this security token.
*/
@Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128)
@Size(max = 64)
@Column(name = "sec_token", updatable = true, nullable = false, length = 128)
@Size(max = 128)
@NotEmpty
private String securityToken;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
@JoinColumn(name = "target_Id", insertable = false, updatable = false)
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
private List<RolloutTargetGroup> rolloutTargetGroup;
/**
@@ -214,7 +212,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
actions = new ArrayList<>();
}
return actions.add(action);
return actions.add((JpaAction) action);
}
@Override

View File

@@ -79,8 +79,7 @@ public class JpaTargetInfo implements Persistable<Long>, TargetInfo {
private boolean entityNew;
@CascadeOnDelete
@OneToOne(cascade = { CascadeType.MERGE,
CascadeType.REMOVE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class)
@OneToOne(cascade = { CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class)
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_stat_targ"))
@MapsId
private JpaTarget target;
@@ -112,7 +111,7 @@ public class JpaTargetInfo implements Persistable<Long>, TargetInfo {
@Column(name = "attribute_value", length = 128)
@MapKeyColumn(name = "attribute_key", nullable = false, length = 32)
@CollectionTable(name = "sp_target_attributes", joinColumns = {
@JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
@JoinColumn(name = "target_id", nullable = false, updatable = false) }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
private final Map<String, String> controllerAttributes = Collections.synchronizedMap(new HashMap<String, String>());
// set default request controller attributes to true, because we want to

View File

@@ -32,7 +32,7 @@ import org.hibernate.validator.constraints.NotEmpty;
public class JpaTenantConfiguration extends AbstractJpaTenantAwareBaseEntity implements TenantConfiguration {
private static final long serialVersionUID = 1L;
@Column(name = "conf_key", length = 128, nullable = false)
@Column(name = "conf_key", length = 128, nullable = false, updatable = false)
@Size(max = 128)
@NotEmpty
private String key;

View File

@@ -43,7 +43,7 @@ import org.eclipse.hawkbit.repository.model.TenantMetaData;
public class JpaTenantMetaData extends AbstractJpaBaseEntity implements TenantMetaData {
private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, length = 40)
@Column(name = "tenant", nullable = false, updatable = false, length = 40)
@Size(max = 40)
private String tenant;

View File

@@ -24,7 +24,6 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.persistence.annotations.ExistenceChecking;
@@ -44,19 +43,22 @@ public class RolloutTargetGroup implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
private RolloutGroup rolloutGroup;
@ManyToOne(optional = false, targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
private JpaRolloutGroup rolloutGroup;
@Id
@ManyToOne(targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
@ManyToOne(optional = false, targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST })
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
private JpaTarget target;
@OneToMany(targetEntity = JpaAction.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"),
@JoinColumn(name = "target", referencedColumnName = "target_id") })
private List<Action> actions;
@JoinColumns(value = {
@JoinColumn(name = "rolloutgroup", nullable = false, updatable = false, referencedColumnName = "rolloutGroup_Id"),
@JoinColumn(name = "target", nullable = false, updatable = false, referencedColumnName = "target_id") })
private List<JpaAction> actions;
/**
* default constructor for JPA.
@@ -66,7 +68,7 @@ public class RolloutTargetGroup implements Serializable {
}
public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) {
this.rolloutGroup = rolloutGroup;
this.rolloutGroup = (JpaRolloutGroup) rolloutGroup;
this.target = (JpaTarget) target;
}

View File

@@ -9,63 +9,78 @@
package org.eclipse.hawkbit.repository.jpa.rollout;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutProperties;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.google.common.base.Throwables;
/**
* Scheduler to schedule the
* {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the
* checks be be configured using the properties from {@link RolloutProperties}.
* Scheduler to schedule the {@link RolloutManagement#handleRollouts()}. The
* delay between the checks be be configured using the property from
* {#PROP_SCHEDULER_DELAY_PLACEHOLDER}.
*/
@Component
// don't active the rollout scheduler in test, otherwise it is hard to test
// rolloutmanagement and leads weird side-effects maybe.
@Profile("!test")
public class RolloutScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutScheduler.class);
@Autowired
private TenantAware tenantAware;
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.scheduler.fixedDelay:2000}";
@Autowired
private SystemManagement systemManagement;
private final TenantAware tenantAware;
@Autowired
private RolloutManagement rolloutManagement;
private final SystemManagement systemManagement;
@Autowired
private SystemSecurityContext systemSecurityContext;
private final RolloutManagement rolloutManagement;
@Autowired
private RolloutProperties rolloutProperties;
private final SystemSecurityContext systemSecurityContext;
private final ExecutorCompletionService<Void> completionService;
/**
* Constructor.
*
* @param tenantAware
* to run as specific tenant
* @param systemManagement
* to find all tenants
* @param rolloutManagement
* to run the rollout handler
* @param systemSecurityContext
* to run as system
* @param threadPoolExecutor
* to execute the handlers in parallel
*/
public RolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext,
final Executor threadPoolExecutor) {
this.tenantAware = tenantAware;
this.systemManagement = systemManagement;
this.rolloutManagement = rolloutManagement;
this.systemSecurityContext = systemSecurityContext;
completionService = new ExecutorCompletionService<>(threadPoolExecutor);
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkRunningRollouts(long)} in the
* tenant the {@link RolloutManagement#handleRollouts()} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_SCHEDULER_DELAY_PLACEHOLDER)
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void runningRolloutScheduler() {
if (!rolloutProperties.getScheduler().isEnabled()) {
return;
}
LOGGER.debug("rollout schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
final int tasks = systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
@@ -75,119 +90,25 @@ public class RolloutScheduler {
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getScheduler().getFixedDelay();
rolloutManagement.checkRunningRollouts(fixedDelay);
completionService.submit(() -> tenantAware.runAsTenant(tenant, () -> {
rolloutManagement.handleRollouts();
return null;
});
}));
}
return null;
return tenants.size();
});
waitUntilHandlersAreComplete(tasks);
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkStartingRollouts(long)} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER)
public void startingRolloutScheduler() {
if (!rolloutProperties.getStartingScheduler().isEnabled()) {
return;
}
LOGGER.debug("rollout starting schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking starting rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getStartingScheduler().getFixedDelay();
rolloutManagement.checkStartingRollouts(fixedDelay);
return null;
});
private void waitUntilHandlersAreComplete(final int tasks) {
try {
for (int i = 0; i < tasks; i++) {
completionService.take().get();
}
return null;
});
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkCreatingRollouts(long)} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER)
public void creatingRolloutScheduler() {
if (!rolloutProperties.getCreatingScheduler().isEnabled()) {
return;
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw Throwables.propagate(e);
}
LOGGER.debug("rollout creating schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking creating rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getCreatingScheduler().getFixedDelay();
rolloutManagement.checkCreatingRollouts(fixedDelay);
return null;
});
}
return null;
});
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkReadyRollouts(long)} in the
* {@link SystemSecurityContext}. Used to auto start Rollouts as soon as
* their startAt time is reached.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER)
public void readyRolloutScheduler() {
if (!rolloutProperties.getReadyScheduler().isEnabled()) {
return;
}
LOGGER.debug("rollout ready schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking ready rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getReadyScheduler().getFixedDelay();
rolloutManagement.checkReadyRollouts(fixedDelay);
return null;
});
}
return null;
});
}
}

View File

@@ -71,7 +71,7 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
// scheduled. If the group is empty now, we just finish the group if
// there are not actions available for this group.
final List<JpaRolloutGroup> findByRolloutGroupParent = rolloutGroupRepository
.findByParentAndStatus((JpaRolloutGroup) rolloutGroup, RolloutGroupStatus.SCHEDULED);
.findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroupStatus.SCHEDULED);
findByRolloutGroupParent.forEach(nextGroup -> {
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
nextGroup.setStatus(RolloutGroupStatus.FINISHED);

View File

@@ -20,8 +20,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Target;
import org.springframework.data.jpa.domain.Specification;
/**
@@ -37,25 +37,50 @@ public final class ActionSpecifications {
/**
* Specification which joins all necessary tables to retrieve the dependency
* between a target and a local file assignment through the assigen action
* between a target and a local file assignment through the assigned action
* of the target. All actions are included, not only active actions.
*
* @param target
* the target to verfiy if the given artifact is currently
* @param controllerId
* the target to verify if the given artifact is currently
* assigned or had been assigned
* @param sha1Hash
* of the local artifact to check wherever the target had ever
* been assigned
* @return a specification to use with spring JPA
*/
public static Specification<JpaAction> hasTargetAssignedArtifact(final Target target, final String sha1Hash) {
public static Specification<JpaAction> hasTargetAssignedArtifact(final String controllerId, final String sha1Hash) {
return (actionRoot, query, criteriaBuilder) -> {
final Join<JpaAction, JpaDistributionSet> dsJoin = actionRoot.join(JpaAction_.distributionSet);
final SetJoin<JpaDistributionSet, JpaSoftwareModule> modulesJoin = dsJoin.join(JpaDistributionSet_.modules);
final ListJoin<JpaSoftwareModule, JpaArtifact> artifactsJoin = modulesJoin
.join(JpaSoftwareModule_.artifacts);
return criteriaBuilder.and(criteriaBuilder.equal(artifactsJoin.get(JpaArtifact_.sha1Hash), sha1Hash),
criteriaBuilder.equal(actionRoot.get(JpaAction_.target), target));
criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.controllerId),
controllerId));
};
}
/**
* Specification which joins all necessary tables to retrieve the dependency
* between a target and a local file assignment through the assigned action
* of the target. All actions are included, not only active actions.
*
* @param targetId
* the target to verify if the given artifact is currently
* assigned or had been assigned
* @param sha1Hash
* of the local artifact to check wherever the target had ever
* been assigned
* @return a specification to use with spring JPA
*/
public static Specification<JpaAction> hasTargetAssignedArtifact(final Long targetId, final String sha1Hash) {
return (actionRoot, query, criteriaBuilder) -> {
final Join<JpaAction, JpaDistributionSet> dsJoin = actionRoot.join(JpaAction_.distributionSet);
final SetJoin<JpaDistributionSet, JpaSoftwareModule> modulesJoin = dsJoin.join(JpaDistributionSet_.modules);
final ListJoin<JpaSoftwareModule, JpaArtifact> artifactsJoin = modulesJoin
.join(JpaSoftwareModule_.artifacts);
return criteriaBuilder.and(criteriaBuilder.equal(artifactsJoin.get(JpaArtifact_.sha1Hash), sha1Hash),
criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId));
};
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations 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.specifications;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link Rollout}s. The class provides Spring Data
* JPQL Specifications.
*
*/
public final class RolloutSpecification {
private RolloutSpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link Rollout}s by its DELETED
* attribute.
*
* @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored
* @return the {@link Rollout} {@link Specification}
*/
public static Specification<JpaRollout> isDeleted(final Boolean isDeleted) {
return (root, query, cb) -> cb.equal(root.<Boolean> get(JpaRollout_.deleted), isDeleted);
}
}

View File

@@ -0,0 +1,26 @@
ALTER TABLE sp_rollout ADD COLUMN deleted BOOLEAN;
UPDATE sp_rollout SET deleted = 0;
ALTER TABLE sp_action MODIFY target BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_action_status MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY rollout BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_ds_type_element DROP CONSTRAINT fk_ds_type_element_element;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_element
FOREIGN KEY (distribution_set_type)
REFERENCES sp_distribution_set_type (id)
ON DELETE CASCADE;
ALTER TABLE sp_ds_type_element DROP CONSTRAINT fk_ds_type_element_smtype;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_smtype
FOREIGN KEY (software_module_type)
REFERENCES sp_software_module_type (id)
ON DELETE CASCADE;

View File

@@ -0,0 +1,26 @@
ALTER TABLE sp_rollout ADD COLUMN deleted BOOLEAN;
UPDATE sp_rollout SET deleted = 0;
ALTER TABLE sp_action MODIFY target BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_action_status MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY rollout BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_ds_type_element DROP FOREIGN KEY fk_ds_type_element_element;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_element
FOREIGN KEY (distribution_set_type)
REFERENCES sp_distribution_set_type (id)
ON DELETE CASCADE;
ALTER TABLE sp_ds_type_element DROP FOREIGN KEY fk_ds_type_element_smtype;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_smtype
FOREIGN KEY (software_module_type)
REFERENCES sp_software_module_type (id)
ON DELETE CASCADE;

View File

@@ -65,6 +65,12 @@ public class RemoteIdEventTest extends AbstractRemoteEventTest {
assertAndCreateRemoteEvent(SoftwareModuleDeletedEvent.class);
}
@Test
@Description("Verifies that the rollout id is correct reloaded")
public void testRolloutDeletedEvent() {
assertAndCreateRemoteEvent(RolloutDeletedEvent.class);
}
protected void assertAndCreateRemoteEvent(final Class<? extends RemoteIdEvent> eventType) {
final Constructor<?> constructor = Arrays.stream(eventType.getDeclaredConstructors())

View File

@@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
@@ -46,6 +47,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
final Target target = testdataFactory.createTarget("Test");
generateAction.setTarget(target);
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
final Action action = actionRepository.save(generateAction);
final TargetAssignDistributionSetEvent assignmentEvent = new TargetAssignDistributionSetEvent(action,

View File

@@ -8,9 +8,16 @@
*/
package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import java.lang.reflect.Constructor;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
@@ -35,7 +42,49 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
@Description("Verifies that the action entity reloading by remote updated works")
public void testActionUpdatedEvent() {
assertAndCreateRemoteEvent(ActionUpdatedEvent.class);
}
@Override
protected RemoteEntityEvent<?> createRemoteEvent(final Action baseEntity,
final Class<? extends RemoteEntityEvent<?>> eventType) {
Constructor<?> constructor = null;
for (final Constructor<?> constructors : eventType.getDeclaredConstructors()) {
if (constructors.getParameterCount() == 4) {
constructor = constructors;
}
}
if (constructor == null) {
throw new IllegalArgumentException("No suitable constructor foundes");
}
try {
return (RemoteEntityEvent<?>) constructor.newInstance(baseEntity, 1L, 2L, "Node");
} catch (final ReflectiveOperationException e) {
fail("Exception should not happen " + e.getMessage());
}
return null;
}
@Override
protected RemoteEntityEvent<?> assertEntity(final Action baseEntity, final RemoteEntityEvent<?> e) {
final AbstractActionEvent event = (AbstractActionEvent) e;
assertThat(event.getEntity()).isSameAs(baseEntity);
assertThat(event.getRolloutId()).isEqualTo(1L);
AbstractActionEvent underTestCreatedEvent = (AbstractActionEvent) createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L);
underTestCreatedEvent = (AbstractActionEvent) createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L);
return underTestCreatedEvent;
}
@Override
@@ -43,8 +92,10 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
final JpaAction generateAction = new JpaAction();
generateAction.setActionType(ActionType.FORCED);
final Target target = testdataFactory.createTarget("Test");
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
generateAction.setTarget(target);
generateAction.setDistributionSet(testdataFactory.createDistributionSet());
generateAction.setDistributionSet(distributionSet);
generateAction.setStatus(Status.RUNNING);
return actionRepository.save(generateAction);
}

View File

@@ -8,8 +8,10 @@
*/
package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.junit.Assert.fail;
import static org.assertj.core.api.Assertions.assertThat;
import java.lang.reflect.Constructor;
import java.util.UUID;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -44,6 +46,47 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<Rollout
assertAndCreateRemoteEvent(RolloutGroupUpdatedEvent.class);
}
@Override
protected RemoteEntityEvent<?> createRemoteEvent(final RolloutGroup baseEntity,
final Class<? extends RemoteEntityEvent<?>> eventType) {
Constructor<?> constructor = null;
for (final Constructor<?> constructors : eventType.getDeclaredConstructors()) {
if (constructors.getParameterCount() == 3) {
constructor = constructors;
}
}
if (constructor == null) {
throw new IllegalArgumentException("No suitable constructor foundes");
}
try {
return (RemoteEntityEvent<?>) constructor.newInstance(baseEntity, 1L, "Node");
} catch (final ReflectiveOperationException e) {
fail("Exception should not happen " + e.getMessage());
}
return null;
}
@Override
protected RemoteEntityEvent<?> assertEntity(final RolloutGroup baseEntity, final RemoteEntityEvent<?> e) {
final AbstractRolloutGroupEvent event = (AbstractRolloutGroupEvent) e;
assertThat(event.getEntity()).isSameAs(baseEntity);
assertThat(event.getRolloutId()).isEqualTo(1L);
AbstractRolloutGroupEvent underTestCreatedEvent = (AbstractRolloutGroupEvent) createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
underTestCreatedEvent = (AbstractRolloutGroupEvent) createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
return underTestCreatedEvent;
}
@Override
protected RolloutGroup createEntity() {
testdataFactory.createTarget(UUID.randomUUID().toString());

View File

@@ -31,6 +31,8 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
@SpringApplicationConfiguration(classes = {
org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration.class })
public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest {
@@ -80,6 +82,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Autowired
protected RolloutGroupRepository rolloutGroupRepository;
@Autowired
protected RolloutTargetGroupRepository rolloutTargetGroupRepository;
@Autowired
protected RolloutRepository rolloutRepository;
@@ -91,7 +96,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
protected List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutIdAndStatus(rollout.getId(), actionStatus);
return Lists.newArrayList(actionRepository.findByRolloutIdAndStatus(pageReq, rollout.getId(), actionStatus));
}
protected TargetTagAssignmentResult toggleTagAssignment(final Collection<Target> targets, final TargetTag tag) {

View File

@@ -9,23 +9,29 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.HashGeneratorUtils;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.Test;
@@ -35,24 +41,47 @@ import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Test class for {@link ArtifactManagement} with running MongoDB instance..
*
*
*
*
* Test class for {@link ArtifactManagement}.
*/
@Features("Component Tests - Repository")
@Stories("Artifact Management")
public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
/**
* Test method for
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#createArtifact(java.io.InputStream)}
* .
*
* @throws IOException
* @throws NoSuchAlgorithmException
*/
@Test
@Description("Verifies that management queries react as specfied on calls for non existing entities.")
@ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) })
public void nonExistingEntityQueries() throws URISyntaxException {
final SoftwareModule module = testdataFactory.createSoftwareModuleOs();
assertThatThrownBy(() -> artifactManagement.createArtifact(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx",
null, null, false, null)).isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThatThrownBy(
() -> artifactManagement.createArtifact(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx", false))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThatThrownBy(() -> artifactManagement.deleteArtifact(1234L)).isInstanceOf(EntityNotFoundException.class)
.hasMessageContaining("1234").hasMessageContaining("Artifact");
assertThat(artifactManagement.findArtifact(1234L).isPresent()).isFalse();
assertThatThrownBy(() -> artifactManagement.findArtifactBySoftwareModule(pageReq, 1234L))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThat(artifactManagement.findArtifactByFilename("1234").isPresent()).isFalse();
assertThatThrownBy(() -> artifactManagement.findByFilenameAndSoftwareModule("xxx", 1234L))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThat(artifactManagement.findByFilenameAndSoftwareModule("1234", module.getId()).isPresent()).isFalse();
assertThat(artifactManagement.findFirstArtifactBySHA1("1234").isPresent()).isFalse();
assertThat(artifactManagement.loadArtifactBinary("1234").isPresent()).isFalse();
}
@Test
@Description("Test if a local artifact can be created by API including metadata.")
public void createArtifact() throws NoSuchAlgorithmException, IOException {
@@ -205,7 +234,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final Artifact result = artifactManagement.createArtifact(new ByteArrayInputStream(random),
testdataFactory.createSoftwareModuleOs().getId(), "file1", false);
try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash())
try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get()
.getFileInputStream()) {
assertTrue("The stored binary matches the given binary",
IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream));

View File

@@ -9,9 +9,12 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
@@ -29,10 +32,12 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
@@ -55,6 +60,75 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Autowired
private RepositoryProperties repositoryProperties;
@Test
@Description("Verifies that management queries react as specfied on calls for non existing entities.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 1) })
public void nonExistingEntityQueries() throws URISyntaxException {
final Target target = testdataFactory.createTarget();
final SoftwareModule module = testdataFactory.createSoftwareModuleOs();
assertThatThrownBy(() -> controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.FINISHED)))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThatThrownBy(() -> controllerManagement
.addInformationalActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.RUNNING)))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThatThrownBy(() -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.FINISHED)))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThat(controllerManagement.findActionWithDetails(1234L).isPresent()).isFalse();
assertThat(controllerManagement.findByControllerId("1234").isPresent()).isFalse();
assertThat(controllerManagement.findByTargetId(1234L).isPresent()).isFalse();
assertThatThrownBy(() -> controllerManagement.findOldestActiveActionByTarget("1234"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThatThrownBy(() -> controllerManagement
.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), 1234L))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThatThrownBy(
() -> controllerManagement.getActionForDownloadByTargetAndSoftwareModule("1234", module.getId()))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThat(controllerManagement
.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), module.getId()).isPresent())
.isFalse();
assertThatThrownBy(() -> controllerManagement.hasTargetArtifactAssigned(1234L, "XXX"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThatThrownBy(() -> controllerManagement.hasTargetArtifactAssigned("1234", "XXX"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse();
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse();
assertThatThrownBy(() -> controllerManagement.registerRetrieved(1234L, "test message"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThatThrownBy(() -> controllerManagement.updateControllerAttributes("1234", Maps.newHashMap()))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThatThrownBy(() -> controllerManagement.updateLastTargetQuery("1234", new URI("http://test.com")))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
}
@Test
@Description("Controller confirms successfull update with FINISHED status.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@@ -68,7 +142,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnUpdate(actionId);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.FINISHED, Action.Status.FINISHED, false);
@@ -91,7 +165,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.FINISHED, Action.Status.FINISHED, false);
@@ -111,7 +185,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs();
try {
controllerManagament.addCancelActionStatus(
controllerManagement.addCancelActionStatus(
entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
fail("Expected " + CancelActionNotAllowedException.class.getName());
} catch (final CancelActionNotAllowedException e) {
@@ -144,7 +218,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.CANCELED, Action.Status.FINISHED, false);
@@ -171,7 +245,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.CANCELED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.CANCELED, Action.Status.CANCELED, false);
@@ -199,7 +273,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament.addCancelActionStatus(
controllerManagement.addCancelActionStatus(
entityFactory.actionStatus().create(actionId).status(Action.Status.CANCEL_REJECTED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.CANCEL_REJECTED, true);
@@ -227,7 +301,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.ERROR, true);
@@ -249,22 +323,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Step
private void simulateIntermediateStatusOnCancellation(final Long actionId) {
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.RUNNING, true);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.DOWNLOAD));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.DOWNLOAD, true);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RETRIEVED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.RETRIEVED, true);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.WARNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.WARNING, true);
@@ -272,22 +346,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Step
private void simulateIntermediateStatusOnUpdate(final Long actionId) {
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.RUNNING, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.DOWNLOAD));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.DOWNLOAD, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RETRIEVED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.RETRIEVED, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.WARNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.WARNING, true);
@@ -305,7 +379,10 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final List<ActionStatus> actionStatusList = deploymentManagement.findActionStatusByAction(pageReq, actionId)
.getContent();
assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus);
if (actionActive) {
assertThat(controllerManagement.findOldestActiveActionByTarget(controllerId).get().getId())
.isEqualTo(actionId);
}
}
@Test
@@ -332,31 +409,31 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(artifact.getSha1Hash()).isEqualTo(artifact2.getSha1Hash());
assertThat(
controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
.isFalse();
savedTarget = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getAssignedEntity().iterator()
.next();
assertThat(
controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
.isTrue();
assertThat(
controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact2.getSha1Hash()))
controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact2.getSha1Hash()))
.isTrue();
}
@Test
@Description("Register a controller which does not exist")
public void findOrRegisterTargetIfItDoesNotexist() {
final Target target = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", null);
assertThat(target).as("target should not be null").isNotNull();
final Target sameTarget = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null);
final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", null);
assertThat(target).as("Target should be the equals").isEqualTo(sameTarget);
assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L);
// throws exception
try {
controllerManagament.findOrRegisterTargetIfItDoesNotexist("", null);
controllerManagement.findOrRegisterTargetIfItDoesNotexist("", null);
fail("should fail as target does not exist");
} catch (final ConstraintViolationException e) {
@@ -375,19 +452,19 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs();
// test and verify
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.RUNNING, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR,
Action.Status.ERROR, Action.Status.ERROR, false);
// try with disabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -397,7 +474,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// try with enabled late feedback - should not make a difference as it
// only allows intermediate feedbacks and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -422,7 +499,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// try with disabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -432,7 +509,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// try with enabled late feedback - should not make a difference as it
// only allows intermediate feedbacks and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -458,7 +535,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Action action = prepareFinishedUpdate();
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(action.getId()).status(Action.Status.RUNNING));
// nothing changed as "feedback after close" is disabled
@@ -483,7 +560,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
repositoryProperties.setRejectActionStatusForClosedAction(false);
Action action = prepareFinishedUpdate();
action = controllerManagament.addUpdateActionStatus(
action = controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(action.getId()).status(Action.Status.RUNNING));
// nothing changed as "feedback after close" is disabled
@@ -512,7 +589,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
private void addAttributeAndVerify(final String controllerId) {
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(1);
testData.put("test1", "testdata1");
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
assertThat(target.getTargetInfo().getControllerAttributes()).as("Controller Attributes are wrong")
@@ -523,7 +600,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
private void addSecondAttributeAndVerify(final String controllerId) {
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(2);
testData.put("test2", "testdata20");
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
testData.put("test1", "testdata1");
@@ -536,7 +613,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(2);
testData.put("test1", "testdata12");
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
testData.put("test2", "testdata20");

View File

@@ -752,7 +752,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.as("Installed distribution set of action should be null").isNotNull();
final Page<Action> updAct = actionRepository.findByDistributionSetId(pageReq, dsA.getId());
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(updAct.getContent().get(0).getId()).status(Status.FINISHED));
targ = targetManagement.findTargetByControllerID(targ.getControllerId()).get();

View File

@@ -139,7 +139,7 @@ public class ReportManagementTest extends AbstractJpaIntegrationTest {
final Target createTarget = testdataFactory.createTarget("t" + month);
final DistributionSetAssignmentResult result = assignDistributionSet(distributionSet,
Lists.newArrayList(createTarget));
controllerManagament.registerRetrieved(result.getActions().get(0),
controllerManagement.registerRetrieved(result.getActions().get(0),
"Controller retrieved update action and should start now the download.");
}
DataReportSeries<LocalDate> feedbackReceivedOverTime = reportManagement
@@ -160,7 +160,7 @@ public class ReportManagementTest extends AbstractJpaIntegrationTest {
final Target createTarget = testdataFactory.createTarget("t2" + month);
final DistributionSetAssignmentResult result = assignDistributionSet(distributionSet,
Lists.newArrayList(createTarget));
controllerManagament.registerRetrieved(result.getActions().get(0),
controllerManagement.registerRetrieved(result.getActions().get(0),
"Controller retrieved update action and should start now the download.");
}
feedbackReceivedOverTime = reportManagement.feedbackReceivedOverTime(DateTypes.perMonth(), from, to);

View File

@@ -23,10 +23,23 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
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.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.utils.MultipleInvokeHelper;
import org.eclipse.hawkbit.repository.jpa.utils.SuccessCondition;
import org.eclipse.hawkbit.repository.model.Action;
@@ -45,6 +58,8 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
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.repository.test.util.TestdataFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -138,12 +153,12 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// finish one action should be sufficient due the finish condition is at
// 50%
final JpaAction action = (JpaAction) runningActions.get(0);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED));
// check running rollouts again, now the finish condition should be hit
// and should start the next group
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// verify that now the first and the second group are in running state
final List<RolloutGroup> runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
@@ -194,7 +209,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(createdRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
return rolloutManagement.findRolloutById(createdRollout.getId()).get();
}
@@ -202,8 +217,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Step("Finish three actions of the rollout group and delete two targets")
private void finishActionAndDeleteTargetsOfFirstRunningGroup(final Rollout createdRollout) {
// finish group one by finishing targets and deleting targets
final List<Action> runningActions = actionRepository.findByRolloutIdAndStatus(createdRollout.getId(),
Status.RUNNING);
final Slice<JpaAction> runningActionsSlice = actionRepository.findByRolloutIdAndStatus(pageReq,
createdRollout.getId(), Status.RUNNING);
final List<JpaAction> runningActions = Lists.newArrayList(runningActionsSlice.iterator());
finishAction(runningActions.get(0));
finishAction(runningActions.get(1));
finishAction(runningActions.get(2));
@@ -213,7 +229,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Step("Check the status of the rollout groups, second group should be in running status")
private void checkSecondGroupStatusIsRunning(final Rollout createdRollout) {
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final List<RolloutGroup> runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
createdRollout.getId(), new OffsetBasedPageRequest(0, 10, new Sort(Direction.ASC, "id"))).getContent();
assertThat(runningRolloutGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.FINISHED);
@@ -223,8 +239,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Step("Finish one action of the rollout group and delete four targets")
private void finishActionAndDeleteTargetsOfSecondRunningGroup(final Rollout createdRollout) {
final List<Action> runningActions = actionRepository.findByRolloutIdAndStatus(createdRollout.getId(),
Status.RUNNING);
final Slice<JpaAction> runningActionsSlice = actionRepository.findByRolloutIdAndStatus(pageReq,
createdRollout.getId(), Status.RUNNING);
final List<JpaAction> runningActions = Lists.newArrayList(runningActionsSlice.iterator());
finishAction(runningActions.get(0));
targetManagement.deleteTargets(
Lists.newArrayList(runningActions.get(1).getTarget().getId(), runningActions.get(2).getTarget().getId(),
@@ -234,8 +251,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Step("Delete all targets of the rollout group")
private void deleteAllTargetsFromThirdGroup(final Rollout createdRollout) {
final List<Action> runningActions = actionRepository.findByRolloutIdAndStatus(createdRollout.getId(),
Status.SCHEDULED);
final Slice<JpaAction> runningActionsSlice = actionRepository.findByRolloutIdAndStatus(pageReq,
createdRollout.getId(), Status.SCHEDULED);
final List<JpaAction> runningActions = Lists.newArrayList(runningActionsSlice.iterator());
targetManagement.deleteTargets(Lists.newArrayList(runningActions.get(0).getTarget().getId(),
runningActions.get(1).getTarget().getId(), runningActions.get(2).getTarget().getId(),
runningActions.get(3).getTarget().getId(), runningActions.get(4).getTarget().getId()));
@@ -243,7 +261,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
@Step("Check the status of the rollout groups and the rollout")
private void verifyRolloutAndAllGroupsAreFinished(final Rollout createdRollout) {
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final List<RolloutGroup> runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
createdRollout.getId(), new OffsetBasedPageRequest(0, 10, new Sort(Direction.ASC, "id"))).getContent();
assertThat(runningRolloutGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.FINISHED);
@@ -255,7 +273,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
private void finishAction(final Action action) {
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED));
}
@@ -276,13 +294,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// finish actions with error
for (final Action action : runningActions) {
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.ERROR));
}
// check running rollouts again, now the error condition should be hit
// and should execute the error action
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()).get();
// the rollout itself should be in paused based on the error action
@@ -316,13 +334,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final List<Action> runningActions = findActionsByRolloutAndStatus(createdRollout, Status.RUNNING);
// finish actions with error
for (final Action action : runningActions) {
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.ERROR));
}
// check running rollouts again, now the error condition should be hit
// and should execute the error action
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()).get();
// the rollout itself should be in paused based on the error action
@@ -341,7 +359,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
.isEqualTo(RolloutStatus.RUNNING);
// checking rollouts again
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// next group should be running again after resuming the rollout
final List<RolloutGroup> resumedGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
@@ -367,7 +385,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// calculate the rest of the groups and finish them
for (int groupsLeft = amountGroups - 1; groupsLeft >= 1; groupsLeft--) {
// next check and start next group
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// finish running actions, 2 actions should be finished
assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2);
assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).get().getStatus())
@@ -376,7 +394,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
// check rollout to see that all actions and all groups are finished and
// so can go to FINISHED state of the rollout
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// verify all groups are in finished state
rolloutGroupManagement
@@ -409,7 +427,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(createdRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// 6 targets are ready and 2 are running
validationMap = createInitStatusMap();
@@ -418,7 +436,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// 4 targets are ready, 2 are finished and 2 are running
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L);
@@ -427,7 +445,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// 2 targets are ready, 4 are finished and 2 are running
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L);
@@ -436,7 +454,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// 0 targets are ready, 6 are finished and 2 are running
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L);
@@ -444,7 +462,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// 0 targets are ready, 8 are finished and 0 are running
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L);
@@ -472,7 +490,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(createdRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// 6 targets are ready and 2 are running
validationMap = createInitStatusMap();
@@ -481,7 +499,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
changeStatusForAllRunningActions(createdRollout, Status.ERROR);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// 6 targets are ready and 2 are error
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
@@ -502,7 +520,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
successCondition, errorCondition);
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// round(9/4)=2 targets finished (Group 1)
// round(7/3)=2 targets running (Group 3)
@@ -591,7 +609,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two", 1,
"controllerId==rollout-*", dsForRolloutTwo, "50", "80");
changeStatusForAllRunningActions(rolloutOne, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// Verify that 5 targets are finished, 5 are running and 5 are ready.
Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L);
@@ -602,7 +620,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(rolloutTwo.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// Verify that 5 targets are finished, 5 are still running and 5 are
// cancelled.
@@ -629,13 +647,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get();
changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// 9 targets are finished and 6 have error
Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
@@ -653,7 +671,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(rolloutTwo.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId()).get();
// 6 error targets are know running
@@ -687,7 +705,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get();
changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// verify: 40% error but 60% finished -> should move to next group
final List<RolloutGroup> rolloutGruops = rolloutOne.getRolloutGroups();
final Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
@@ -711,7 +729,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get();
changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// verify: 40% error and 60% finished -> should not move to next group
// because successCondition 80%
rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get();
@@ -736,7 +754,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get();
changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
// verify: 40% error -> should pause because errorCondition is 20%
rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get();
assertThat(RolloutStatus.PAUSED).isEqualTo(rolloutOne.getStatus());
@@ -753,42 +771,42 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
successCondition, errorCondition, "RolloutA", "RolloutA");
rolloutManagement.startRollout(rolloutA.getId());
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
final int amountTargetsForRollout2 = 10;
final int amountGroups2 = 2;
final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2,
successCondition, errorCondition, "RolloutB", "RolloutB");
rolloutManagement.startRollout(rolloutB.getId());
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForAllRunningActions(rolloutB, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final int amountTargetsForRollout3 = 10;
final int amountGroups3 = 2;
final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3,
successCondition, errorCondition, "RolloutC", "RolloutC");
rolloutManagement.startRollout(rolloutC.getId());
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForAllRunningActions(rolloutC, Status.ERROR);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final int amountTargetsForRollout4 = 15;
final int amountGroups4 = 3;
final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4,
successCondition, errorCondition, "RolloutD", "RolloutD");
rolloutManagement.startRollout(rolloutD.getId());
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForRunningActions(rolloutD, Status.ERROR, 1);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForAllRunningActions(rolloutD, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
final Page<Rollout> rolloutPage = rolloutManagement
.findAllRolloutsWithDetailedStatus(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")));
final Page<Rollout> rolloutPage = rolloutManagement.findAllRolloutsWithDetailedStatus(
new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), false);
final List<Rollout> rolloutList = rolloutPage.getContent();
// validate rolloutA -> 6 running and 6 ready
@@ -874,7 +892,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
final Slice<Rollout> rollout = rolloutManagement.findRolloutWithDetailedStatusByFilters(
new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%");
new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%", false);
final List<Rollout> rolloutList = rollout.getContent();
assertThat(rolloutList.size()).isEqualTo(5);
int i = 1;
@@ -915,10 +933,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(myRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
changeStatusForRunningActions(myRollout, Status.FINISHED, 2);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get();
float percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(),
@@ -926,7 +944,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(percent).isEqualTo(40);
changeStatusForRunningActions(myRollout, Status.FINISHED, 3);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(),
myRollout.getRolloutGroups().get(0).getId());
@@ -934,7 +952,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForRunningActions(myRollout, Status.FINISHED, 4);
changeStatusForAllRunningActions(myRollout, Status.ERROR);
rolloutManagement.checkRunningRollouts(0);
rolloutManagement.handleRollouts();
percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(),
myRollout.getRolloutGroups().get(1).getId());
@@ -961,7 +979,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(myRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get();
final List<RolloutGroup> rolloutGroups = myRollout.getRolloutGroups();
@@ -1063,7 +1081,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(myRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
final SuccessConditionRolloutStatus conditionRolloutStatus = new SuccessConditionRolloutStatus(
RolloutStatus.RUNNING);
@@ -1097,7 +1115,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
rolloutManagement.checkReadyRollouts(0);
rolloutManagement.handleRollouts();
myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get();
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
@@ -1105,7 +1123,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutManagement.startRollout(myRollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
final SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus(
RolloutStatus.RUNNING);
@@ -1141,7 +1159,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// schedule rollout auto start into the future
rolloutManagement.updateRollout(
entityFactory.rollout().update(myRollout.getId()).startAt(System.currentTimeMillis() + 60000));
rolloutManagement.checkReadyRollouts(0);
rolloutManagement.handleRollouts();
// rollout should not have been started
myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get();
@@ -1150,13 +1168,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// schedule to now
rolloutManagement
.updateRollout(entityFactory.rollout().update(myRollout.getId()).startAt(System.currentTimeMillis()));
rolloutManagement.checkReadyRollouts(0);
rolloutManagement.handleRollouts();
myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get();
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.STARTING);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
final SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus(
RolloutStatus.RUNNING);
@@ -1212,7 +1230,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
TimeUnit.SECONDS.sleep(1);
testdataFactory.createTargets(10, rolloutName + "-notIn-", rolloutName);
rolloutManagement.fillRolloutGroupsWithTargets(myRollout.getId());
rolloutManagement.handleRollouts();
myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get();
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
@@ -1297,6 +1315,50 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verify Exception when a Rollout groups are queried for rollout that does not exist.")
public void findRolloutGroupsForRolloutThatDoesNotExist() throws Exception {
try {
rolloutGroupManagement.findAllRolloutGroupsWithDetailedStatus(1234L, pageReq);
fail("Was able to get Rollout group for rollout that does not exist.");
} catch (final EntityNotFoundException e) {
// OK
}
}
@Test
@Description("Verify Exception when targets are queried for rollout group that does not exist.")
public void findRolloutGroupTargetsForGroupThatDoesNotExist() throws Exception {
try {
rolloutGroupManagement.findRolloutGroupTargets(1234L, pageReq);
fail("Was able to get Rollout group targets for rollout group that does not exist.");
} catch (final EntityNotFoundException e) {
// OK
}
}
@Test
@Description("Verify Exception when targets are queried for rollout group that does not exist.")
public void findRolloutGroupTargetWithActionsForGroupThatDoesNotExist() throws Exception {
try {
rolloutGroupManagement.findAllTargetsWithActionStatus(pageReq, 1234L);
fail("Was able to get Rollout group targets for rollout group that does not exist.");
} catch (final EntityNotFoundException e) {
// OK
}
}
@Test
@Description("Verify Exception when targets are counted for rollout group that does not exist.")
public void countRolloutGroupTargetWithActionsForGroupThatDoesNotExist() throws Exception {
try {
rolloutGroupManagement.countTargetsOfRolloutsGroup(1234L);
fail("Was able to count Rollout group targets for rollout group that does not exist.");
} catch (final EntityNotFoundException e) {
// OK
}
}
@Test
@Description("Verify the start of a Rollout does not work during creation phase.")
public void createAndStartRolloutDuringCreationFails() throws Exception {
@@ -1331,6 +1393,83 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@ExpectEvents({ @Expect(type = RolloutDeletedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 25), @Expect(type = RolloutUpdatedEvent.class, count = 2),
@Expect(type = RolloutGroupCreatedEvent.class, count = 5),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = RolloutGroupUpdatedEvent.class, count = 10) })
public void deleteRolloutWhichHasNeverStartedIsHardDeleted() {
final int amountTargetsForRollout = 10;
final int amountOtherTargets = 15;
final int amountGroups = 5;
final String successCondition = "50";
final String errorCondition = "80";
final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
amountOtherTargets, amountGroups, successCondition, errorCondition);
// test
rolloutManagement.deleteRollout(createdRollout.getId());
rolloutManagement.handleRollouts();
// verify
final JpaRollout deletedRollout = rolloutRepository.findOne(createdRollout.getId());
assertThat(deletedRollout).isNull();
assertThat(rolloutGroupRepository.count()).isZero();
assertThat(rolloutTargetGroupRepository.count()).isZero();
}
@Test
@ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = RolloutGroupUpdatedEvent.class, count = 16),
@Expect(type = RolloutUpdatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 25), @Expect(type = TargetUpdatedEvent.class, count = 4),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = RolloutGroupCreatedEvent.class, count = 5),
@Expect(type = ActionCreatedEvent.class, count = 10), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = RolloutDeletedEvent.class, count = 1) })
public void deleteRolloutWhichHasBeenStartedBeforeIsSoftDeleted() {
final int amountTargetsForRollout = 10;
final int amountOtherTargets = 15;
final int amountGroups = 5;
final String successCondition = "50";
final String errorCondition = "80";
final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
amountOtherTargets, amountGroups, successCondition, errorCondition);
// start the rollout, so it has active running actions and a group which
// has been started
rolloutManagement.startRollout(createdRollout.getId());
rolloutManagement.handleRollouts();
// verify we have running actions
assertThat(actionRepository.findByRolloutIdAndStatus(pageReq, createdRollout.getId(), Status.RUNNING)
.getNumberOfElements()).isEqualTo(2);
// test
rolloutManagement.deleteRollout(createdRollout.getId());
rolloutManagement.handleRollouts();
// verify
final JpaRollout deletedRollout = rolloutRepository.findOne(createdRollout.getId());
assertThat(deletedRollout).isNotNull();
assertThat(deletedRollout.getStatus()).isEqualTo(RolloutStatus.DELETED);
assertThat(rolloutManagement.findAll(pageReq, true).getContent()).hasSize(1);
assertThat(rolloutManagement.findAll(pageReq, false).getContent()).hasSize(0);
assertThat(rolloutGroupManagement.findAllRolloutGroupsWithDetailedStatus(createdRollout.getId(), pageReq)
.getContent()).hasSize(amountGroups);
// verify that all scheduled actions are deleted
assertThat(actionRepository.findByRolloutIdAndStatus(pageReq, deletedRollout.getId(), Status.SCHEDULED)
.getNumberOfElements()).isEqualTo(0);
// verify that all running actions keep running
assertThat(actionRepository.findByRolloutIdAndStatus(pageReq, deletedRollout.getId(), Status.RUNNING)
.getNumberOfElements()).isEqualTo(2);
}
private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage,
final String targetFilter) {
return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc")
@@ -1397,7 +1536,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) {
final List<Action> runningActions = findActionsByRolloutAndStatus(rollout, Status.RUNNING);
for (final Action action : runningActions) {
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(status));
}
return runningActions.size();
@@ -1408,7 +1547,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final List<Action> runningActions = findActionsByRolloutAndStatus(rollout, Status.RUNNING);
assertThat(runningActions.size()).isGreaterThanOrEqualTo(amountOfTargetsToGetChanged);
for (int i = 0; i < amountOfTargetsToGetChanged; i++) {
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(runningActions.get(i).getId()).status(status));
}
return runningActions.size();

View File

@@ -96,7 +96,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
final Long actionId = assignDistributionSet(installedSet.getId(), assignedC).getActions().get(0);
// set one installed DS also
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(actionId).status(Status.FINISHED).message("message"));
assignDistributionSet(setA.getId(), installedC);
@@ -663,7 +663,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
List<Target> installedtargets = testdataFactory.createTargets(10, "assigned", "assigned");
// set on installed and assign another one
assignDistributionSet(installedSet, installedtargets).getActions().forEach(actionId -> controllerManagament
assignDistributionSet(installedSet, installedtargets).getActions().forEach(actionId -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
assignDistributionSet(assignedSet, installedtargets);

View File

@@ -248,7 +248,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
testData.put("test1", "testdata1");
targetManagement.createTarget(entityFactory.target().create().controllerId(controllerId));
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
assertThat(target.getTargetInfo().getControllerAttributes()).as("Controller Attributes are wrong")
@@ -279,11 +279,11 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
Target target = createTargetWithAttributes("4711");
final long current = System.currentTimeMillis();
controllerManagament.updateLastTargetQuery("4711", null);
controllerManagement.updateLastTargetQuery("4711", null);
final DistributionSetAssignmentResult result = assignDistributionSet(set.getId(), "4711");
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(result.getActions().get(0)).status(Status.FINISHED));
assignDistributionSet(set2.getId(), "4711");
@@ -694,7 +694,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetCreatedEvent.class, count = 0)
public void targetCanBeReadWithOnlyReadTargetPermission() throws Exception {
final String knownTargetControllerId = "readTarget";
controllerManagament.findOrRegisterTargetIfItDoesNotexist(knownTargetControllerId, new URI("http://127.0.0.1"));
controllerManagement.findOrRegisterTargetIfItDoesNotexist(knownTargetControllerId, new URI("http://127.0.0.1"));
securityRule.runAs(WithSpringAuthorityRule.withUser("bumlux", "READ_TARGET"), () -> {
final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownTargetControllerId)

View File

@@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.event.TenantAwareEvent;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
@@ -27,6 +28,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.event.RepositoryEntityEventTest.RepositoryTestConfiguration;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Before;
@@ -90,6 +92,30 @@ public class RepositoryEntityEventTest extends AbstractJpaIntegrationTest {
assertThat(targetDeletedEvent.getEntityId()).isEqualTo(createdTarget.getId());
}
@Test
@Description("Verifies that the rollout deleted event is published when a rollout has been deleted")
public void rolloutDeletedEventIsPublished() throws InterruptedException {
final int amountTargetsForRollout = 500;
final int amountGroups = 5;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTest";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
final Rollout createdRollout = createRolloutByVariables(rolloutName, "desc", amountGroups,
"controllerId==" + targetPrefixName + "-*", distributionSet, successCondition, errorCondition);
rolloutManagement.deleteRollout(createdRollout.getId());
rolloutManagement.handleRollouts();
final RolloutDeletedEvent rolloutDeletedEvent = eventListener.waitForEvent(RolloutDeletedEvent.class, 1,
TimeUnit.SECONDS);
assertThat(rolloutDeletedEvent).isNotNull();
assertThat(rolloutDeletedEvent.getEntityId()).isEqualTo(createdRollout.getId());
}
@Test
@Description("Verifies that the distribution set created event is published when a distribution set has been created")
public void distributionSetCreatedEventIsPublished() throws InterruptedException {

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.junit.Before;
import org.junit.Test;
@@ -43,14 +44,17 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
action = new JpaAction();
action.setActionType(ActionType.SOFT);
action.setDistributionSet(dsA);
target.addAction(action);
action.setTarget(target);
action.setStatus(Status.RUNNING);
target.addAction(action);
actionRepository.save(action);
for (int i = 0; i < 10; i++) {
final JpaAction newAction = new JpaAction();
newAction.setActionType(ActionType.SOFT);
newAction.setDistributionSet(dsA);
newAction.setActive(i % 2 == 0);
newAction.setStatus(Status.RUNNING);
newAction.setTarget(target);
actionRepository.save(newAction);
target.addAction(newAction);

View File

@@ -48,13 +48,13 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
target = targetManagement.createTarget(entityFactory.target().create().controllerId("targetId123")
.name("targetName123").description("targetDesc123"));
attributes.put("revision", "1.1");
target = controllerManagament.updateControllerAttributes(target.getControllerId(), attributes);
target = controllerManagement.updateControllerAttributes(target.getControllerId(), attributes);
target2 = targetManagement
.createTarget(entityFactory.target().create().controllerId("targetId1234").description("targetId1234"));
attributes.put("revision", "1.2");
Thread.sleep(1);
target2 = controllerManagament.updateControllerAttributes(target2.getControllerId(), attributes);
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), attributes);
testdataFactory.createTarget("targetId1235");
testdataFactory.createTarget("targetId1236");