Rollouts can be deleted (#436)
* Management UI Signed-off-by: Melanie Retter <melanie.retter@bosch-si.com> * Repository Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com> * Optimisations and scheduler deleting enabled Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
committed by
Kai Zimmermann
parent
804522f966
commit
5628d625e8
@@ -15,6 +15,7 @@ import java.util.Optional;
|
||||
import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.DistributionSetTagDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||
@@ -91,12 +92,13 @@ public class EventType {
|
||||
|
||||
// download
|
||||
TYPES.put(20, DownloadProgressEvent.class);
|
||||
|
||||
|
||||
TYPES.put(21, SoftwareModuleCreatedEvent.class);
|
||||
TYPES.put(22, SoftwareModuleDeletedEvent.class);
|
||||
TYPES.put(23, SoftwareModuleUpdatedEvent.class);
|
||||
|
||||
TYPES.put(24, TargetPollEvent.class);
|
||||
TYPES.put(25, RolloutDeletedEvent.class);
|
||||
}
|
||||
|
||||
private int value;
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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.tenancy.TenantAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
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.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
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 ApplicationEventPublisher eventPublisher;
|
||||
|
||||
protected final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
|
||||
protected final PlatformTransactionManager txManager;
|
||||
|
||||
protected final TenantAware tenantAware;
|
||||
|
||||
protected final LockRegistry lockRegistry;
|
||||
|
||||
protected AbstractRolloutManagement(final TargetManagement targetManagement,
|
||||
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
|
||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware,
|
||||
final LockRegistry lockRegistry) {
|
||||
this.targetManagement = targetManagement;
|
||||
this.deploymentManagement = deploymentManagement;
|
||||
this.rolloutGroupManagement = rolloutGroupManagement;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.context = context;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||
this.txManager = txManager;
|
||||
this.tenantAware = tenantAware;
|
||||
this.lockRegistry = lockRegistry;
|
||||
}
|
||||
|
||||
protected int runInNewTransaction(final String transactionName, final TransactionCallback<Integer> action) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName(transactionName);
|
||||
def.setReadOnly(false);
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
def.setIsolationLevel(Isolation.READ_UNCOMMITTED.value());
|
||||
return new TransactionTemplate(txManager, def).execute(action);
|
||||
}
|
||||
|
||||
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::countTargetByTargetFilterQuery));
|
||||
|
||||
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.countTargetByTargetFilterQuery(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.countTargetByTargetFilterQuery(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.countTargetByTargetFilterQuery(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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
|
||||
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
|
||||
/**
|
||||
* A collection of static helper methods for the {@link RolloutManagement}
|
||||
*/
|
||||
public final class RolloutHelper {
|
||||
private RolloutHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the required success condition and action are actually set.
|
||||
*
|
||||
* @param conditions
|
||||
* input conditions and actions
|
||||
*/
|
||||
public static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) {
|
||||
if (conditions.getSuccessCondition() == null) {
|
||||
throw new ConstraintViolationException("Rollout group is missing success condition");
|
||||
}
|
||||
if (conditions.getSuccessAction() == null) {
|
||||
throw new ConstraintViolationException("Rollout group is missing success action");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the group has the required success condition and action and
|
||||
* a falid target percentage.
|
||||
*
|
||||
* @param group
|
||||
* the input group
|
||||
* @return the verified group
|
||||
*/
|
||||
public static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) {
|
||||
if (group.getTargetPercentage() < 1F || group.getTargetPercentage() > 100F) {
|
||||
throw new ConstraintViolationException("Target percentage has to be between 1 and 100");
|
||||
}
|
||||
|
||||
if (group.getSuccessCondition() == null) {
|
||||
throw new ConstraintViolationException("Rollout group is missing success condition");
|
||||
}
|
||||
if (group.getSuccessAction() == null) {
|
||||
throw new ConstraintViolationException("Rollout group is missing success action");
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the supplied amount of groups is in range
|
||||
*
|
||||
* @param amountGroup
|
||||
* amount of groups
|
||||
*/
|
||||
public static void verifyRolloutGroupParameter(final int amountGroup) {
|
||||
if (amountGroup <= 0) {
|
||||
throw new ConstraintViolationException("the amountGroup must be greater than zero");
|
||||
} else if (amountGroup > 500) {
|
||||
throw new ConstraintViolationException("the amountGroup must not be greater than 500");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the supplied percentage is in range
|
||||
*
|
||||
* @param percentage
|
||||
* the percentage
|
||||
*/
|
||||
public static void verifyRolloutGroupTargetPercentage(final float percentage) {
|
||||
if (percentage <= 0) {
|
||||
throw new ConstraintViolationException("the percentage must be greater than zero");
|
||||
} else if (percentage > 100) {
|
||||
throw new ConstraintViolationException("the percentage must not be greater than 100");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the target filter query to only match targets that were created
|
||||
* after the Rollout.
|
||||
*
|
||||
* @param rollout
|
||||
* Rollout to derive the filter from
|
||||
* @return resulting target filter query
|
||||
*/
|
||||
public static String getTargetFilterQuery(final Rollout rollout) {
|
||||
return getTargetFilterQuery(rollout.getTargetFilterQuery(), rollout.getCreatedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetFilter
|
||||
* the target filter tp be extended
|
||||
* @param createdAt
|
||||
* timestamp
|
||||
* @return a target filter query that only matches targets that were created
|
||||
* after the provided timestamp.
|
||||
*/
|
||||
public static String getTargetFilterQuery(final String targetFilter, final Long createdAt) {
|
||||
if (createdAt != null) {
|
||||
return targetFilter + ";createdat=le=" + createdAt.toString();
|
||||
}
|
||||
return targetFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the Rollout is in the required status.
|
||||
*
|
||||
* @param rollout
|
||||
* the Rollout
|
||||
* @param status
|
||||
* the Status
|
||||
*/
|
||||
public static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) {
|
||||
if (!rollout.getStatus().equals(status)) {
|
||||
throw new RolloutIllegalStateException("Rollout is not in status " + status.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the groups of a Rollout to match a specific status and adds a
|
||||
* group to the result.
|
||||
*
|
||||
* @param rollout
|
||||
* the rollout
|
||||
* @param status
|
||||
* the required status for the groups
|
||||
* @param group
|
||||
* the group to add
|
||||
* @return list of groups
|
||||
*/
|
||||
public static List<Long> getGroupsByStatusIncludingGroup(final Rollout rollout,
|
||||
final RolloutGroup.RolloutGroupStatus status, final RolloutGroup group) {
|
||||
return rollout.getRolloutGroups().stream()
|
||||
.filter(innerGroup -> innerGroup.getStatus().equals(status) || innerGroup.equals(group))
|
||||
.map(RolloutGroup::getId).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the groups of a rollout by their Ids order
|
||||
*
|
||||
* @param rollout
|
||||
* the rollout
|
||||
* @return ordered list of groups
|
||||
*/
|
||||
public static List<RolloutGroup> getOrderedGroups(final Rollout rollout) {
|
||||
return rollout.getRolloutGroups().stream().sorted((group1, group2) -> {
|
||||
if (group1.getId() < group2.getId()) {
|
||||
return -1;
|
||||
}
|
||||
if (group1.getId() > group2.getId()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RSQL expression that matches all targets in the provided
|
||||
* groups. Links all target filter queries with OR.
|
||||
*
|
||||
* @param groups
|
||||
* the rollout groups
|
||||
* @return RSQL string without base filter of the Rollout. Can be an empty
|
||||
* string.
|
||||
*/
|
||||
public static String getAllGroupsTargetFilter(final List<RolloutGroup> groups) {
|
||||
if (groups.stream().anyMatch(group -> StringUtils.isEmpty(group.getTargetFilterQuery()))) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "(" + groups.stream().map(RolloutGroup::getTargetFilterQuery).distinct().sorted()
|
||||
.collect(Collectors.joining("),(")) + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RSQL Filter that matches all targets that are in the provided
|
||||
* group and in the provided groups.
|
||||
*
|
||||
* @param baseFilter
|
||||
* the base filter from the rollout
|
||||
* @param groups
|
||||
* the rollout groups
|
||||
* @param group
|
||||
* the target group
|
||||
* @return RSQL string without base filter of the Rollout. Can be an empty
|
||||
* string.
|
||||
*/
|
||||
public static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List<RolloutGroup> groups,
|
||||
final RolloutGroup group) {
|
||||
final String groupFilter = group.getTargetFilterQuery();
|
||||
// when any previous group has the same filter as the target group the
|
||||
// overlap is 100%
|
||||
if (isTargetFilterInGroups(groupFilter, groups)) {
|
||||
return concatAndTargetFilters(baseFilter, groupFilter);
|
||||
}
|
||||
final String previousGroupFilters = getAllGroupsTargetFilter(groups);
|
||||
if (StringUtils.isNotEmpty(previousGroupFilters)) {
|
||||
if (StringUtils.isNotEmpty(groupFilter)) {
|
||||
return concatAndTargetFilters(baseFilter, groupFilter, previousGroupFilters);
|
||||
} else {
|
||||
return concatAndTargetFilters(baseFilter, previousGroupFilters);
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(groupFilter)) {
|
||||
return concatAndTargetFilters(baseFilter, groupFilter);
|
||||
} else {
|
||||
return baseFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTargetFilterInGroups(final String groupFilter, final List<RolloutGroup> groups) {
|
||||
return StringUtils.isNotEmpty(groupFilter)
|
||||
&& groups.stream().anyMatch(prevGroup -> StringUtils.isNotEmpty(prevGroup.getTargetFilterQuery())
|
||||
&& prevGroup.getTargetFilterQuery().equals(groupFilter));
|
||||
}
|
||||
|
||||
private static String concatAndTargetFilters(final String... filters) {
|
||||
return "(" + Arrays.stream(filters).collect(Collectors.joining(");(")) + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseFilter
|
||||
* the base filter from the rollout
|
||||
* @param group
|
||||
* group for which the filter string should be created
|
||||
* @return the final target filter query for a rollout group
|
||||
*/
|
||||
static String getGroupTargetFilter(final String baseFilter, final RolloutGroup group) {
|
||||
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
||||
return baseFilter;
|
||||
} else {
|
||||
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that no targets are left
|
||||
*
|
||||
* @param targetCount
|
||||
* the count of left targets
|
||||
*/
|
||||
public static void verifyRemainingTargets(final long targetCount) {
|
||||
if (targetCount > 0) {
|
||||
throw new ConstraintViolationException(
|
||||
"Rollout groups don't match all targets that are targeted by the rollout");
|
||||
}
|
||||
if (targetCount != 0) {
|
||||
throw new ConstraintViolationException("Rollout groups target count verification failed");
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
|
||||
if (!(Rollout.RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user