diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java index 0d8888deb..dd780f30f 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java @@ -161,15 +161,15 @@ public enum SpServerError { "Storage quota will be exceeded if file is uploaded."), /** - * error message, which describes that the action can not be canceled cause - * the action is inactive. + * error message, which describes that the action can not be canceled cause the + * action is inactive. */ SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable", "Only active actions which are in status pending are cancelable."), /** - * error message, which describes that the action can not be force quit - * cause the action is inactive. + * error message, which describes that the action can not be force quit cause + * the action is inactive. */ SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable", "Only active actions which are in status pending can be force quit."), @@ -250,8 +250,7 @@ public enum SpServerError { "Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future."), /** - * Error message informing that the action type for auto-assignment is - * invalid. + * Error message informing that the action type for auto-assignment is invalid. */ SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType", "The given action type for auto-assignment is invalid: allowed values are ['forced', 'soft', 'downloadonly']"), @@ -277,6 +276,9 @@ public enum SpServerError { SP_TARGET_TYPE_IN_USE("hawkbit.server.error.target.type.used", "Target type is still in use by a target."), + SP_TARGET_TYPE_INCOMPATIBLE("hawkbit.server.error.target.type.incompatible", + "Target type of target is not compatible with distribution set."), + SP_STOP_ROLLOUT_FAILED("hawkbit.server.error.stopRolloutFailed", "Stopping the rollout failed"); private final String key; diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index f42abb3ed..538b0b429 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -181,8 +181,8 @@ public interface RolloutManagement { RolloutGroupConditions conditions); /** - * Calculates how many targets are addressed by each rollout group and - * returns the validation information. + * Calculates how many targets are addressed by each rollout group and returns + * the validation information. * * @param groups * a list of rollout groups @@ -190,6 +190,8 @@ public interface RolloutManagement { * the rollout * @param createdAt * timestamp when the rollout was created + * @param dsTypeId + * ID of the type of distribution set of the rollout * @return the validation information * @throws RolloutIllegalStateException * thrown when no targets are targeted by the rollout @@ -199,7 +201,7 @@ public interface RolloutManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ) ListenableFuture validateTargetsInGroups(@Valid List groups, - String targetFilter, Long createdAt); + String targetFilter, Long createdAt, @NotNull Long dsTypeId); /** * Retrieves all rollouts. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 17ce5133c..61b942af5 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -102,8 +102,8 @@ public interface TargetManagement { * @param selectTargetWithNoTag * flag to select targets with no tag assigned * - * @return the found number {@link Target}s - * + * @return the found number of {@link Target}s + * * @throws EntityNotFoundException * if distribution set with given ID does not exist */ @@ -117,7 +117,7 @@ public interface TargetManagement { * @param distId * to search for * @return number of found {@link Target}s. - * + * * @throws EntityNotFoundException * if distribution set with given ID does not exist */ @@ -144,19 +144,33 @@ public interface TargetManagement { * * @param rsqlParam * filter definition in RSQL syntax - * @return the found number {@link Target}s + * @return the found number of {@link Target}s */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) long countByRsql(@NotEmpty String rsqlParam); + /** + * Count all targets for given {@link TargetFilterQuery} and that are compatible + * with the passed {@link DistributionSetType}. + * + * @param rsqlParam + * filter definition in RSQL syntax + * @param dsTypeId + * ID of the {@link DistributionSetType} the targets need to be + * compatible with + * @return the found number of{@link Target}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + long countByRsqlAndCompatible(@NotEmpty String rsqlParam, @NotNull Long dsTypeId); + /** * Count {@link TargetFilterQuery}s for given target filter query. * * @param targetFilterQueryId * {@link TargetFilterQuery#getId()} - * @return the found number {@link Target}s - * - * @throws EntityNotFoundException + * @return the found number of {@link Target}s + * + * @throws EntityNotFoundException * if {@link TargetFilterQuery} with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/DistributionSetTypeNotInTargetTypeException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/DistributionSetTypeNotInTargetTypeException.java deleted file mode 100644 index f711a2303..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/DistributionSetTypeNotInTargetTypeException.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2021 Bosch.IO GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.repository.exception; - -import java.util.Collection; - -import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.repository.model.TargetType; - -/** - * the {@link DistributionSetTypeNotInTargetTypeException} is thrown when a - * {@link DistributionSetType} is requested as part of a {@link TargetType} but - * is not returned in {@link TargetType#getCompatibleDistributionSetTypes()}. - */ -public class DistributionSetTypeNotInTargetTypeException extends EntityNotFoundException { - - private static final long serialVersionUID = 1L; - - /** - * Constructor - * - * @param distributionSetTypeId - * that is not compatible with given {@link TargetType}s - * @param targetTypeIds - * of the {@link TargetType}s where given {@link DistributionSetType} - * is not part of - */ - public DistributionSetTypeNotInTargetTypeException(final Long distributionSetTypeId, - final Collection targetTypeIds) { - super("DistributionSetType " + distributionSetTypeId + " is not compatible to TargetTypes " + targetTypeIds); - } - - /** - * Constructor - * - * @param distributionSetTypeIds - * that are not compatible with given {@link TargetType} - * @param targetTypeId - * of the {@link TargetType} where given {@link DistributionSetType}s - * are not part of - */ - public DistributionSetTypeNotInTargetTypeException(final Collection distributionSetTypeIds, - final Long targetTypeId) { - super("DistributionSetTypes " + distributionSetTypeIds + " are not compatible to TargetTypes " + targetTypeId); - } -} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/IncompatibleTargetTypeException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/IncompatibleTargetTypeException.java new file mode 100644 index 000000000..e8e31d49a --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/IncompatibleTargetTypeException.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.exception; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.hawkbit.exception.AbstractServerRtException; +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetType; + +/** + * Thrown if user tries to assign a {@link DistributionSet} to a {@link Target} + * that has an incompatible {@link TargetType} + */ +public class IncompatibleTargetTypeException extends AbstractServerRtException { + + private static final long serialVersionUID = 1L; + private final Collection targetTypeNames; + private final Collection distributionSetTypeNames; + + /** + * Creates a new IncompatibleTargetTypeException with + * {@link SpServerError#SP_TARGET_TYPE_INCOMPATIBLE} error. + * + * @param targetTypeName + * Name of the target type + * @param distributionSetTypeNames + * Names of the distribution set types + */ + public IncompatibleTargetTypeException(final String targetTypeName, + final Collection distributionSetTypeNames) { + super(String.format("Target of type %s is not compatible with distribution set of types %s", targetTypeName, + distributionSetTypeNames), SpServerError.SP_TARGET_TYPE_INCOMPATIBLE); + this.targetTypeNames = Collections.singleton(targetTypeName); + this.distributionSetTypeNames = distributionSetTypeNames; + } + + /** + * Creates a new IncompatibleTargetTypeException with + * {@link SpServerError#SP_TARGET_TYPE_INCOMPATIBLE} error. + * + * @param targetTypeNames + * Name of the target types + * @param distributionSetTypeName + * Name of the distribution set type + */ + public IncompatibleTargetTypeException(final Collection targetTypeNames, + final String distributionSetTypeName) { + super(String.format("Targets of types %s are not compatible with distribution set of type %s", targetTypeNames, + distributionSetTypeName), SpServerError.SP_TARGET_TYPE_INCOMPATIBLE); + this.targetTypeNames = targetTypeNames; + this.distributionSetTypeNames = Collections.singleton(distributionSetTypeName); + } + + public Collection getTargetTypeNames() { + return targetTypeNames; + } + + public Collection getDistributionSetTypeNames() { + return distributionSetTypeNames; + } +} diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/AbstractRolloutManagement.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/AbstractRolloutManagement.java deleted file mode 100644 index 61bdc7dff..000000000 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/AbstractRolloutManagement.java +++ /dev/null @@ -1,168 +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; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import javax.validation.ConstraintDeclarationException; - -import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; -import org.eclipse.hawkbit.repository.model.RolloutGroup; -import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation; -import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.context.ApplicationContext; -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.util.concurrent.ListenableFuture; - -/** - * Core functionality for {@link RolloutManagement} implementations. - * - */ -public abstract class AbstractRolloutManagement implements RolloutManagement { - - protected final TargetManagement targetManagement; - - protected final DeploymentManagement deploymentManagement; - - protected final RolloutGroupManagement rolloutGroupManagement; - - protected final DistributionSetManagement distributionSetManagement; - - protected final ApplicationContext context; - - protected final VirtualPropertyReplacer virtualPropertyReplacer; - - protected final PlatformTransactionManager txManager; - - protected final TenantAware tenantAware; - - protected final LockRegistry lockRegistry; - - protected final RolloutApprovalStrategy rolloutApprovalStrategy; - - protected final TenantConfigurationManagement tenantConfigurationManagement; - - protected final SystemSecurityContext systemSecurityContext; - - protected AbstractRolloutManagement(final TargetManagement targetManagement, - final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, - final DistributionSetManagement distributionSetManagement, final ApplicationContext context, - final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, - final TenantAware tenantAware, final LockRegistry lockRegistry, - final RolloutApprovalStrategy rolloutApprovalStrategy, - final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext) { - this.targetManagement = targetManagement; - this.deploymentManagement = deploymentManagement; - this.rolloutGroupManagement = rolloutGroupManagement; - this.distributionSetManagement = distributionSetManagement; - this.context = context; - this.virtualPropertyReplacer = virtualPropertyReplacer; - this.txManager = txManager; - this.tenantAware = tenantAware; - this.lockRegistry = lockRegistry; - this.rolloutApprovalStrategy = rolloutApprovalStrategy; - this.tenantConfigurationManagement = tenantConfigurationManagement; - this.systemSecurityContext = systemSecurityContext; - } - - protected RolloutGroupsValidation validateTargetsInGroups(final List groups, final String baseFilter, - final long totalTargets) { - final List groupTargetCounts = new ArrayList<>(groups.size()); - final Map targetFilterCounts = groups.stream() - .map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct() - .collect(Collectors.toMap(Function.identity(), targetManagement::countByRsql)); - - 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 groups, - final RolloutGroup group, final int groupIndex, final Map targetFilterCounts) { - // there can't be overlapping targets in the first group - if (groupIndex == 0) { - return 0; - } - final List 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.countByRsql(overlappingTargetsFilter); - targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets); - return overlappingTargets; - } - } - - protected long calculateRemainingTargets(final List groups, final String targetFilter, - final Long createdAt) { - final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); - final long totalTargets = targetManagement.countByRsql(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 validateTargetsInGroups(final List groups, - final String targetFilter, final Long createdAt) { - - final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); - final long totalTargets = targetManagement.countByRsql(baseFilter); - if (totalTargets == 0) { - throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); - } - - return new AsyncResult<>(validateTargetsInGroups( - groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets)); - } -} diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java index f5e227273..e215bca48 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java @@ -45,8 +45,8 @@ public final class RolloutHelper { } /** - * Verifies that the group has the required success condition and action and - * a falid target percentage. + * Verifies that the group has the required success condition and action and a + * valid target percentage. * * @param group * the input group @@ -140,8 +140,8 @@ public final class RolloutHelper { } /** - * Filters the groups of a Rollout to match a specific status and adds a - * group to the result. + * Filters the groups of a Rollout to match a specific status and adds a group + * to the result. * * @param status * the required status for the groups @@ -156,8 +156,8 @@ public final class RolloutHelper { } /** - * Creates an RSQL expression that matches all targets in the provided - * groups. Links all target filter queries with OR. + * Creates an RSQL expression that matches all targets in the provided groups. + * Links all target filter queries with OR. * * @param groups * the rollout groups @@ -224,7 +224,7 @@ public final class RolloutHelper { * 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) { + public static String getGroupTargetFilter(final String baseFilter, final RolloutGroup group) { if (StringUtils.isEmpty(group.getTargetFilterQuery())) { return baseFilter; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 706989b6a..6ebb026bc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -42,9 +42,10 @@ 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.IncompatibleTargetTypeException; +import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; @@ -256,14 +257,14 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl private void checkCompatibilityForSingleDsAssignment(final Long distSetId, final List controllerIds) { final DistributionSetType distSetType = distributionSetManagement.getValidAndComplete(distSetId).getType(); - final Set incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT) + final Set 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()); + .flatMap(List::stream).map(Target::getTargetType).map(TargetType::getName).collect(Collectors.toSet()); if (!incompatibleTargetTypes.isEmpty()) { - throw new DistributionSetTypeNotInTargetTypeException(distSetType.getId(), incompatibleTargetTypes); + throw new IncompatibleTargetTypeException(incompatibleTargetTypes, distSetType.getName()); } } @@ -278,9 +279,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl incompatibleDistSetTypes.removeAll(target.getTargetType().getCompatibleDistributionSetTypes()); if (!incompatibleDistSetTypes.isEmpty()) { - final Set distSetTypeIds = incompatibleDistSetTypes.stream().map(DistributionSetType::getId) + final Set distSetTypeNames = incompatibleDistSetTypes.stream().map(DistributionSetType::getName) .collect(Collectors.toSet()); - throw new DistributionSetTypeNotInTargetTypeException(distSetTypeIds, target.getTargetType().getId()); + throw new IncompatibleTargetTypeException(target.getTargetType().getName(), distSetTypeNames); } } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java index 6d4aa2421..346bb054b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java @@ -10,25 +10,24 @@ package org.eclipse.hawkbit.repository.jpa; import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.locks.Lock; +import java.util.function.Function; import java.util.stream.Collectors; import javax.validation.ConstraintDeclarationException; import javax.validation.ValidationException; -import org.eclipse.hawkbit.repository.AbstractRolloutManagement; -import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.RolloutApprovalStrategy; import org.eclipse.hawkbit.repository.RolloutExecutor; 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.RolloutStatusCache; @@ -54,6 +53,7 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper; import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -70,7 +70,6 @@ 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.ApplicationContext; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -96,7 +95,7 @@ import com.google.common.collect.Lists; */ @Validated @Transactional(readOnly = true) -public class JpaRolloutManagement extends AbstractRolloutManagement { +public class JpaRolloutManagement implements RolloutManagement { private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class); private static final List ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING, @@ -125,23 +124,35 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { @Autowired private RolloutStatusCache rolloutStatusCache; + private final TargetManagement targetManagement; + private final DistributionSetManagement distributionSetManagement; + private final VirtualPropertyReplacer virtualPropertyReplacer; + private final PlatformTransactionManager txManager; + private final TenantAware tenantAware; + private final LockRegistry lockRegistry; + private final RolloutApprovalStrategy rolloutApprovalStrategy; + private final TenantConfigurationManagement tenantConfigurationManagement; + private final SystemSecurityContext systemSecurityContext; private final RolloutExecutor rolloutExecutor; - private final EventPublisherHolder eventPublisherHolder; - private final Database database; public JpaRolloutManagement(final TargetManagement targetManagement, - final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, - final DistributionSetManagement distributionSetManagement, final ApplicationContext context, - final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, - final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, - final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy, + final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder, + final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, + final TenantAware tenantAware, final LockRegistry lockRegistry, final Database database, + final RolloutApprovalStrategy rolloutApprovalStrategy, final TenantConfigurationManagement tenantConfigurationManagement, final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) { - super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context, - virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy, - tenantConfigurationManagement, systemSecurityContext); + this.targetManagement = targetManagement; + this.distributionSetManagement = distributionSetManagement; + this.virtualPropertyReplacer = virtualPropertyReplacer; + this.txManager = txManager; + this.tenantAware = tenantAware; + this.lockRegistry = lockRegistry; + this.rolloutApprovalStrategy = rolloutApprovalStrategy; + this.tenantConfigurationManagement = tenantConfigurationManagement; + this.systemSecurityContext = systemSecurityContext; this.eventPublisherHolder = eventPublisherHolder; this.database = database; this.rolloutExecutor = rolloutExecutor; @@ -203,7 +214,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { private JpaRollout createRollout(final JpaRollout rollout) { WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout); - final Long totalTargets = targetManagement.countByRsql(rollout.getTargetFilterQuery()); + final Long totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(), + rollout.getDistributionSet().getType().getId()); if (totalTargets == 0) { throw new ValidationException("Rollout does not match any existing targets"); } @@ -248,6 +260,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { final RolloutGroupConditions conditions, final Rollout rollout) { RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING); final JpaRollout savedRollout = (JpaRollout) rollout; + final DistributionSetType distributionSetType = savedRollout.getDistributionSet().getType(); // prepare the groups final List groups = groupList.stream() @@ -255,13 +268,13 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { .collect(Collectors.toList()); groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions); - RolloutHelper.verifyRemainingTargets( - calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt())); + RolloutHelper.verifyRemainingTargets(calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), + savedRollout.getCreatedAt(), distributionSetType.getId())); // check if we need to enforce the 'max targets per group' quota if (quotaManagement.getMaxTargetsPerRolloutGroup() > 0) { - validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt()) - .getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota); + validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt(), + distributionSetType.getId()).getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota); } // create and persist the groups (w/o filling them with targets) @@ -299,21 +312,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId()))); } - @Override - @Async - public ListenableFuture validateTargetsInGroups(final List groups, - final String targetFilter, final Long createdAt) { - - final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); - final long totalTargets = targetManagement.countByRsql(baseFilter); - if (totalTargets == 0) { - throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); - } - - return new AsyncResult<>(validateTargetsInGroups( - groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets)); - } - @Override @Transactional @Retryable(include = { @@ -524,7 +522,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { @Override public Page findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) { - Page rollouts; + final Page rollouts; final Specification spec = RolloutSpecification.isDeletedWithDistributionSet(deleted); rollouts = rolloutRepository.findAll(spec, pageable); setRolloutStatusDetails(rollouts); @@ -599,8 +597,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { * Enforces the quota defining the maximum number of {@link Target}s per * {@link RolloutGroup}. * - * @param group - * The rollout group * @param requested * number of targets to check */ @@ -626,4 +622,93 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { }); } + private RolloutGroupsValidation validateTargetsInGroups(final List groups, final String baseFilter, + final long totalTargets, final Long dsTypeId) { + final List groupTargetCounts = new ArrayList<>(groups.size()); + final Map targetFilterCounts = groups.stream() + .map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct() + .collect(Collectors.toMap(Function.identity(), + groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId))); + + 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 groups, + final RolloutGroup group, final int groupIndex, final Map targetFilterCounts) { + // there can't be overlapping targets in the first group + if (groupIndex == 0) { + return 0; + } + final List 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.countByRsql(overlappingTargetsFilter); + targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets); + return overlappingTargets; + } + } + + private long calculateRemainingTargets(final List groups, final String targetFilter, + final Long createdAt, final Long dsTypeId) { + final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); + final long totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId); + if (totalTargets == 0) { + throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); + } + + final RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets, dsTypeId); + + return totalTargets - validation.getTargetsInGroups(); + } + + @Override + @Async + public ListenableFuture validateTargetsInGroups(final List groups, + final String targetFilter, final Long createdAt, final Long dsTypeId) { + + final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); + + final long totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId); + + if (totalTargets == 0) { + throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); + } + + return new AsyncResult<>( + validateTargetsInGroups(groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), + baseFilter, totalTargets, dsTypeId)); + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 3daf75af7..b201da777 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -786,10 +786,14 @@ public class JpaTargetManagement implements TargetManagement { public long countByRsql(final String targetFilterQuery) { final Specification specs = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database); - return targetRepository.count((root, query, cb) -> { - query.distinct(true); - return specs.toPredicate(root, query, cb); - }); + return targetRepository.count(specs); + } + + @Override + public long countByRsqlAndCompatible(final String targetFilterQuery, final Long dsTypeId) { + final Specification rsqlSpec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, + TargetFields.class, virtualPropertyReplacer, database); + return targetRepository.count(rsqlSpec.and(TargetSpecifications.isCompatibleWithDistributionSetType(dsTypeId))); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java index 77699c116..66ae45bf4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java @@ -664,17 +664,15 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean RolloutManagement rolloutManagement(final TargetManagement targetManagement, - final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, - final DistributionSetManagement distributionSetManagement, final ApplicationContext context, - final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, - final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, - final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy, + final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder, + final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, + final TenantAware tenantAware, final LockRegistry lockRegistry, final JpaProperties properties, + final RolloutApprovalStrategy rolloutApprovalStrategy, final TenantConfigurationManagement tenantConfigurationManagement, final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) { - return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement, - distributionSetManagement, context, eventPublisherHolder, virtualPropertyReplacer, txManager, - tenantAware, lockRegistry, properties.getDatabase(), rolloutApprovalStrategy, - tenantConfigurationManagement, systemSecurityContext, rolloutExecutor); + return new JpaRolloutManagement(targetManagement, distributionSetManagement, eventPublisherHolder, + virtualPropertyReplacer, txManager, tenantAware, lockRegistry, properties.getDatabase(), + rolloutApprovalStrategy, tenantConfigurationManagement, systemSecurityContext, rolloutExecutor); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index 729d6d62e..3a30eba3b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -394,7 +394,7 @@ public final class TargetSpecifications { // isNull predicate first final Predicate targetTypeIsNull = targetRoot.get(JpaTarget_.targetType).isNull(); - return cb.or(targetTypeIsNull, getDistSetTypeEqualPredicate(targetRoot, cb, distributionSetTypeId)); + return cb.or(targetTypeIsNull, cb.equal(getDsTypeIdPath(targetRoot), distributionSetTypeId)); }; } @@ -414,19 +414,21 @@ public final class TargetSpecifications { // isNotNull predicate first final Predicate targetTypeNotNull = targetRoot.get(JpaTarget_.targetType).isNotNull(); + // We need to check for isNull(...) and notEqual(...) since we allow + // target types that don't have any compatible distribution set type return cb.and(targetTypeNotNull, - cb.isNull(getDistSetTypeEqualPredicate(targetRoot, cb, distributionSetTypeId))); + cb.or(cb.isNull(getDsTypeIdPath(targetRoot)), + cb.notEqual(getDsTypeIdPath(targetRoot), distributionSetTypeId))); }; } - private static Predicate getDistSetTypeEqualPredicate(final Root root, final CriteriaBuilder cb, - final Long dsTypeId) { + private static Path getDsTypeIdPath(final Root root) { final Join targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT); targetTypeJoin.fetch(JpaTargetType_.distributionSetTypes); final SetJoin dsTypeTargetTypeJoin = targetTypeJoin .join(JpaTargetType_.distributionSetTypes, JoinType.LEFT); - return cb.equal(dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id), dsTypeId); + return dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java index b9390bcd4..9d68482fc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java @@ -41,9 +41,9 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; -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.IncompatibleTargetTypeException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; @@ -62,6 +62,7 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.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.TargetType; @@ -1442,14 +1443,31 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { @Description("Verify that the DistributionSet assignment fails for target with incompatible target type.") void verifyDSAssignmentFailsForTargetsWithIncompatibleTargetTypes() { final DistributionSet ds = testdataFactory.createDistributionSet("test-ds"); - final TargetType targetType = testdataFactory.createTargetType("test-type", Collections.emptyList()); + final DistributionSetType dsType = testdataFactory.findOrCreateDistributionSetType("test-ds-type", "dsType"); + final TargetType targetType = testdataFactory.createTargetType("target-type", + Collections.singletonList(dsType)); final Target target = testdataFactory.createTarget("test-target", "test-target", targetType.getId()); final DeploymentRequest deploymentRequest = DeploymentManagement .deploymentRequest(target.getControllerId(), ds.getId()).build(); final List deploymentRequests = Collections.singletonList(deploymentRequest); - assertThatExceptionOfType(DistributionSetTypeNotInTargetTypeException.class) + assertThatExceptionOfType(IncompatibleTargetTypeException.class) + .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests)); + } + + @Test + @Description("Verify that the DistributionSet assignment fails for target with target type that is not compatible with any dsType.") + void verifyDSAssignmentFailsForTargetsWithTargetTypesThatAreNotCompatibleWithAnyDs() { + final DistributionSet ds = testdataFactory.createDistributionSet("test-ds"); + final TargetType emptyTargetType = testdataFactory.createTargetType("target-type", Collections.emptyList()); + final Target targetWithEmptyType = testdataFactory.createTarget("test-target", "test-target", + emptyTargetType.getId()); + + final DeploymentRequest deploymentRequestWithEmptyType = DeploymentManagement + .deploymentRequest(targetWithEmptyType.getControllerId(), ds.getId()).build(); + final List deploymentRequests = Collections.singletonList(deploymentRequestWithEmptyType); + assertThatExceptionOfType(IncompatibleTargetTypeException.class) .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests)); } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java index 40a4d9ef4..1725a22e7 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java @@ -1205,8 +1205,7 @@ public class TestdataFactory { /** * Finds {@link TargetType} in repository with given - * {@link TargetType#getName()} or creates if it does not exist yet. No ds - * types + * {@link TargetType#getName()} or creates if it does not exist yet. No ds types * are assigned on creation. * * @param targetTypeName @@ -1222,8 +1221,7 @@ public class TestdataFactory { /** * Creates {@link TargetType} in repository with given - * {@link TargetType#getName()}. Compatible distribution set types are - * assigned + * {@link TargetType#getName()}. Compatible distribution set types are assigned * on creation * * @param targetTypeName @@ -1256,8 +1254,8 @@ public class TestdataFactory { } /** - * Creates a distribution set and directly invalidates it. No actions will - * be canceled and no rollouts will be stopped with this invalidation. + * Creates a distribution set and directly invalidates it. No actions will be + * canceled and no rollouts will be stopped with this invalidation. * * @return created invalidated {@link DistributionSet} */ @@ -1269,8 +1267,8 @@ public class TestdataFactory { } /** - * Creates a distribution set that has no software modules assigned, so it - * is incomplete. + * Creates a distribution set that has no software modules assigned, so it is + * incomplete. * * @return created incomplete {@link DistributionSet} */ diff --git a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java index c5ebc3663..ffc6f629d 100644 --- a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java +++ b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java @@ -84,6 +84,7 @@ public class ResponseExceptionHandler { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_INCOMPATIBLE, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INCOMPLETE, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STOP_ROLLOUT_FAILED, HttpStatus.LOCKED); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java index 631572ea0..204fd90bc 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java @@ -20,6 +20,7 @@ import org.eclipse.hawkbit.ui.common.data.suppliers.TargetManagementStateDataSup import org.eclipse.hawkbit.ui.error.HawkbitUIErrorHandler; import org.eclipse.hawkbit.ui.error.extractors.ConstraintViolationErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.EntityNotFoundErrorExtractor; +import org.eclipse.hawkbit.ui.error.extractors.IncompatibleTargetTypeErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.InsufficientPermissionErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.InvalidDistributionSetErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.UiErrorDetailsExtractor; @@ -144,9 +145,21 @@ public class MgmtUiConfiguration { return new EntityNotFoundErrorExtractor(i18n); } + /** + * UI incompatible Target Type error details extractor bean. + * + * @param i18n + * VaadinMessageSource + * @return UI IncompatibleTargetType Error details extractor + */ + @Bean + UiErrorDetailsExtractor incompatibleTargetTypeErrorExtractor(final VaadinMessageSource i18n) { + return new IncompatibleTargetTypeErrorExtractor(i18n); + } + /** * UI Insufficient Permission Error details extractor bean. - * + * * @param i18n * VaadinMessageSource * @return UI InsufficientPermission Error details extractor diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java index b29d8564a..0a4968775 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java @@ -26,7 +26,8 @@ public class RolloutToProxyRolloutMapper extends AbstractNamedEntityToProxyNamed mapNamedEntityAttributes(rollout, proxyRollout); final DistributionSet ds = rollout.getDistributionSet(); - proxyRollout.setDsInfo(new ProxyDistributionSetInfo(ds.getId(), ds.getName(), ds.getVersion(), ds.isValid())); + proxyRollout.setDsInfo(new ProxyDistributionSetInfo(ds.getId(), ds.getName(), ds.getVersion(), + ds.getType().getId(), ds.isValid())); proxyRollout .setNumberOfGroups(rollout.getRolloutGroupsCreated() > 0 ? rollout.getRolloutGroupsCreated() : null); proxyRollout.setForcedTime(rollout.getForcedTime() > 0 ? rollout.getForcedTime() : null); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java index 00e8a212e..b01d706b9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java @@ -38,7 +38,8 @@ public class TargetFilterQueryToProxyTargetFilterMapper if (distributionSet != null) { proxyTargetFilter.setAutoAssignmentEnabled(true); proxyTargetFilter.setDistributionSetInfo(new ProxyDistributionSetInfo(distributionSet.getId(), - distributionSet.getName(), distributionSet.getVersion(), distributionSet.isValid())); + distributionSet.getName(), distributionSet.getVersion(), distributionSet.getType().getId(), + distributionSet.isValid())); proxyTargetFilter.setAutoAssignActionType(targetFilterQuery.getAutoAssignActionType()); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java index 2bc624309..a7a700621 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java @@ -87,7 +87,6 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa } /** - * Flag that indicates if the distribution set is valid. * * @return true if the distribution set is valid, otherwise @@ -163,6 +162,7 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa ds.setId(dsInfo.getId()); ds.setName(dsInfo.getName()); ds.setVersion(dsInfo.getVersion()); + ds.setTypeInfo(new ProxyTypeInfo(dsInfo.getDsTypeId(), null)); ds.setNameVersion(dsInfo.getNameVersion()); ds.setIsValid(dsInfo.isValid()); @@ -170,11 +170,12 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa } /** - * Gets the Id, Name and version of distribution set + * Gets the Id, name, version, dsTypeId and invalidation state of distribution + * set * - * @return proxy of Id, Name and version + * @return proxy of Id, name, version, dsTypeId and invalidation state */ public ProxyDistributionSetInfo getInfo() { - return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getIsValid()); + return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getTypeInfo().getId(), getIsValid()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java index 05f040a73..53bed4a2f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java @@ -21,6 +21,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { private String name; private String version; private String nameVersion; + private Long dsTypeId; private boolean isValid; /** @@ -39,12 +40,18 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { * distribution set name * @param version * distribution set version + * @param dsTypeId + * ID of the assigned dsType + * @param isValid + * invalidation state */ - public ProxyDistributionSetInfo(final Long id, final String name, final String version, final boolean isValid) { + public ProxyDistributionSetInfo(final Long id, final String name, final String version, final Long dsTypeId, + final boolean isValid) { super(id); this.name = name; this.version = version; + this.dsTypeId = dsTypeId; this.isValid = isValid; this.nameVersion = HawkbitCommonUtil.getFormattedNameVersion(name, version); } @@ -81,13 +88,23 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { this.isValid = isValid; } + public Long getDsTypeId() { + return dsTypeId; + } + + public void setDsTypeId(final Long dsTypeId) { + this.dsTypeId = dsTypeId; + } + @Override public int hashCode() { // nameVersion is ignored because it is a composition of name and // version - return Objects.hash(getId(), getName(), getVersion(), isValid()); + return Objects.hash(getId(), getName(), getVersion(), getDsTypeId(), isValid()); } + // equals method requires all of the used conditions + @SuppressWarnings("squid:S1067") @Override public boolean equals(final Object obj) { if (obj == null) { @@ -102,6 +119,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { // version return Objects.equals(this.getId(), other.getId()) && Objects.equals(this.getName(), other.getName()) && Objects.equals(this.getVersion(), other.getVersion()) + && Objects.equals(this.getDsTypeId(), other.getDsTypeId()) && Objects.equals(this.isValid(), other.isValid()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/IncompatibleTargetTypeErrorExtractor.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/IncompatibleTargetTypeErrorExtractor.java new file mode 100644 index 000000000..378e34b92 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/IncompatibleTargetTypeErrorExtractor.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.error.extractors; + +import java.util.Optional; + +import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException; +import org.eclipse.hawkbit.ui.error.UiErrorDetails; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; + +/** + * UI error details extractor for {@link IncompatibleTargetTypeException}. + */ +public class IncompatibleTargetTypeErrorExtractor extends AbstractSingleUiErrorDetailsExtractor { + + private final VaadinMessageSource i18n; + + /** + * Constructor for IncompatibleTargetTypeErrorExtractor. + * + * @param i18n + * Message source used for localization + */ + public IncompatibleTargetTypeErrorExtractor(final VaadinMessageSource i18n) { + this.i18n = i18n; + } + + @Override + protected Optional findDetails(final Throwable error) { + return findExceptionOf(error, IncompatibleTargetTypeException.class) + .map(ex -> UiErrorDetails.create(i18n.getMessage("caption.error"), + i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_TARGET_TYPE_INCOMPATIBLE, + ex.getTargetTypeNames(), ex.getDistributionSetTypeNames()))); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/DeploymentAssignmentWindowController.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/DeploymentAssignmentWindowController.java index f14022d03..1b94ab6d0 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/DeploymentAssignmentWindowController.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/DeploymentAssignmentWindowController.java @@ -64,8 +64,8 @@ public class DeploymentAssignmentWindowController { * @param deploymentManagement * DeploymentManagement */ - public DeploymentAssignmentWindowController(final CommonUiDependencies uiDependencies, final UiProperties uiProperties, - final DeploymentManagement deploymentManagement) { + public DeploymentAssignmentWindowController(final CommonUiDependencies uiDependencies, + final UiProperties uiProperties, final DeploymentManagement deploymentManagement) { this.i18n = uiDependencies.getI18n(); this.eventBus = uiDependencies.getEventBus(); this.notification = uiDependencies.getUiNotification(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/AdvancedGroupsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/AdvancedGroupsLayout.java index 947d6f523..0b4be1738 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/AdvancedGroupsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/AdvancedGroupsLayout.java @@ -56,6 +56,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout { private final GridLayout layout; private String targetFilter; + private Long dsTypeId; private final List groupRows; private int lastGroupIndex; @@ -237,7 +238,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout { private void validateTargetsPerGroup() { resetErrors(); - if (StringUtils.isEmpty(targetFilter)) { + if (StringUtils.isEmpty(targetFilter) || dsTypeId == null) { return; } @@ -245,7 +246,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout { final List groupsCreate = getRolloutGroupsCreateFromDefinitions( getAdvancedRolloutGroupDefinitions()); final ListenableFuture validateTargetsInGroups = rolloutManagement - .validateTargetsInGroups(groupsCreate, targetFilter, System.currentTimeMillis()); + .validateTargetsInGroups(groupsCreate, targetFilter, System.currentTimeMillis(), dsTypeId); final UI ui = UI.getCurrent(); validateTargetsInGroups.addCallback(validation -> ui.access(() -> updateGroupsByValidation(validation)), @@ -273,10 +274,9 @@ public class AdvancedGroupsLayout extends ValidatableLayout { } /** - * YOU SHOULD NOT CALL THIS METHOD MANUALLY. It's only for the callback. - * Only 1 runningValidation should be executed. If this runningValidation is - * done, then this method is called. Maybe then a new runningValidation is - * executed. + * YOU SHOULD NOT CALL THIS METHOD MANUALLY. It's only for the callback. Only 1 + * runningValidation should be executed. If this runningValidation is done, then + * this method is called. Maybe then a new runningValidation is executed. * */ private void updateGroupsByValidation(final RolloutGroupsValidation validation) { @@ -338,7 +338,30 @@ public class AdvancedGroupsLayout extends ValidatableLayout { */ public void setTargetFilter(final String targetFilter) { this.targetFilter = targetFilter; + updateValidation(); + } + /** + * + * @param dsTypeId + * ID of the Distribution set type which is required for the + * compatibility check + */ + public void setDsTypeId(final Long dsTypeId) { + this.dsTypeId = dsTypeId; + updateValidation(); + } + + /** + * @param targetFilter + * the target filter which is required for verification + * @param dsTypeId + * ID of the Distribution set type which is required for the + * compatibility check + */ + public void setTargetFilterAndDsType(final String targetFilter, final Long dsTypeId) { + this.targetFilter = targetFilter; + this.dsTypeId = dsTypeId; updateValidation(); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/RolloutFormLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/RolloutFormLayout.java index 617350165..e9e52ebe5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/RolloutFormLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/RolloutFormLayout.java @@ -28,6 +28,7 @@ import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import com.vaadin.data.Binder; import com.vaadin.data.Binder.Binding; +import com.vaadin.data.HasValue; import com.vaadin.data.ValidationException; import com.vaadin.data.Validator; import com.vaadin.data.validator.LongRangeValidator; @@ -72,6 +73,7 @@ public class RolloutFormLayout extends ValidatableLayout { private Long totalTargets; private Consumer filterQueryChangedListener; + private Consumer distSetChangedListener; /** * Constructor for RolloutFormLayout @@ -190,11 +192,8 @@ public class RolloutFormLayout extends ValidatableLayout { } private void addValueChangeListeners() { - targetFilterQueryCombo.getComponent().addValueChangeListener(event -> { - if (filterQueryChangedListener != null) { - filterQueryChangedListener.accept(event.getValue() != null ? event.getValue().getQuery() : null); - } - }); + targetFilterQueryCombo.getComponent().addValueChangeListener(filterQueryChangedListener()); + dsCombo.addValueChangeListener(distSetChangedListener()); actionTypeLayout.getComponent().getActionTypeOptionGroup().addValueChangeListener( event -> actionTypeLayout.setRequired(event.getValue() == ActionType.TIMEFORCED)); @@ -202,6 +201,26 @@ public class RolloutFormLayout extends ValidatableLayout { event -> autoStartOptionGroupLayout.setRequired(event.getValue() == AutoStartOption.SCHEDULED)); } + private HasValue.ValueChangeListener filterQueryChangedListener() { + return event -> { + if (filterQueryChangedListener != null) { + filterQueryChangedListener.accept(event.getValue() != null ? event.getValue().getQuery() : null); + } + }; + } + + private HasValue.ValueChangeListener distSetChangedListener() { + return event -> { + if (distSetChangedListener != null) { + if (event.getValue() != null && event.getValue().getTypeInfo() != null) { + distSetChangedListener.accept(event.getValue().getTypeInfo().getId()); + } else { + distSetChangedListener.accept(null); + } + } + }; + } + /** * Add rollout form to add layout * @@ -285,6 +304,16 @@ public class RolloutFormLayout extends ValidatableLayout { this.filterQueryChangedListener = filterQueryChangedListener; } + /** + * Sets the changed listener for distribution set + * + * @param distSetChangedListener + * Changed listener + */ + public void setDistSetChangedListener(final Consumer distSetChangedListener) { + this.distSetChangedListener = distSetChangedListener; + } + /** * Sets the count of total targets * diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/VisualGroupDefinitionLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/VisualGroupDefinitionLayout.java index f19b423d3..b7a3fff13 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/VisualGroupDefinitionLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/components/VisualGroupDefinitionLayout.java @@ -61,7 +61,7 @@ public class VisualGroupDefinitionLayout { if (groupDefinitionMode == GroupDefinitionMode.SIMPLE) { updateBySimpleGroupsDefinition(); - } else { + } else if (groupDefinitionMode == GroupDefinitionMode.ADVANCED) { updateByAdvancedGroupsDefinition(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java index 5e73ac85d..3de8a10db 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java @@ -42,6 +42,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout { private final VisualGroupDefinitionLayout visualGroupDefinitionLayout; private String filterQuery; + private Long dsTypeId; private Long totalTargets; private int noOfGroups; private List advancedRolloutGroupDefinitions; @@ -82,24 +83,33 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout { } private void addValueChangeListeners() { - rolloutFormLayout.setFilterQueryChangedListener(this::onTargetFilterQueryChange); + rolloutFormLayout.setFilterQueryChangedListener(this::onFilterQueryChange); + rolloutFormLayout.setDistSetChangedListener(this::onDistSetTypeChange); groupsDefinitionTabs.addSelectedTabChangeListener(event -> onGroupDefinitionTabChanged()); simpleGroupsLayout.setNoOfGroupsChangedListener(this::onNoOfSimpleGroupsChanged); advancedGroupsLayout.setAdvancedGroupDefinitionsChangedListener(this::onAdvancedGroupsChanged); } - private void onTargetFilterQueryChange(final String filterQuery) { + private void onFilterQueryChange(final String filterQuery) { this.filterQuery = filterQuery; - - totalTargets = !StringUtils.isEmpty(filterQuery) ? targetManagement.countByRsql(filterQuery) : null; - updateTotalTargetsAwareComponents(); + updateTotalTargets(); if (isAdvancedGroupsTabSelected()) { advancedGroupsLayout.setTargetFilter(filterQuery); } } - private void updateTotalTargetsAwareComponents() { + private void onDistSetTypeChange(final Long dsTypeId) { + this.dsTypeId = dsTypeId; + updateTotalTargets(); + + if (isAdvancedGroupsTabSelected()) { + advancedGroupsLayout.setDsTypeId(dsTypeId); + } + } + + private void updateTotalTargets() { + this.totalTargets = getTotalTargets(filterQuery, dsTypeId); rolloutFormLayout.setTotalTargets(totalTargets); visualGroupDefinitionLayout.setTotalTargets(totalTargets); if (isSimpleGroupsTabSelected()) { @@ -107,6 +117,16 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout { } } + private Long getTotalTargets(final String filterQuery, final Long distSetTypeId) { + if (StringUtils.isEmpty(filterQuery)) { + return null; + } + if (distSetTypeId == null) { + return targetManagement.countByRsql(filterQuery); + } + return targetManagement.countByRsqlAndCompatible(filterQuery, distSetTypeId); + } + private boolean isSimpleGroupsTabSelected() { return groupsDefinitionTabs.getSelectedTab().equals(simpleGroupsLayout.getLayout()); } @@ -128,7 +148,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout { if (isAdvancedGroupsTabSelected()) { adaptAdvancedGroupsValidation(); - advancedGroupsLayout.setTargetFilter(filterQuery); + advancedGroupsLayout.setTargetFilterAndDsType(filterQuery, dsTypeId); visualGroupDefinitionLayout.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED); visualGroupDefinitionLayout.setAdvancedRolloutGroupDefinitions(advancedRolloutGroupDefinitions); @@ -209,7 +229,6 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout { proxyEntity.setAdvancedRolloutGroupDefinitions(advancedGroupsLayout.getAdvancedRolloutGroupDefinitions()); proxyEntity.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED); } - return proxyEntity; } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java index 4252c7510..538ac25fa 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java @@ -163,6 +163,8 @@ public final class UIMessageIdProvider { public static final String MESSAGE_ERROR_PERMISSION_INSUFFICIENT = "message.permission.insufficient"; + public static final String MESSAGE_ERROR_TARGET_TYPE_INCOMPATIBLE = "message.target.type.incompatible"; + public static final String CRON_VALIDATION_ERROR = "message.maintenancewindow.schedule.validation.error"; public static final String TOOLTIP_OVERDUE = "tooltip.overdue"; diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index d8f4dab53..56bbffd26 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -538,6 +538,7 @@ message.invalidate.distributionset.affected.entities.intro.plural = When you con message.invalidate.distributionset.affected.entities.actions = {0} action(s) will be canceled message.invalidate.distributionset.affected.entities.rollouts = {0} rollout(s) will be stopped message.invalidate.distributionset.affected.entities.autoassignments = {0} auto assignment(s) will be stopped +message.target.type.incompatible = Target(s) of type(s) {0} not compatible with distribution set type(s) {1} # action info action.target.table.selectall = Select all (Ctrl+A)