Skip to content

Adapt UI for target type compatibility check #1189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ public enum SpServerError {
"Storage quota will be exceeded if file is uploaded."),

/**
* error message, which describes that the action can not be canceled cause
* the action is inactive.
* error message, which describes that the action can not be canceled cause the
* action is inactive.
*/
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable",
"Only active actions which are in status pending are cancelable."),

/**
* error message, which describes that the action can not be force quit
* cause the action is inactive.
* error message, which describes that the action can not be force quit cause
* the action is inactive.
*/
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable",
"Only active actions which are in status pending can be force quit."),
Expand Down Expand Up @@ -250,8 +250,7 @@ public enum SpServerError {
"Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future."),

/**
* Error message informing that the action type for auto-assignment is
* invalid.
* Error message informing that the action type for auto-assignment is invalid.
*/
SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType",
"The given action type for auto-assignment is invalid: allowed values are ['forced', 'soft', 'downloadonly']"),
Expand All @@ -277,6 +276,9 @@ public enum SpServerError {

SP_TARGET_TYPE_IN_USE("hawkbit.server.error.target.type.used", "Target type is still in use by a target."),

SP_TARGET_TYPE_INCOMPATIBLE("hawkbit.server.error.target.type.incompatible",
"Target type of target is not compatible with distribution set."),

SP_STOP_ROLLOUT_FAILED("hawkbit.server.error.stopRolloutFailed", "Stopping the rollout failed");

private final String key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,17 @@ Rollout create(@Valid @NotNull RolloutCreate rollout, @NotNull @Valid List<Rollo
RolloutGroupConditions conditions);

/**
* Calculates how many targets are addressed by each rollout group and
* returns the validation information.
* Calculates how many targets are addressed by each rollout group and returns
* the validation information.
*
* @param groups
* a list of rollout groups
* @param targetFilter
* the rollout
* @param createdAt
* timestamp when the rollout was created
* @param distSetId
* ID of the distribution set of the rollout
* @return the validation information
* @throws RolloutIllegalStateException
* thrown when no targets are targeted by the rollout
Expand All @@ -199,7 +201,7 @@ Rollout create(@Valid @NotNull RolloutCreate rollout, @NotNull @Valid List<Rollo
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ)
ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(@Valid List<RolloutGroupCreate> groups,
String targetFilter, Long createdAt);
String targetFilter, Long createdAt, @NotNull Long distSetId);

/**
* Retrieves all rollouts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@ long countByFilters(Collection<TargetUpdateStatus> status, Boolean overdueState,
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByRsql(@NotEmpty String rsqlParam);

/**
* Count all targets for given {@link TargetFilterQuery} and that are compatible
* with the passed {@link DistributionSetType}.
*
* @param rsqlParam
* filter definition in RSQL syntax
* @param dsTypeId
* ID of the {@link DistributionSetType} the targets need to be
* compatible
* @return the found number {@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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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> getTargetTypeName() {
return targetTypeNames;
}

public Collection<String> getDistributionSetTypeName() {
return distributionSetTypeNames;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import javax.validation.ConstraintDeclarationException;

import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
Expand Down Expand Up @@ -82,11 +85,12 @@ protected AbstractRolloutManagement(final TargetManagement targetManagement,
}

protected RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets) {
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(), targetManagement::countByRsql));
.collect(Collectors.toMap(Function.identity(),
groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId)));

long unusedTargetsCount = 0;

Expand Down Expand Up @@ -139,30 +143,36 @@ private long countOverlappingTargetsWithPreviousGroups(final String baseFilter,
}

protected long calculateRemainingTargets(final List<RolloutGroup> groups, final String targetFilter,
final Long createdAt) {
final Long createdAt, final Long dsTypeId) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countByRsql(baseFilter);
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);
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 String targetFilter, final Long createdAt, final Long distSetId) {

final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countByRsql(baseFilter);

final DistributionSetType distributionSetType = distributionSetManagement.get(distSetId)
.map(DistributionSet::getType)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distSetId));
final long totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, distributionSetType.getId());

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));
return new AsyncResult<>(
validateTargetsInGroups(groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()),
baseFilter, totalTargets, distributionSetType.getId()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.DistributionSetTypeNotInTargetTypeException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
Expand Down Expand Up @@ -256,14 +257,14 @@ private void checkForTargetTypeCompatibility(final List<DeploymentRequest> deplo

private void checkCompatibilityForSingleDsAssignment(final Long distSetId, final List<String> controllerIds) {
final DistributionSetType distSetType = distributionSetManagement.getValidAndComplete(distSetId).getType();
final Set<Long> incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT)
final Set<String> incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT)
.stream()
.map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids)
.and(TargetSpecifications.notCompatibleWithDistributionSetType(distSetType.getId()))))
.flatMap(List::stream).map(Target::getTargetType).map(TargetType::getId).collect(Collectors.toSet());
.flatMap(List::stream).map(Target::getTargetType).map(TargetType::getName).collect(Collectors.toSet());

if (!incompatibleTargetTypes.isEmpty()) {
throw new DistributionSetTypeNotInTargetTypeException(distSetType.getId(), incompatibleTargetTypes);
throw new IncompatibleTargetTypeException(incompatibleTargetTypes, distSetType.getName());
}
}

Expand All @@ -278,9 +279,9 @@ private void checkCompatibilityForMultiDsAssignment(final String controllerId, f
incompatibleDistSetTypes.removeAll(target.getTargetType().getCompatibleDistributionSetTypes());

if (!incompatibleDistSetTypes.isEmpty()) {
final Set<Long> distSetTypeIds = incompatibleDistSetTypes.stream().map(DistributionSetType::getId)
final Set<String> distSetTypeNames = incompatibleDistSetTypes.stream().map(DistributionSetType::getName)
.collect(Collectors.toSet());
throw new DistributionSetTypeNotInTargetTypeException(distSetTypeIds, target.getTargetType().getId());
throw new IncompatibleTargetTypeException(target.getTargetType().getName(), distSetTypeNames);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import javax.validation.ConstraintDeclarationException;
import javax.validation.ValidationException;

import org.eclipse.hawkbit.repository.AbstractRolloutManagement;
Expand Down Expand Up @@ -54,12 +53,12 @@
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
Expand All @@ -80,13 +79,10 @@
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.validation.annotation.Validated;

import com.google.common.collect.Lists;
Expand Down Expand Up @@ -203,7 +199,8 @@ public Rollout create(final RolloutCreate rollout, final List<RolloutGroupCreate

private JpaRollout createRollout(final JpaRollout rollout) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final Long totalTargets = targetManagement.countByRsql(rollout.getTargetFilterQuery());
final Long totalTargets = targetManagement.countByRsqlAndCompatible(rollout.getTargetFilterQuery(),
rollout.getDistributionSet().getType().getId());
if (totalTargets == 0) {
throw new ValidationException("Rollout does not match any existing targets");
}
Expand Down Expand Up @@ -248,20 +245,21 @@ private Rollout createRolloutGroups(final List<RolloutGroupCreate> groupList,
final RolloutGroupConditions conditions, final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
final JpaRollout savedRollout = (JpaRollout) rollout;
final DistributionSetType distributionSetType = savedRollout.getDistributionSet().getType();

// prepare the groups
final List<RolloutGroup> groups = groupList.stream()
.map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.collect(Collectors.toList());
groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions);

RolloutHelper.verifyRemainingTargets(
calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt()));
RolloutHelper.verifyRemainingTargets(calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(),
savedRollout.getCreatedAt(), distributionSetType.getId()));

// check if we need to enforce the 'max targets per group' quota
if (quotaManagement.getMaxTargetsPerRolloutGroup() > 0) {
validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt())
.getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota);
validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt(),
distributionSetType.getId()).getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota);
}

// create and persist the groups (w/o filling them with targets)
Expand Down Expand Up @@ -299,21 +297,6 @@ private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group
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
@Transactional
@Retryable(include = {
Expand Down Expand Up @@ -524,7 +507,7 @@ private JpaRollout getRolloutAndThrowExceptionIfNotFound(final Long rolloutId) {

@Override
public Page<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
Page<JpaRollout> rollouts;
final Page<JpaRollout> rollouts;
final Specification<JpaRollout> spec = RolloutSpecification.isDeletedWithDistributionSet(deleted);
rollouts = rolloutRepository.findAll(spec, pageable);
setRolloutStatusDetails(rollouts);
Expand Down
Loading