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:
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user