Tune/fix action cleanup tenant properties (#2782)

* actions.cleanup.onQuotaHit.percent -> action.cleanup.onQuotaHit.percent
* action.cleanup.enabled - removed - instead enabled / disable <=> expire < / >= 0
* action.cleanup.actionExpiry -> action.cleanup.auto.expiry and action.cleanup.auto.status - so both are under action.cleanup.auto, and differentiate from on quota hit
* auto db convert of props with one backward incompatibility - if you had action.cleanup.enabled=true and not set action.cleanup.actionExpiry (assuming default 30 days) - auto cleanup will be disabled
  you should set action.cleanup.auto.expiry=2592000000 in order to get the old behavior
* Note that if you have configured global action cleanup the properties are changed also this config you shall change manually

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-28 12:40:37 +02:00
committed by GitHub
parent f9ff15f671
commit 64bdced682
16 changed files with 99 additions and 143 deletions

View File

@@ -47,7 +47,6 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.aspects.ExceptionMappingAspectHandler;
import org.eclipse.hawkbit.repository.jpa.autocleanup.AutoActionCleanup;
import org.eclipse.hawkbit.repository.jpa.autocleanup.AutoCleanupScheduler;
import org.eclipse.hawkbit.repository.jpa.autocleanup.CleanupTask;
import org.eclipse.hawkbit.repository.jpa.cluster.DistributedLockRepository;
import org.eclipse.hawkbit.repository.jpa.cluster.LockProperties;
import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager;
@@ -416,8 +415,7 @@ public class JpaRepositoryConfiguration {
* @return a new {@link AutoActionCleanup} bean
*/
@Bean
CleanupTask actionCleanup(final DeploymentManagement deploymentManagement,
final TenantConfigurationManagement configManagement) {
AutoCleanupScheduler.CleanupTask actionCleanup(final DeploymentManagement deploymentManagement, final TenantConfigurationManagement configManagement) {
return new AutoActionCleanup(deploymentManagement, configManagement);
}
@@ -434,10 +432,10 @@ public class JpaRepositoryConfiguration {
@ConditionalOnMissingBean
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.autocleanup.scheduler", name = "enabled", matchIfMissing = true)
AutoCleanupScheduler autoCleanupScheduler(final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry,
final List<CleanupTask> cleanupTasks) {
return new AutoCleanupScheduler(systemManagement, systemSecurityContext, lockRegistry, cleanupTasks);
AutoCleanupScheduler autoCleanupScheduler(
final List<AutoCleanupScheduler.CleanupTask> cleanupTasks,
final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry) {
return new AutoCleanupScheduler(cleanupTasks, systemManagement, systemSecurityContext, lockRegistry);
}
/**

View File

@@ -9,15 +9,12 @@
*/
package org.eclipse.hawkbit.repository.jpa.autocleanup;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ACTION_CLEANUP_ACTION_EXPIRY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ACTION_CLEANUP_ACTION_STATUS;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ACTION_CLEANUP_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ACTION_CLEANUP_AUTO_EXPIRY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ACTION_CLEANUP_AUTO_STATUS;
import java.io.Serializable;
import java.time.Instant;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@@ -28,31 +25,20 @@ import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
/**
* A cleanup task for {@link Action} entities which can be used to delete
* actions which are in a certain {@link Action.Status}. It is recommended to
* only clean up actions which have terminated already (i.e. actions in status
* CANCELLED or ERROR).
*
* The cleanup task can be enabled /disabled and configured on a per tenant
* basis.
* A cleanup task for {@link Action} entities which can be used to delete actions which are in a certain {@link Action.Status}.
* It is recommended to only clean up actions which have terminated already (i.e. actions in status CANCELLED or ERROR).
* <p/>
* The cleanup task can be enabled /disabled and configured on a per-tenant basis.
*/
@Slf4j
public class AutoActionCleanup implements CleanupTask {
public class AutoActionCleanup implements AutoCleanupScheduler.CleanupTask {
private static final String ID = "action-cleanup";
private static final boolean ACTION_CLEANUP_ENABLED_DEFAULT = false;
private static final long ACTION_CLEANUP_ACTION_EXPIRY_DEFAULT = TimeUnit.DAYS.toMillis(30);
private static final EnumSet<Status> EMPTY_STATUS_SET = EnumSet.noneOf(Status.class);
private final DeploymentManagement deploymentMgmt;
private final TenantConfigurationManagement config;
/**
* Constructs the action cleanup handler.
*
* @param deploymentMgmt The {@link DeploymentManagement} to operate on.
* @param configMgmt The {@link TenantConfigurationManagement} service.
*/
public AutoActionCleanup(final DeploymentManagement deploymentMgmt, final TenantConfigurationManagement configMgmt) {
this.deploymentMgmt = deploymentMgmt;
this.config = configMgmt;
@@ -60,17 +46,20 @@ public class AutoActionCleanup implements CleanupTask {
@Override
public void run() {
if (!isEnabled()) {
final TenantConfigurationValue<Long> expiryCV = config.getConfigurationValue(ACTION_CLEANUP_AUTO_EXPIRY, Long.class);
final long expiry = expiryCV != null ? expiryCV.getValue() : -1L;
if (expiry < 0L) {
log.debug("Action cleanup is disabled for this tenant...");
return;
}
final EnumSet<Status> status = getActionStatus();
if (!status.isEmpty()) {
final long lastModified = System.currentTimeMillis() - getExpiry();
final int actionsCount = deploymentMgmt.deleteActionsByStatusAndLastModifiedBefore(status, lastModified);
log.debug("Deleted {} actions in status {} which have not been modified since {} ({})", actionsCount,
status, Instant.ofEpochMilli(lastModified), lastModified);
} else {
final EnumSet<Status> status = getActionStatus();
if (status.isEmpty()) {
log.debug("Action cleanup is disabled for this tenant...");
} else {
final long lastModified = System.currentTimeMillis() - expiry;
final int actionsCount = deploymentMgmt.deleteActionsByStatusAndLastModifiedBefore(status, lastModified);
log.debug("Deleted {} actions in status {} which have not been modified since {} ({})",
actionsCount, status, Instant.ofEpochMilli(lastModified), lastModified);
}
}
}
@@ -79,27 +68,12 @@ public class AutoActionCleanup implements CleanupTask {
return ID;
}
private long getExpiry() {
final TenantConfigurationValue<Long> expiry = getConfigValue(ACTION_CLEANUP_ACTION_EXPIRY, Long.class);
return expiry != null ? expiry.getValue() : ACTION_CLEANUP_ACTION_EXPIRY_DEFAULT;
}
private EnumSet<Status> getActionStatus() {
final TenantConfigurationValue<String> statusStr = getConfigValue(ACTION_CLEANUP_ACTION_STATUS, String.class);
final TenantConfigurationValue<String> statusStr = config.getConfigurationValue(ACTION_CLEANUP_AUTO_STATUS, String.class);
if (statusStr != null) {
return Arrays.stream(statusStr.getValue().split("[;,]")).map(Status::valueOf)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(Status.class)));
return Arrays.stream(statusStr.getValue().split("[;,]"))
.map(Status::valueOf).collect(Collectors.toCollection(() -> EnumSet.noneOf(Status.class)));
}
return EMPTY_STATUS_SET;
}
private boolean isEnabled() {
final TenantConfigurationValue<Boolean> isEnabled = getConfigValue(ACTION_CLEANUP_ENABLED, Boolean.class);
return isEnabled != null ? isEnabled.getValue() : ACTION_CLEANUP_ENABLED_DEFAULT;
}
private <T extends Serializable> TenantConfigurationValue<T> getConfigValue(final String key,
final Class<T> valueType) {
return config.getConfigurationValue(key, valueType);
}
}

View File

@@ -36,15 +36,14 @@ public class AutoCleanupScheduler {
/**
* Constructs the cleanup schedulers and initializes it with a set of cleanup handlers.
*
* @param cleanupTasks A list of cleanup tasks.
* @param systemManagement Management APIs to invoke actions in a certain tenant context.
* @param systemSecurityContext The system security context.
* @param lockRegistry A registry for shared locks.
* @param cleanupTasks A list of cleanup tasks.
*/
public AutoCleanupScheduler(
final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry,
final List<CleanupTask> cleanupTasks) {
final List<CleanupTask> cleanupTasks,
final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry) {
this.systemManagement = systemManagement;
this.systemSecurityContext = systemSecurityContext;
this.lockRegistry = lockRegistry;
@@ -57,8 +56,7 @@ public class AutoCleanupScheduler {
@Scheduled(initialDelayString = PROP_AUTO_CLEANUP_INTERVAL, fixedDelayString = PROP_AUTO_CLEANUP_INTERVAL)
public void run() {
log.debug("Auto cleanup scheduler has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities
// run this code in system code privileged to have the necessary permission to query and create entities
if (!cleanupTasks.isEmpty()) {
systemSecurityContext.runAsSystem(this::executeAutoCleanup);
}
@@ -70,7 +68,7 @@ public class AutoCleanupScheduler {
@SuppressWarnings("squid:S3516")
private Void executeAutoCleanup() {
systemManagement.forEachTenant(tenant -> cleanupTasks.forEach(task -> {
final Lock lock = obtainLock(task, tenant);
final Lock lock = lockRegistry.obtain(AUTO_CLEANUP + SEP + task.getId() + SEP + tenant);
if (!lock.tryLock()) {
return;
}
@@ -85,7 +83,20 @@ public class AutoCleanupScheduler {
return null;
}
private Lock obtainLock(final CleanupTask task, final String tenant) {
return lockRegistry.obtain(AUTO_CLEANUP + SEP + task.getId() + SEP + tenant);
/**
* Interface modeling a cleanup task.
*/
public interface CleanupTask extends Runnable {
/**
* Executes the cleanup task.
*/
@Override
void run();
/**
* @return The identifier of this cleanup task. Never null.
*/
String getId();
}
}

View File

@@ -1,27 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.autocleanup;
/**
* Interface modeling a cleanup task.
*/
public interface CleanupTask extends Runnable {
/**
* Executes the cleanup task.
*/
@Override
void run();
/**
* @return The identifier of this cleanup task. Never null.
*/
String getId();
}

View File

@@ -89,6 +89,7 @@ import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
@@ -628,7 +629,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private int getActionsPurgePercentage() {
return getConfigValue(TenantConfigurationProperties.TenantConfigurationKey.ACTIONS_PURGE_PERCENTAGE_ON_QUOTA_HIT, Integer.class);
return getConfigValue(TenantConfigurationKey.ACTION_PURGE_PERCENTAGE_ON_QUOTA_HIT, Integer.class);
}
protected boolean isActionsAutocloseEnabled() {