Feature - TargetType compatibility check (#1180)

* added targettype compatibility check in deployment

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* added targettype compatibility for autssignment

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* added / fixed tests for auto assignment compatibility check

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* adapted rollout creation to use JPA specifications for compatibility checks

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fix unit tests and javadoc

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fix copyright header

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed review findings

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed review findings

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* replaced validated-DS management calls

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* improved compatibility check in DeploymentManagementTest

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed review findings

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>
This commit is contained in:
Robert Sing
2021-10-15 15:39:21 +02:00
committed by GitHub
parent 39ccf65f2e
commit 76be812cf1
16 changed files with 857 additions and 502 deletions

View File

@@ -42,6 +42,7 @@ import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.DistributionSetTypeNotInTargetTypeException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
@@ -71,6 +72,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.Cancelat
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
@@ -229,10 +231,60 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
deploymentRequests = deploymentRequests.stream().distinct().collect(Collectors.toList());
checkIfRequiresMultiAssignment(deploymentRequests);
}
checkForTargetTypeCompatibility(deploymentRequests);
checkQuotaForAssignment(deploymentRequests);
return deploymentRequests;
}
private void checkForTargetTypeCompatibility(final List<DeploymentRequest> deploymentRequests) {
final List<String> controllerIds = deploymentRequests.stream().map(DeploymentRequest::getControllerId)
.distinct().collect(Collectors.toList());
final List<Long> distSetIds = deploymentRequests.stream().map(DeploymentRequest::getDistributionSetId)
.distinct().collect(Collectors.toList());
if (controllerIds.size() > 1 && distSetIds.size() > 1) {
throw new IllegalStateException(
"Assigning multiple Targets to multiple Distribution Sets simultaneously is not allowed!");
}
if (distSetIds.size() == 1) {
checkCompatibilityForSingleDsAssignment(distSetIds.iterator().next(), controllerIds);
} else {
checkCompatibilityForMultiDsAssignment(controllerIds.iterator().next(), distSetIds);
}
}
private void checkCompatibilityForSingleDsAssignment(final Long distSetId, final List<String> controllerIds) {
final DistributionSetType distSetType = distributionSetManagement.getValidAndComplete(distSetId).getType();
final Set<Long> incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT)
.stream()
.map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids)
.and(TargetSpecifications.notCompatibleWithDistributionSetType(distSetType.getId()))))
.flatMap(List::stream).map(Target::getTargetType).map(TargetType::getId).collect(Collectors.toSet());
if (!incompatibleTargetTypes.isEmpty()) {
throw new DistributionSetTypeNotInTargetTypeException(distSetType.getId(), incompatibleTargetTypes);
}
}
private void checkCompatibilityForMultiDsAssignment(final String controllerId, final List<Long> distSetIds) {
final Target target = targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
if (target.getTargetType() != null) {
// we assume that list of assigned DS is less than MAX_ENTRIES_IN_STATEMENT
final Set<DistributionSetType> incompatibleDistSetTypes = distributionSetManagement.get(distSetIds).stream()
.map(DistributionSet::getType).collect(Collectors.toSet());
incompatibleDistSetTypes.removeAll(target.getTargetType().getCompatibleDistributionSetTypes());
if (!incompatibleDistSetTypes.isEmpty()) {
final Set<Long> distSetTypeIds = incompatibleDistSetTypes.stream().map(DistributionSetType::getId)
.collect(Collectors.toSet());
throw new DistributionSetTypeNotInTargetTypeException(distSetTypeIds, target.getTargetType().getId());
}
}
}
private static Map<Long, List<TargetWithActionType>> convertRequest(
final Collection<DeploymentRequest> deploymentRequests) {
return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId,
@@ -256,8 +308,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
* method assigns the {@link DistributionSet} to all {@link Target}s by their
* IDs with a specific {@link ActionType} and {@code forcetime}.
*
*
* In case the update was executed offline (i.e. not managed by hawkBit) the
@@ -265,8 +317,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* A. it ignores targets completely that are in
* {@link TargetUpdateStatus#PENDING}.<br/>
* B. it created completed actions.<br/>
* C. sets both installed and assigned DS on the target and switches the
* status to {@link TargetUpdateStatus#IN_SYNC} <br/>
* C. sets both installed and assigned DS on the target and switches the status
* to {@link TargetUpdateStatus#IN_SYNC} <br/>
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
*
* @param initiatedBy
@@ -281,9 +333,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* the assignment strategy (online /offline)
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
* {@link SoftwareModuleType} are not assigned as define by the
* {@link DistributionSetType}.
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsID,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
@@ -351,9 +403,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
/**
* split tIDs length into max entries in-statement because many database
* have constraint of max entries in in-statements e.g. Oracle with maximum
* 1000 elements, so we need to split the entries here and execute multiple
* split tIDs length into max entries in-statement because many database have
* constraint of max entries in in-statements e.g. Oracle with maximum 1000
* elements, so we need to split the entries here and execute multiple
* statements
*/
private static List<List<Long>> getTargetEntitiesAsChunks(final List<JpaTarget> targetEntities) {
@@ -813,10 +865,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return 0;
}
/*
* We use a native query here because Spring JPA does not support to
* specify a LIMIT clause on a DELETE statement. However, for this
* specific use case (action cleanup), we must specify a row limit to
* reduce the overall load on the database.
* We use a native query here because Spring JPA does not support to specify a
* LIMIT clause on a DELETE statement. However, for this specific use case
* (action cleanup), we must specify a row limit to reduce the overall load on
* the database.
*/
final int statusCount = status.size();

View File

@@ -530,7 +530,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final long targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
count -> targetManagement.countByRsqlAndNotInRolloutGroups(readyGroups, groupTargetFilter));
count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatible(readyGroups, groupTargetFilter,
rollout.getDistributionSet().getType()));
final long expectedInGroup = Math
.round((double) (group.getTargetPercentage() / 100) * (double) targetsInGroupFilter);
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
@@ -574,8 +575,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
RolloutGroupStatus.READY, group);
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroups(pageRequest,
readyGroups, targetFilter);
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
createAssignmentOfTargetsToGroup(targets, group);
@@ -584,8 +585,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}
/**
* Schedules a group of the rollout. Scheduled Actions are created to
* achieve this. The creation of those Actions is allowed to fail.
* Schedules a group of the rollout. Scheduled Actions are created to achieve
* this. The creation of those Actions is allowed to fail.
*/
private boolean scheduleRolloutGroup(final JpaRollout rollout, final JpaRolloutGroup group) {
final long targetsInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
@@ -648,8 +649,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
/**
* Creates an action entry into the action repository. In case of existing
* scheduled actions the scheduled actions gets canceled. A scheduled action
* is created in-active.
* scheduled actions the scheduled actions gets canceled. A scheduled action is
* created in-active.
*/
private void createScheduledAction(final Collection<Target> targets, final DistributionSet distributionSet,
final ActionType actionType, final Long forcedTime, final Rollout rollout,

View File

@@ -27,6 +27,7 @@ import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields;
@@ -57,6 +58,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
@@ -84,8 +86,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
/**
* JPA implementation of {@link TargetManagement}.
*
@@ -96,6 +96,8 @@ public class JpaTargetManagement implements TargetManagement {
private final EntityManager entityManager;
private final DistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement;
private final TargetRepository targetRepository;
@@ -106,8 +108,6 @@ public class JpaTargetManagement implements TargetManagement {
private final RolloutGroupRepository rolloutGroupRepository;
private final DistributionSetRepository distributionSetRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetTagRepository targetTagRepository;
@@ -122,22 +122,22 @@ public class JpaTargetManagement implements TargetManagement {
private final Database database;
public JpaTargetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
public JpaTargetManagement(final EntityManager entityManager,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
this.entityManager = entityManager;
this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
this.targetRepository = targetRepository;
this.targetTypeRepository = targetTypeRepository;
this.targetMetadataRepository = targetMetadataRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.distributionSetRepository = distributionSetRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetTagRepository = targetTagRepository;
this.eventPublisherHolder = eventPublisherHolder;
@@ -383,17 +383,16 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public Page<Target> findByAssignedDistributionSet(final Pageable pageReq, final long distributionSetID) {
throwEntityNotFoundIfDsDoesNotExist(distributionSetID);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
return convertPage(
targetRepository.findAll(TargetSpecifications.hasAssignedDistributionSet(distributionSetID), pageReq),
pageReq);
return convertPage(targetRepository
.findAll(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId()), pageReq), pageReq);
}
@Override
public Page<Target> findByAssignedDistributionSetAndRsql(final Pageable pageReq, final long distributionSetID,
final String rsqlParam) {
throwEntityNotFoundIfDsDoesNotExist(distributionSetID);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class,
virtualPropertyReplacer, database);
@@ -401,18 +400,12 @@ public class JpaTargetManagement implements TargetManagement {
return convertPage(
targetRepository
.findAll((Specification<JpaTarget>) (root, query,
cb) -> cb.and(TargetSpecifications.hasAssignedDistributionSet(distributionSetID)
cb) -> cb.and(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId())
.toPredicate(root, query, cb), spec.toPredicate(root, query, cb)),
pageReq),
pageReq);
}
private void throwEntityNotFoundIfDsDoesNotExist(final Long distributionSetID) {
if (!distributionSetRepository.existsById(distributionSetID)) {
throw new EntityNotFoundException(DistributionSet.class, distributionSetID);
}
}
public static Page<Target> convertPage(final Page<JpaTarget> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
@@ -423,16 +416,15 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public Page<Target> findByInstalledDistributionSet(final Pageable pageReq, final long distributionSetID) {
throwEntityNotFoundIfDsDoesNotExist(distributionSetID);
return convertPage(
targetRepository.findAll(TargetSpecifications.hasInstalledDistributionSet(distributionSetID), pageReq),
pageReq);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
return convertPage(targetRepository
.findAll(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId()), pageReq), pageReq);
}
@Override
public Page<Target> findByInstalledDistributionSetAndRsql(final Pageable pageable, final long distributionSetId,
final String rsqlParam) {
throwEntityNotFoundIfDsDoesNotExist(distributionSetId);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class,
virtualPropertyReplacer, database);
@@ -440,7 +432,7 @@ public class JpaTargetManagement implements TargetManagement {
return convertPage(
targetRepository
.findAll((Specification<JpaTarget>) (root, query,
cb) -> cb.and(TargetSpecifications.hasInstalledDistributionSet(distributionSetId)
cb) -> cb.and(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId())
.toPredicate(root, query, cb), spec.toPredicate(root, query, cb)),
pageable),
pageable);
@@ -476,10 +468,10 @@ public class JpaTargetManagement implements TargetManagement {
specList.add(TargetSpecifications.isOverdue(TimestampCalculator.calculateOverdueTimestamp()));
}
if (filterParams.getFilterByDistributionId() != null) {
throwEntityNotFoundIfDsDoesNotExist(filterParams.getFilterByDistributionId());
final DistributionSet validDistSet = distributionSetManagement
.getOrElseThrowException(filterParams.getFilterByDistributionId());
specList.add(TargetSpecifications
.hasInstalledOrAssignedDistributionSet(filterParams.getFilterByDistributionId()));
specList.add(TargetSpecifications.hasInstalledOrAssignedDistributionSet(validDistSet.getId()));
}
if (!StringUtils.isEmpty(filterParams.getFilterBySearchText())) {
specList.add(TargetSpecifications
@@ -669,51 +661,53 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public long countByAssignedDistributionSet(final long distId) {
throwEntityNotFoundIfDsDoesNotExist(distId);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId));
return targetRepository.count(TargetSpecifications.hasAssignedDistributionSet(distId));
return targetRepository.count(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId()));
}
@Override
public long countByInstalledDistributionSet(final long distId) {
throwEntityNotFoundIfDsDoesNotExist(distId);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId));
return targetRepository.count(TargetSpecifications.hasInstalledDistributionSet(distId));
return targetRepository.count(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId()));
}
@Override
public boolean existsByInstalledOrAssignedDistributionSet(final long distId) {
throwEntityNotFoundIfDsDoesNotExist(distId);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId));
return targetRepository.exists(TargetSpecifications.hasInstalledOrAssignedDistributionSet(distId));
return targetRepository
.exists(TargetSpecifications.hasInstalledOrAssignedDistributionSet(validDistSet.getId()));
}
@Override
public Page<Target> findByTargetFilterQueryAndNonDS(final Pageable pageRequest, final long distributionSetId,
final String targetFilterQuery) {
throwEntityNotFoundIfDsDoesNotExist(distributionSetId);
public Page<Target> findByTargetFilterQueryAndNonDSAndCompatible(final Pageable pageRequest,
final long distributionSetId, final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId();
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database);
final Specification<JpaTarget> dsNotInActions = TargetSpecifications
.hasNotDistributionSetInActions(distributionSetId);
final Specification<JpaTarget> isCompatibleWithDsType = TargetSpecifications
.isCompatibleWithDistributionSetType(distSetTypeId);
return findTargetsBySpec(
(root, cq,
cb) -> cb.and(spec.toPredicate(root, cq, cb), TargetSpecifications
.hasNotDistributionSetInActions(distributionSetId).toPredicate(root, cq, cb)),
pageRequest);
return findTargetsBySpec(spec.and(dsNotInActions).and(isCompatibleWithDsType), pageRequest);
}
@Override
public Page<Target> findByTargetFilterQueryAndNotInRolloutGroups(final Pageable pageRequest,
final Collection<Long> groups, final String targetFilterQuery) {
public Page<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(final Pageable pageRequest,
final Collection<Long> groups, final String targetFilterQuery, final DistributionSetType dsType) {
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database);
final Specification<JpaTarget> notInRolloutGroups = TargetSpecifications.isNotInRolloutGroups(groups);
final Specification<JpaTarget> isCompatibleWithDsType = TargetSpecifications
.isCompatibleWithDistributionSetType(dsType.getId());
return findTargetsBySpec((root, cq, cb) -> cb.and(spec.toPredicate(root, cq, cb),
TargetSpecifications.isNotInRolloutGroups(groups).toPredicate(root, cq, cb)), pageRequest);
return findTargetsBySpec((spec.and(notInRolloutGroups).and(isCompatibleWithDsType)), pageRequest);
}
@Override
@@ -728,24 +722,27 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
public long countByRsqlAndNotInRolloutGroups(final Collection<Long> groups, final String targetFilterQuery) {
public long countByRsqlAndNotInRolloutGroupsAndCompatible(final Collection<Long> groups,
final String targetFilterQuery, final DistributionSetType dsType) {
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database);
final List<Specification<JpaTarget>> specList = Arrays.asList(spec,
TargetSpecifications.isNotInRolloutGroups(groups));
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()));
return countByCriteriaAPI(specList);
}
@Override
public long countByRsqlAndNonDS(final long distributionSetId, final String targetFilterQuery) {
throwEntityNotFoundIfDsDoesNotExist(distributionSetId);
public long countByRsqlAndNonDSAndCompatible(final long distributionSetId, final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId();
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database);
final List<Specification<JpaTarget>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add(spec);
specList.add(TargetSpecifications.hasNotDistributionSetInActions(distributionSetId));
final List<Specification<JpaTarget>> specList = Arrays.asList(spec,
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId));
return countByCriteriaAPI(specList);
}

View File

@@ -244,8 +244,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* @param dsTypeManagement
* for loading
* {@link TargetType#getCompatibleDistributionSetTypes()}
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* @return TargetTypeBuilder bean
*/
@Bean
@@ -261,9 +260,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* @param softwareManagement
* for loading
* {@link DistributionSetType#getMandatoryModuleTypes()} and
* {@link DistributionSetType#getOptionalModuleTypes()}
* for loading {@link DistributionSetType#getMandatoryModuleTypes()}
* and {@link DistributionSetType#getOptionalModuleTypes()}
* @return DistributionSetTypeBuilder bean
*/
@Bean
@@ -305,8 +303,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* @return the {@link SystemSecurityContext} singleton bean which make it
* accessible in beans which cannot access the service directly,
* e.g. JPA entities.
* accessible in beans which cannot access the service directly, e.g.
* JPA entities.
*/
@Bean
SystemSecurityContextHolder systemSecurityContextHolder() {
@@ -314,9 +312,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @return the {@link TenantConfigurationManagement} singleton bean which
* make it accessible in beans which cannot access the service
* directly, e.g. JPA entities.
* @return the {@link TenantConfigurationManagement} singleton bean which make
* it accessible in beans which cannot access the service directly, e.g.
* JPA entities.
*/
@Bean
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
@@ -325,9 +323,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* @return the {@link SystemManagementHolder} singleton bean which holds the
* current {@link SystemManagement} service and make it accessible
* in beans which cannot access the service directly, e.g. JPA
* entities.
* current {@link SystemManagement} service and make it accessible in
* beans which cannot access the service directly, e.g. JPA entities.
*/
@Bean
SystemManagementHolder systemManagementHolder() {
@@ -335,10 +332,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @return the {@link TenantAwareHolder} singleton bean which holds the
* current {@link TenantAware} service and make it accessible in
* beans which cannot access the service directly, e.g. JPA
* entities.
* @return the {@link TenantAwareHolder} singleton bean which holds the current
* {@link TenantAware} service and make it accessible in beans which
* cannot access the service directly, e.g. JPA entities.
*/
@Bean
TenantAwareHolder tenantAwareHolder() {
@@ -346,10 +342,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which
* holds the current {@link SecurityTokenGenerator} service and make
* it accessible in beans which cannot access the service via
* injection
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which holds
* the current {@link SecurityTokenGenerator} service and make it
* accessible in beans which cannot access the service via injection
*/
@Bean
SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
@@ -532,25 +527,24 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* {@link JpaTenantConfigurationManagement} bean.
* {@link JpaTargetManagement} bean.
*
* @return a new {@link TenantConfigurationManagement}
* @return a new {@link JpaTargetManagement}
*/
@Bean
@ConditionalOnMissingBean
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware,
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties) {
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository,
targetMetadataRepository, rolloutGroupRepository, distributionSetRepository,
targetFilterQueryRepository, targetTagRepository, eventPublisherHolder, tenantAware, afterCommit,
virtualPropertyReplacer, properties.getDatabase());
final JpaProperties properties, final DistributionSetManagement distributionSetManagement) {
return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository,
targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTagRepository, eventPublisherHolder, tenantAware, afterCommit, virtualPropertyReplacer,
properties.getDatabase());
}
/**

View File

@@ -84,14 +84,15 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor {
do {
final List<String> controllerIds = targetManagement
.findByTargetFilterQueryAndNonDS(PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT),
.findByTargetFilterQueryAndNonDSAndCompatible(
PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT),
targetFilterQuery.getAutoAssignDistributionSet().getId(), targetFilterQuery.getQuery())
.getContent().stream().map(Target::getControllerId).collect(Collectors.toList());
count = runTransactionalAssignment(targetFilterQuery, controllerIds);
} while (count == Constants.MAX_ENTRIES_IN_STATEMENT);
} catch (PersistenceException | AbstractServerRtException e) {
} catch (final PersistenceException | AbstractServerRtException e) {
LOGGER.error("Error during auto assign check of target filter query " + targetFilterQuery.getId(), e);
}

View File

@@ -13,6 +13,7 @@ import java.util.Collection;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.MapJoin;
@@ -25,18 +26,24 @@ import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.jpa.domain.Specification;
@@ -150,7 +157,7 @@ public final class TargetSpecifications {
*
* @param updateStatus
* to be filtered on
*
*
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasTargetUpdateStatus(final Collection<TargetUpdateStatus> updateStatus) {
@@ -176,7 +183,7 @@ public final class TargetSpecifications {
*
* @param updateStatus
* to be filtered on
*
*
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> notEqualToTargetUpdateStatus(final TargetUpdateStatus updateStatus) {
@@ -185,15 +192,14 @@ public final class TargetSpecifications {
/**
* {@link Specification} for retrieving {@link Target}s that are overdue. A
* target is overdue if it did not respond during the configured
* intervals:<br>
* target is overdue if it did not respond during the configured intervals:<br>
* <em>poll_itvl + overdue_itvl</em>
*
* @param overdueTimestamp
* the calculated timestamp to compare with the last respond of a
* target (lastTargetQuery).<br>
* The <code>overdueTimestamp</code> has to be calculated with
* the following expression:<br>
* The <code>overdueTimestamp</code> has to be calculated with the
* following expression:<br>
* <em>overdueTimestamp = nowTimestamp - poll_itvl -
* overdue_itvl</em>
*
@@ -205,8 +211,8 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s by "like
* controllerId or like name or like description".
* {@link Specification} for retrieving {@link Target}s by "like controllerId or
* like name or like description".
*
* @param searchText
* to be filtered on
@@ -240,8 +246,8 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s by "like
* controllerId or like name or like description or like attribute value".
* {@link Specification} for retrieving {@link Target}s by "like controllerId or
* like name or like description or like attribute value".
*
* @param searchText
* to be filtered on
@@ -252,8 +258,7 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s by "like
* controllerId".
* {@link Specification} for retrieving {@link Target}s by "like controllerId".
*
* @param distributionId
* to be filtered on
@@ -264,8 +269,8 @@ public final class TargetSpecifications {
}
/**
* Finds all targets by given {@link Target#getControllerId()}s and which
* are not yet assigned to given {@link DistributionSet}.
* Finds all targets by given {@link Target#getControllerId()}s and which are
* not yet assigned to given {@link DistributionSet}.
*
* @param tIDs
* to search for.
@@ -298,8 +303,8 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s by "has no tag
* names"or "has at least on of the given tag names".
* {@link Specification} for retrieving {@link Target}s by "has no tag names"or
* "has at least on of the given tag names".
*
* @param tagNames
* to be filtered on
@@ -341,8 +346,8 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s by assigned
* distribution set.
* {@link Specification} for retrieving {@link Target}s by assigned distribution
* set.
*
* @param distributionSetId
* the ID of the distribution set which must be assigned
@@ -372,6 +377,57 @@ public final class TargetSpecifications {
};
}
/**
* {@link Specification} for retrieving {@link Target}s that are compatible with
* given {@link DistributionSetType}. Compatibility is evaluated by checking the
* {@link TargetType} of a target. Targets that don't have a {@link TargetType}
* are compatible with all {@link DistributionSetType}
*
* @param distributionSetTypeId
* the ID of the distribution set type which must be compatible
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> isCompatibleWithDistributionSetType(final Long distributionSetTypeId) {
return (targetRoot, query, cb) -> {
// Since the targetRoot is changed by joining we need to get the
// isNull predicate first
final Predicate targetTypeIsNull = targetRoot.get(JpaTarget_.targetType).isNull();
return cb.or(targetTypeIsNull, getDistSetTypeEqualPredicate(targetRoot, cb, distributionSetTypeId));
};
}
/**
* {@link Specification} for retrieving {@link Target}s that are NOT compatible
* with given {@link DistributionSetType}. Compatibility is evaluated by
* checking the {@link TargetType} of a target. Targets that don't have a
* {@link TargetType} are compatible with all {@link DistributionSetType}
*
* @param distributionSetTypeId
* the ID of the distribution set type which must be incompatible
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> notCompatibleWithDistributionSetType(final Long distributionSetTypeId) {
return (targetRoot, query, cb) -> {
// Since the targetRoot is changed by joining we need to get the
// isNotNull predicate first
final Predicate targetTypeNotNull = targetRoot.get(JpaTarget_.targetType).isNotNull();
return cb.and(targetTypeNotNull,
cb.isNull(getDistSetTypeEqualPredicate(targetRoot, cb, distributionSetTypeId)));
};
}
private static Predicate getDistSetTypeEqualPredicate(final Root<JpaTarget> root, final CriteriaBuilder cb,
final Long dsTypeId) {
final Join<JpaTarget, JpaTargetType> targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT);
targetTypeJoin.fetch(JpaTargetType_.distributionSetTypes);
final SetJoin<JpaTargetType, JpaDistributionSetType> dsTypeTargetTypeJoin = targetTypeJoin
.join(JpaTargetType_.distributionSetTypes, JoinType.LEFT);
return cb.equal(dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id), dsTypeId);
}
/**
* {@link Specification} for retrieving {@link Target}s that are in a given
* {@link RolloutGroup}
@@ -423,8 +479,8 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s that have no Action
* of the {@link RolloutGroup}.
* {@link Specification} for retrieving {@link Target}s that have no Action of
* the {@link RolloutGroup}.
*
* @param group
* the {@link RolloutGroup}
@@ -445,8 +501,8 @@ public final class TargetSpecifications {
}
/**
* {@link Specification} for retrieving {@link Target}s by assigned
* distribution set.
* {@link Specification} for retrieving {@link Target}s by assigned distribution
* set.
*
* @param distributionSetId
* the ID of the distribution set which must be assigned
@@ -471,4 +527,14 @@ public final class TargetSpecifications {
return cb.equal(tags.get(JpaTargetTag_.id), tagId);
};
}
/**
* {@link Specification} for retrieving {@link Target}s that have a
* {@link org.eclipse.hawkbit.repository.model.TargetType} assigned
*
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasTargetType() {
return (targetRoot, query, cb) -> cb.isNotNull(targetRoot.get(JpaTarget_.targetType));
}
}