Adapt UI for target type compatibility check (#1189)

* Added compatibility calls needed for UI

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

* Adapted UI for target type compatibility checks

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

* improved exception handling for incompatibility check

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

* added & fixed unit tests

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

* fixed merged conflicts

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

* fixed target type incompatibly specification

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

* changed UI behaviour to close assignment popup in case of IncompatibleTargetTypeException

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

* added unit test to validate incompatibly specification fix

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>

* fix potential null pointer

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

* Fixed rolloutcopy by adding dsTypeId to ProxyDistributionSetInfo

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

* suppressed warning

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>
This commit is contained in:
Robert Sing
2021-10-22 16:23:25 +02:00
committed by GitHub
parent f94b4430e0
commit dea6fa3ce6
28 changed files with 473 additions and 347 deletions

View File

@@ -161,15 +161,15 @@ public enum SpServerError {
"Storage quota will be exceeded if file is uploaded."), "Storage quota will be exceeded if file is uploaded."),
/** /**
* error message, which describes that the action can not be canceled cause * error message, which describes that the action can not be canceled cause the
* the action is inactive. * action is inactive.
*/ */
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable", SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable",
"Only active actions which are in status pending are cancelable."), "Only active actions which are in status pending are cancelable."),
/** /**
* error message, which describes that the action can not be force quit * error message, which describes that the action can not be force quit cause
* cause the action is inactive. * the action is inactive.
*/ */
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable", SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable",
"Only active actions which are in status pending can be force quit."), "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."), "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 * Error message informing that the action type for auto-assignment is invalid.
* invalid.
*/ */
SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType", 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']"), "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_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"); SP_STOP_ROLLOUT_FAILED("hawkbit.server.error.stopRolloutFailed", "Stopping the rollout failed");
private final String key; private final String key;

View File

@@ -181,8 +181,8 @@ public interface RolloutManagement {
RolloutGroupConditions conditions); RolloutGroupConditions conditions);
/** /**
* Calculates how many targets are addressed by each rollout group and * Calculates how many targets are addressed by each rollout group and returns
* returns the validation information. * the validation information.
* *
* @param groups * @param groups
* a list of rollout groups * a list of rollout groups
@@ -190,6 +190,8 @@ public interface RolloutManagement {
* the rollout * the rollout
* @param createdAt * @param createdAt
* timestamp when the rollout was created * timestamp when the rollout was created
* @param dsTypeId
* ID of the type of distribution set of the rollout
* @return the validation information * @return the validation information
* @throws RolloutIllegalStateException * @throws RolloutIllegalStateException
* thrown when no targets are targeted by the rollout * 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) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ)
ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(@Valid List<RolloutGroupCreate> groups, ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(@Valid List<RolloutGroupCreate> groups,
String targetFilter, Long createdAt); String targetFilter, Long createdAt, @NotNull Long dsTypeId);
/** /**
* Retrieves all rollouts. * Retrieves all rollouts.

View File

@@ -102,8 +102,8 @@ public interface TargetManagement {
* @param selectTargetWithNoTag * @param selectTargetWithNoTag
* flag to select targets with no tag assigned * 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 * @throws EntityNotFoundException
* if distribution set with given ID does not exist * if distribution set with given ID does not exist
*/ */
@@ -117,7 +117,7 @@ public interface TargetManagement {
* @param distId * @param distId
* to search for * to search for
* @return number of found {@link Target}s. * @return number of found {@link Target}s.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if distribution set with given ID does not exist * if distribution set with given ID does not exist
*/ */
@@ -144,19 +144,33 @@ public interface TargetManagement {
* *
* @param rsqlParam * @param rsqlParam
* filter definition in RSQL syntax * 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) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByRsql(@NotEmpty String rsqlParam); 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. * Count {@link TargetFilterQuery}s for given target filter query.
* *
* @param targetFilterQueryId * @param targetFilterQueryId
* {@link TargetFilterQuery#getId()} * {@link TargetFilterQuery#getId()}
* @return the found number {@link Target}s * @return the found number of {@link Target}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if {@link TargetFilterQuery} with given ID does not exist * if {@link TargetFilterQuery} with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)

View File

@@ -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<Long> 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<Long> distributionSetTypeIds,
final Long targetTypeId) {
super("DistributionSetTypes " + distributionSetTypeIds + " are not compatible to TargetTypes " + targetTypeId);
}
}

View File

@@ -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<String> targetTypeNames;
private final Collection<String> 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<String> 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<String> 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<String> getTargetTypeNames() {
return targetTypeNames;
}
public Collection<String> getDistributionSetTypeNames() {
return distributionSetTypeNames;
}
}

View File

@@ -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<RolloutGroup> groups, final String baseFilter,
final long totalTargets) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
final Map<String, Long> targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(), targetManagement::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<RolloutGroup> groups,
final RolloutGroup group, final int groupIndex, final Map<String, Long> targetFilterCounts) {
// there can't be overlapping targets in the first group
if (groupIndex == 0) {
return 0;
}
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter,
previousGroups, group);
if (targetFilterCounts.containsKey(overlappingTargetsFilter)) {
return targetFilterCounts.get(overlappingTargetsFilter);
} else {
final long overlappingTargets = targetManagement.countByRsql(overlappingTargetsFilter);
targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets);
return overlappingTargets;
}
}
protected long calculateRemainingTargets(final List<RolloutGroup> 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<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> 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));
}
}

View File

@@ -45,8 +45,8 @@ public final class RolloutHelper {
} }
/** /**
* Verifies that the group has the required success condition and action and * Verifies that the group has the required success condition and action and a
* a falid target percentage. * valid target percentage.
* *
* @param group * @param group
* the input 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 * Filters the groups of a Rollout to match a specific status and adds a group
* group to the result. * to the result.
* *
* @param status * @param status
* the required status for the groups * 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 * Creates an RSQL expression that matches all targets in the provided groups.
* groups. Links all target filter queries with OR. * Links all target filter queries with OR.
* *
* @param groups * @param groups
* the rollout groups * the rollout groups
@@ -224,7 +224,7 @@ public final class RolloutHelper {
* group for which the filter string should be created * group for which the filter string should be created
* @return the final target filter query for a rollout group * @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())) { if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
return baseFilter; return baseFilter;
} }

View File

@@ -42,9 +42,10 @@ import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; 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.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; 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.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; 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<String> controllerIds) { private void checkCompatibilityForSingleDsAssignment(final Long distSetId, final List<String> controllerIds) {
final DistributionSetType distSetType = distributionSetManagement.getValidAndComplete(distSetId).getType(); final DistributionSetType distSetType = distributionSetManagement.getValidAndComplete(distSetId).getType();
final Set<Long> incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT) final Set<String> incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT)
.stream() .stream()
.map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids) .map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids)
.and(TargetSpecifications.notCompatibleWithDistributionSetType(distSetType.getId())))) .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()) { 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()); incompatibleDistSetTypes.removeAll(target.getTargetType().getCompatibleDistributionSetTypes());
if (!incompatibleDistSetTypes.isEmpty()) { if (!incompatibleDistSetTypes.isEmpty()) {
final Set<Long> distSetTypeIds = incompatibleDistSetTypes.stream().map(DistributionSetType::getId) final Set<String> distSetTypeNames = incompatibleDistSetTypes.stream().map(DistributionSetType::getName)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
throw new DistributionSetTypeNotInTargetTypeException(distSetTypeIds, target.getTargetType().getId()); throw new IncompatibleTargetTypeException(target.getTargetType().getName(), distSetTypeNames);
} }
} }
} }

View File

@@ -10,25 +10,24 @@ package org.eclipse.hawkbit.repository.jpa;
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions; import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.ConstraintDeclarationException; import javax.validation.ConstraintDeclarationException;
import javax.validation.ValidationException; 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.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy; import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.RolloutExecutor; import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutFields; import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutHelper; import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache; 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.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet; 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;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -70,7 +70,6 @@ import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -96,7 +95,7 @@ import com.google.common.collect.Lists;
*/ */
@Validated @Validated
@Transactional(readOnly = true) @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 Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING, private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING,
@@ -125,23 +124,35 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
@Autowired @Autowired
private RolloutStatusCache rolloutStatusCache; 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 RolloutExecutor rolloutExecutor;
private final EventPublisherHolder eventPublisherHolder; private final EventPublisherHolder eventPublisherHolder;
private final Database database; private final Database database;
public JpaRolloutManagement(final TargetManagement targetManagement, public JpaRolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, final TenantAware tenantAware, final LockRegistry lockRegistry, final Database database,
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, final RolloutApprovalStrategy rolloutApprovalStrategy,
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) { final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) {
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context, this.targetManagement = targetManagement;
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy, this.distributionSetManagement = distributionSetManagement;
tenantConfigurationManagement, systemSecurityContext); 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.eventPublisherHolder = eventPublisherHolder;
this.database = database; this.database = database;
this.rolloutExecutor = rolloutExecutor; this.rolloutExecutor = rolloutExecutor;
@@ -203,7 +214,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
private JpaRollout createRollout(final JpaRollout rollout) { private JpaRollout createRollout(final JpaRollout rollout) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(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) { if (totalTargets == 0) {
throw new ValidationException("Rollout does not match any existing targets"); 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) { final RolloutGroupConditions conditions, final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
final JpaRollout savedRollout = (JpaRollout) rollout; final JpaRollout savedRollout = (JpaRollout) rollout;
final DistributionSetType distributionSetType = savedRollout.getDistributionSet().getType();
// prepare the groups // prepare the groups
final List<RolloutGroup> groups = groupList.stream() final List<RolloutGroup> groups = groupList.stream()
@@ -255,13 +268,13 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
.collect(Collectors.toList()); .collect(Collectors.toList());
groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions); groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions);
RolloutHelper.verifyRemainingTargets( RolloutHelper.verifyRemainingTargets(calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(),
calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt())); savedRollout.getCreatedAt(), distributionSetType.getId()));
// check if we need to enforce the 'max targets per group' quota // check if we need to enforce the 'max targets per group' quota
if (quotaManagement.getMaxTargetsPerRolloutGroup() > 0) { if (quotaManagement.getMaxTargetsPerRolloutGroup() > 0) {
validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt()) validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt(),
.getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota); distributionSetType.getId()).getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota);
} }
// create and persist the groups (w/o filling them with targets) // 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()))); new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
} }
@Override
@Async
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> 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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -524,7 +522,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
@Override @Override
public Page<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) { public Page<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
Page<JpaRollout> rollouts; final Page<JpaRollout> rollouts;
final Specification<JpaRollout> spec = RolloutSpecification.isDeletedWithDistributionSet(deleted); final Specification<JpaRollout> spec = RolloutSpecification.isDeletedWithDistributionSet(deleted);
rollouts = rolloutRepository.findAll(spec, pageable); rollouts = rolloutRepository.findAll(spec, pageable);
setRolloutStatusDetails(rollouts); setRolloutStatusDetails(rollouts);
@@ -599,8 +597,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
* Enforces the quota defining the maximum number of {@link Target}s per * Enforces the quota defining the maximum number of {@link Target}s per
* {@link RolloutGroup}. * {@link RolloutGroup}.
* *
* @param group
* The rollout group
* @param requested * @param requested
* number of targets to check * number of targets to check
*/ */
@@ -626,4 +622,93 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
}); });
} }
private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets, final Long dsTypeId) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
final Map<String, Long> targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(),
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<RolloutGroup> groups,
final RolloutGroup group, final int groupIndex, final Map<String, Long> targetFilterCounts) {
// there can't be overlapping targets in the first group
if (groupIndex == 0) {
return 0;
}
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter,
previousGroups, group);
if (targetFilterCounts.containsKey(overlappingTargetsFilter)) {
return targetFilterCounts.get(overlappingTargetsFilter);
} else {
final long overlappingTargets = targetManagement.countByRsql(overlappingTargetsFilter);
targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets);
return overlappingTargets;
}
}
private long calculateRemainingTargets(final List<RolloutGroup> 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<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> 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));
}
} }

View File

@@ -786,10 +786,14 @@ public class JpaTargetManagement implements TargetManagement {
public long countByRsql(final String targetFilterQuery) { public long countByRsql(final String targetFilterQuery) {
final Specification<JpaTarget> specs = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, final Specification<JpaTarget> specs = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database); virtualPropertyReplacer, database);
return targetRepository.count((root, query, cb) -> { return targetRepository.count(specs);
query.distinct(true); }
return specs.toPredicate(root, query, cb);
}); @Override
public long countByRsqlAndCompatible(final String targetFilterQuery, final Long dsTypeId) {
final Specification<JpaTarget> rsqlSpec = RSQLUtility.buildRsqlSpecification(targetFilterQuery,
TargetFields.class, virtualPropertyReplacer, database);
return targetRepository.count(rsqlSpec.and(TargetSpecifications.isCompatibleWithDistributionSetType(dsTypeId)));
} }
@Override @Override

View File

@@ -664,17 +664,15 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
RolloutManagement rolloutManagement(final TargetManagement targetManagement, RolloutManagement rolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, final TenantAware tenantAware, final LockRegistry lockRegistry, final JpaProperties properties,
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, final RolloutApprovalStrategy rolloutApprovalStrategy,
final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) { final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) {
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement, return new JpaRolloutManagement(targetManagement, distributionSetManagement, eventPublisherHolder,
distributionSetManagement, context, eventPublisherHolder, virtualPropertyReplacer, txManager, virtualPropertyReplacer, txManager, tenantAware, lockRegistry, properties.getDatabase(),
tenantAware, lockRegistry, properties.getDatabase(), rolloutApprovalStrategy, rolloutApprovalStrategy, tenantConfigurationManagement, systemSecurityContext, rolloutExecutor);
tenantConfigurationManagement, systemSecurityContext, rolloutExecutor);
} }
/** /**

View File

@@ -394,7 +394,7 @@ public final class TargetSpecifications {
// isNull predicate first // isNull predicate first
final Predicate targetTypeIsNull = targetRoot.get(JpaTarget_.targetType).isNull(); 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 // isNotNull predicate first
final Predicate targetTypeNotNull = targetRoot.get(JpaTarget_.targetType).isNotNull(); 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, 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<JpaTarget> root, final CriteriaBuilder cb, private static Path<Long> getDsTypeIdPath(final Root<JpaTarget> root) {
final Long dsTypeId) {
final Join<JpaTarget, JpaTargetType> targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT); final Join<JpaTarget, JpaTargetType> targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT);
targetTypeJoin.fetch(JpaTargetType_.distributionSetTypes); targetTypeJoin.fetch(JpaTargetType_.distributionSetTypes);
final SetJoin<JpaTargetType, JpaDistributionSetType> dsTypeTargetTypeJoin = targetTypeJoin final SetJoin<JpaTargetType, JpaDistributionSetType> dsTypeTargetTypeJoin = targetTypeJoin
.join(JpaTargetType_.distributionSetTypes, JoinType.LEFT); .join(JpaTargetType_.distributionSetTypes, JoinType.LEFT);
return cb.equal(dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id), dsTypeId); return dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id);
} }
/** /**

View File

@@ -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.TenantConfigurationCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; 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.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; 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.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; 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.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType; 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.") @Description("Verify that the DistributionSet assignment fails for target with incompatible target type.")
void verifyDSAssignmentFailsForTargetsWithIncompatibleTargetTypes() { void verifyDSAssignmentFailsForTargetsWithIncompatibleTargetTypes() {
final DistributionSet ds = testdataFactory.createDistributionSet("test-ds"); 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 Target target = testdataFactory.createTarget("test-target", "test-target", targetType.getId());
final DeploymentRequest deploymentRequest = DeploymentManagement final DeploymentRequest deploymentRequest = DeploymentManagement
.deploymentRequest(target.getControllerId(), ds.getId()).build(); .deploymentRequest(target.getControllerId(), ds.getId()).build();
final List<DeploymentRequest> deploymentRequests = Collections.singletonList(deploymentRequest); final List<DeploymentRequest> 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<DeploymentRequest> deploymentRequests = Collections.singletonList(deploymentRequestWithEmptyType);
assertThatExceptionOfType(IncompatibleTargetTypeException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests)); .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests));
} }

View File

@@ -1205,8 +1205,7 @@ public class TestdataFactory {
/** /**
* Finds {@link TargetType} in repository with given * Finds {@link TargetType} in repository with given
* {@link TargetType#getName()} or creates if it does not exist yet. No ds * {@link TargetType#getName()} or creates if it does not exist yet. No ds types
* types
* are assigned on creation. * are assigned on creation.
* *
* @param targetTypeName * @param targetTypeName
@@ -1222,8 +1221,7 @@ public class TestdataFactory {
/** /**
* Creates {@link TargetType} in repository with given * Creates {@link TargetType} in repository with given
* {@link TargetType#getName()}. Compatible distribution set types are * {@link TargetType#getName()}. Compatible distribution set types are assigned
* assigned
* on creation * on creation
* *
* @param targetTypeName * @param targetTypeName
@@ -1256,8 +1254,8 @@ public class TestdataFactory {
} }
/** /**
* Creates a distribution set and directly invalidates it. No actions will * Creates a distribution set and directly invalidates it. No actions will be
* be canceled and no rollouts will be stopped with this invalidation. * canceled and no rollouts will be stopped with this invalidation.
* *
* @return created invalidated {@link DistributionSet} * @return created invalidated {@link DistributionSet}
*/ */
@@ -1269,8 +1267,8 @@ public class TestdataFactory {
} }
/** /**
* Creates a distribution set that has no software modules assigned, so it * Creates a distribution set that has no software modules assigned, so it is
* is incomplete. * incomplete.
* *
* @return created incomplete {@link DistributionSet} * @return created incomplete {@link DistributionSet}
*/ */

View File

@@ -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_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_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_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_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INCOMPLETE, 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); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STOP_ROLLOUT_FAILED, HttpStatus.LOCKED);

View File

@@ -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.HawkbitUIErrorHandler;
import org.eclipse.hawkbit.ui.error.extractors.ConstraintViolationErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.ConstraintViolationErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.EntityNotFoundErrorExtractor; 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.InsufficientPermissionErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.InvalidDistributionSetErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.InvalidDistributionSetErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.UiErrorDetailsExtractor; import org.eclipse.hawkbit.ui.error.extractors.UiErrorDetailsExtractor;
@@ -144,9 +145,21 @@ public class MgmtUiConfiguration {
return new EntityNotFoundErrorExtractor(i18n); 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. * UI Insufficient Permission Error details extractor bean.
* *
* @param i18n * @param i18n
* VaadinMessageSource * VaadinMessageSource
* @return UI InsufficientPermission Error details extractor * @return UI InsufficientPermission Error details extractor

View File

@@ -26,7 +26,8 @@ public class RolloutToProxyRolloutMapper extends AbstractNamedEntityToProxyNamed
mapNamedEntityAttributes(rollout, proxyRollout); mapNamedEntityAttributes(rollout, proxyRollout);
final DistributionSet ds = rollout.getDistributionSet(); 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 proxyRollout
.setNumberOfGroups(rollout.getRolloutGroupsCreated() > 0 ? rollout.getRolloutGroupsCreated() : null); .setNumberOfGroups(rollout.getRolloutGroupsCreated() > 0 ? rollout.getRolloutGroupsCreated() : null);
proxyRollout.setForcedTime(rollout.getForcedTime() > 0 ? rollout.getForcedTime() : null); proxyRollout.setForcedTime(rollout.getForcedTime() > 0 ? rollout.getForcedTime() : null);

View File

@@ -38,7 +38,8 @@ public class TargetFilterQueryToProxyTargetFilterMapper
if (distributionSet != null) { if (distributionSet != null) {
proxyTargetFilter.setAutoAssignmentEnabled(true); proxyTargetFilter.setAutoAssignmentEnabled(true);
proxyTargetFilter.setDistributionSetInfo(new ProxyDistributionSetInfo(distributionSet.getId(), 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()); proxyTargetFilter.setAutoAssignActionType(targetFilterQuery.getAutoAssignActionType());
} }

View File

@@ -87,7 +87,6 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa
} }
/** /**
* Flag that indicates if the distribution set is valid. * Flag that indicates if the distribution set is valid.
* *
* @return <code>true</code> if the distribution set is valid, otherwise * @return <code>true</code> if the distribution set is valid, otherwise
@@ -163,6 +162,7 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa
ds.setId(dsInfo.getId()); ds.setId(dsInfo.getId());
ds.setName(dsInfo.getName()); ds.setName(dsInfo.getName());
ds.setVersion(dsInfo.getVersion()); ds.setVersion(dsInfo.getVersion());
ds.setTypeInfo(new ProxyTypeInfo(dsInfo.getDsTypeId(), null));
ds.setNameVersion(dsInfo.getNameVersion()); ds.setNameVersion(dsInfo.getNameVersion());
ds.setIsValid(dsInfo.isValid()); 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() { public ProxyDistributionSetInfo getInfo() {
return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getIsValid()); return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getTypeInfo().getId(), getIsValid());
} }
} }

View File

@@ -21,6 +21,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
private String name; private String name;
private String version; private String version;
private String nameVersion; private String nameVersion;
private Long dsTypeId;
private boolean isValid; private boolean isValid;
/** /**
@@ -39,12 +40,18 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
* distribution set name * distribution set name
* @param version * @param version
* distribution set 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); super(id);
this.name = name; this.name = name;
this.version = version; this.version = version;
this.dsTypeId = dsTypeId;
this.isValid = isValid; this.isValid = isValid;
this.nameVersion = HawkbitCommonUtil.getFormattedNameVersion(name, version); this.nameVersion = HawkbitCommonUtil.getFormattedNameVersion(name, version);
} }
@@ -81,13 +88,23 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
this.isValid = isValid; this.isValid = isValid;
} }
public Long getDsTypeId() {
return dsTypeId;
}
public void setDsTypeId(final Long dsTypeId) {
this.dsTypeId = dsTypeId;
}
@Override @Override
public int hashCode() { public int hashCode() {
// nameVersion is ignored because it is a composition of name and // nameVersion is ignored because it is a composition of name and
// version // 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 @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
if (obj == null) { if (obj == null) {
@@ -102,6 +119,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
// version // version
return Objects.equals(this.getId(), other.getId()) && Objects.equals(this.getName(), other.getName()) return Objects.equals(this.getId(), other.getId()) && Objects.equals(this.getName(), other.getName())
&& Objects.equals(this.getVersion(), other.getVersion()) && Objects.equals(this.getVersion(), other.getVersion())
&& Objects.equals(this.getDsTypeId(), other.getDsTypeId())
&& Objects.equals(this.isValid(), other.isValid()); && Objects.equals(this.isValid(), other.isValid());
} }
} }

View File

@@ -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<UiErrorDetails> 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())));
}
}

View File

@@ -64,8 +64,8 @@ public class DeploymentAssignmentWindowController {
* @param deploymentManagement * @param deploymentManagement
* DeploymentManagement * DeploymentManagement
*/ */
public DeploymentAssignmentWindowController(final CommonUiDependencies uiDependencies, final UiProperties uiProperties, public DeploymentAssignmentWindowController(final CommonUiDependencies uiDependencies,
final DeploymentManagement deploymentManagement) { final UiProperties uiProperties, final DeploymentManagement deploymentManagement) {
this.i18n = uiDependencies.getI18n(); this.i18n = uiDependencies.getI18n();
this.eventBus = uiDependencies.getEventBus(); this.eventBus = uiDependencies.getEventBus();
this.notification = uiDependencies.getUiNotification(); this.notification = uiDependencies.getUiNotification();

View File

@@ -56,6 +56,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
private final GridLayout layout; private final GridLayout layout;
private String targetFilter; private String targetFilter;
private Long dsTypeId;
private final List<AdvancedGroupRow> groupRows; private final List<AdvancedGroupRow> groupRows;
private int lastGroupIndex; private int lastGroupIndex;
@@ -237,7 +238,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
private void validateTargetsPerGroup() { private void validateTargetsPerGroup() {
resetErrors(); resetErrors();
if (StringUtils.isEmpty(targetFilter)) { if (StringUtils.isEmpty(targetFilter) || dsTypeId == null) {
return; return;
} }
@@ -245,7 +246,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
final List<RolloutGroupCreate> groupsCreate = getRolloutGroupsCreateFromDefinitions( final List<RolloutGroupCreate> groupsCreate = getRolloutGroupsCreateFromDefinitions(
getAdvancedRolloutGroupDefinitions()); getAdvancedRolloutGroupDefinitions());
final ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups = rolloutManagement final ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups = rolloutManagement
.validateTargetsInGroups(groupsCreate, targetFilter, System.currentTimeMillis()); .validateTargetsInGroups(groupsCreate, targetFilter, System.currentTimeMillis(), dsTypeId);
final UI ui = UI.getCurrent(); final UI ui = UI.getCurrent();
validateTargetsInGroups.addCallback(validation -> ui.access(() -> updateGroupsByValidation(validation)), 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. * YOU SHOULD NOT CALL THIS METHOD MANUALLY. It's only for the callback. Only 1
* Only 1 runningValidation should be executed. If this runningValidation is * runningValidation should be executed. If this runningValidation is done, then
* done, then this method is called. Maybe then a new runningValidation is * this method is called. Maybe then a new runningValidation is executed.
* executed.
* *
*/ */
private void updateGroupsByValidation(final RolloutGroupsValidation validation) { private void updateGroupsByValidation(final RolloutGroupsValidation validation) {
@@ -338,7 +338,30 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
*/ */
public void setTargetFilter(final String targetFilter) { public void setTargetFilter(final String targetFilter) {
this.targetFilter = 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(); updateValidation();
} }

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.data.Binder; import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding; import com.vaadin.data.Binder.Binding;
import com.vaadin.data.HasValue;
import com.vaadin.data.ValidationException; import com.vaadin.data.ValidationException;
import com.vaadin.data.Validator; import com.vaadin.data.Validator;
import com.vaadin.data.validator.LongRangeValidator; import com.vaadin.data.validator.LongRangeValidator;
@@ -72,6 +73,7 @@ public class RolloutFormLayout extends ValidatableLayout {
private Long totalTargets; private Long totalTargets;
private Consumer<String> filterQueryChangedListener; private Consumer<String> filterQueryChangedListener;
private Consumer<Long> distSetChangedListener;
/** /**
* Constructor for RolloutFormLayout * Constructor for RolloutFormLayout
@@ -190,11 +192,8 @@ public class RolloutFormLayout extends ValidatableLayout {
} }
private void addValueChangeListeners() { private void addValueChangeListeners() {
targetFilterQueryCombo.getComponent().addValueChangeListener(event -> { targetFilterQueryCombo.getComponent().addValueChangeListener(filterQueryChangedListener());
if (filterQueryChangedListener != null) { dsCombo.addValueChangeListener(distSetChangedListener());
filterQueryChangedListener.accept(event.getValue() != null ? event.getValue().getQuery() : null);
}
});
actionTypeLayout.getComponent().getActionTypeOptionGroup().addValueChangeListener( actionTypeLayout.getComponent().getActionTypeOptionGroup().addValueChangeListener(
event -> actionTypeLayout.setRequired(event.getValue() == ActionType.TIMEFORCED)); event -> actionTypeLayout.setRequired(event.getValue() == ActionType.TIMEFORCED));
@@ -202,6 +201,26 @@ public class RolloutFormLayout extends ValidatableLayout {
event -> autoStartOptionGroupLayout.setRequired(event.getValue() == AutoStartOption.SCHEDULED)); event -> autoStartOptionGroupLayout.setRequired(event.getValue() == AutoStartOption.SCHEDULED));
} }
private HasValue.ValueChangeListener<ProxyTargetFilterQuery> filterQueryChangedListener() {
return event -> {
if (filterQueryChangedListener != null) {
filterQueryChangedListener.accept(event.getValue() != null ? event.getValue().getQuery() : null);
}
};
}
private HasValue.ValueChangeListener<ProxyDistributionSet> 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 * Add rollout form to add layout
* *
@@ -285,6 +304,16 @@ public class RolloutFormLayout extends ValidatableLayout {
this.filterQueryChangedListener = filterQueryChangedListener; this.filterQueryChangedListener = filterQueryChangedListener;
} }
/**
* Sets the changed listener for distribution set
*
* @param distSetChangedListener
* Changed listener
*/
public void setDistSetChangedListener(final Consumer<Long> distSetChangedListener) {
this.distSetChangedListener = distSetChangedListener;
}
/** /**
* Sets the count of total targets * Sets the count of total targets
* *

View File

@@ -61,7 +61,7 @@ public class VisualGroupDefinitionLayout {
if (groupDefinitionMode == GroupDefinitionMode.SIMPLE) { if (groupDefinitionMode == GroupDefinitionMode.SIMPLE) {
updateBySimpleGroupsDefinition(); updateBySimpleGroupsDefinition();
} else { } else if (groupDefinitionMode == GroupDefinitionMode.ADVANCED) {
updateByAdvancedGroupsDefinition(); updateByAdvancedGroupsDefinition();
} }
} }

View File

@@ -42,6 +42,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
private final VisualGroupDefinitionLayout visualGroupDefinitionLayout; private final VisualGroupDefinitionLayout visualGroupDefinitionLayout;
private String filterQuery; private String filterQuery;
private Long dsTypeId;
private Long totalTargets; private Long totalTargets;
private int noOfGroups; private int noOfGroups;
private List<ProxyAdvancedRolloutGroup> advancedRolloutGroupDefinitions; private List<ProxyAdvancedRolloutGroup> advancedRolloutGroupDefinitions;
@@ -82,24 +83,33 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
} }
private void addValueChangeListeners() { private void addValueChangeListeners() {
rolloutFormLayout.setFilterQueryChangedListener(this::onTargetFilterQueryChange); rolloutFormLayout.setFilterQueryChangedListener(this::onFilterQueryChange);
rolloutFormLayout.setDistSetChangedListener(this::onDistSetTypeChange);
groupsDefinitionTabs.addSelectedTabChangeListener(event -> onGroupDefinitionTabChanged()); groupsDefinitionTabs.addSelectedTabChangeListener(event -> onGroupDefinitionTabChanged());
simpleGroupsLayout.setNoOfGroupsChangedListener(this::onNoOfSimpleGroupsChanged); simpleGroupsLayout.setNoOfGroupsChangedListener(this::onNoOfSimpleGroupsChanged);
advancedGroupsLayout.setAdvancedGroupDefinitionsChangedListener(this::onAdvancedGroupsChanged); advancedGroupsLayout.setAdvancedGroupDefinitionsChangedListener(this::onAdvancedGroupsChanged);
} }
private void onTargetFilterQueryChange(final String filterQuery) { private void onFilterQueryChange(final String filterQuery) {
this.filterQuery = filterQuery; this.filterQuery = filterQuery;
updateTotalTargets();
totalTargets = !StringUtils.isEmpty(filterQuery) ? targetManagement.countByRsql(filterQuery) : null;
updateTotalTargetsAwareComponents();
if (isAdvancedGroupsTabSelected()) { if (isAdvancedGroupsTabSelected()) {
advancedGroupsLayout.setTargetFilter(filterQuery); 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); rolloutFormLayout.setTotalTargets(totalTargets);
visualGroupDefinitionLayout.setTotalTargets(totalTargets); visualGroupDefinitionLayout.setTotalTargets(totalTargets);
if (isSimpleGroupsTabSelected()) { 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() { private boolean isSimpleGroupsTabSelected() {
return groupsDefinitionTabs.getSelectedTab().equals(simpleGroupsLayout.getLayout()); return groupsDefinitionTabs.getSelectedTab().equals(simpleGroupsLayout.getLayout());
} }
@@ -128,7 +148,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
if (isAdvancedGroupsTabSelected()) { if (isAdvancedGroupsTabSelected()) {
adaptAdvancedGroupsValidation(); adaptAdvancedGroupsValidation();
advancedGroupsLayout.setTargetFilter(filterQuery); advancedGroupsLayout.setTargetFilterAndDsType(filterQuery, dsTypeId);
visualGroupDefinitionLayout.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED); visualGroupDefinitionLayout.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED);
visualGroupDefinitionLayout.setAdvancedRolloutGroupDefinitions(advancedRolloutGroupDefinitions); visualGroupDefinitionLayout.setAdvancedRolloutGroupDefinitions(advancedRolloutGroupDefinitions);
@@ -209,7 +229,6 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
proxyEntity.setAdvancedRolloutGroupDefinitions(advancedGroupsLayout.getAdvancedRolloutGroupDefinitions()); proxyEntity.setAdvancedRolloutGroupDefinitions(advancedGroupsLayout.getAdvancedRolloutGroupDefinitions());
proxyEntity.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED); proxyEntity.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED);
} }
return proxyEntity; return proxyEntity;
} }
} }

View File

@@ -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_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 CRON_VALIDATION_ERROR = "message.maintenancewindow.schedule.validation.error";
public static final String TOOLTIP_OVERDUE = "tooltip.overdue"; public static final String TOOLTIP_OVERDUE = "tooltip.overdue";

View File

@@ -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.actions = {0} action(s) will be canceled
message.invalidate.distributionset.affected.entities.rollouts = {0} rollout(s) will be stopped 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.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 info
action.target.table.selectall = Select all (Ctrl+A) action.target.table.selectall = Select all (Ctrl+A)