Refactor caches (#2775) (#2777)

* TenantAwareCacheManager define CacheEvictEvent which could be used to evict entities in general way
* JpaTenantConfigurationManagement start using genera cache approach

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-28 14:13:53 +02:00
committed by GitHub
parent 2d562f64cb
commit d488ad6b5f
16 changed files with 462 additions and 377 deletions

File diff suppressed because one or more lines are too long

View File

@@ -13,18 +13,21 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver; import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache; import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -44,7 +47,6 @@ public class TenantAwareCacheManager implements CacheManager {
private static final String CONFIG_SPEC = "spec"; private static final String CONFIG_SPEC = "spec";
private static final TenantAwareCacheManager SINGLETON = new TenantAwareCacheManager(); private static final TenantAwareCacheManager SINGLETON = new TenantAwareCacheManager();
private CacheManager globalCacheManager; private CacheManager globalCacheManager;
private final Map<String, CacheManager> tenant2CacheManager = new ConcurrentHashMap<>(); private final Map<String, CacheManager> tenant2CacheManager = new ConcurrentHashMap<>();
@@ -66,13 +68,14 @@ public class TenantAwareCacheManager implements CacheManager {
globalCacheManager = new TenantCacheManager(null); globalCacheManager = new TenantCacheManager(null);
} }
@Nullable @NonNull
@Override @Override
public Cache getCache(@NonNull final String name) { public Cache getCache(@NonNull final String name) {
return Optional.ofNullable(resolver.resolveTenant()) final Cache cache = Optional.ofNullable(resolver.resolveTenant())
.map(currentTenant -> tenant2CacheManager.computeIfAbsent(currentTenant, TenantCacheManager::new)) .map(currentTenant -> tenant2CacheManager.computeIfAbsent(currentTenant, TenantCacheManager::new))
.orElse(globalCacheManager) .orElse(globalCacheManager)
.getCache(name); .getCache(name);
return cache == null ? new Nop(name) : cache;
} }
@NonNull @NonNull
@@ -87,11 +90,54 @@ public class TenantAwareCacheManager implements CacheManager {
} }
} }
public void evictTenant(final String tenant) { /**
if (tenant == null) { * Ensures that cache eviction takes place in microservice mode in case of deletions.
globalCacheManager.getCacheNames().forEach(name -> Optional.ofNullable(globalCacheManager.getCache(name)).ifPresent(Cache::clear)); *
* @param event The event indicating that a configuration value has been deleted.
*/
@EventListener
@SuppressWarnings("java:S3776") // not too complex and this way is more readable
public void onCacheEvictEvent(final CacheEvictEvent event) {
final CacheManager cacheManager = event.getTenant() == null ? globalCacheManager : tenant2CacheManager.get(event.getTenant());
if (cacheManager != null) {
if (event.getCacheName() == null) { // evict all caches
if (event.getTenant() == null) { // global cache
cacheManager.getCacheNames().forEach(name -> {
final Cache cache = cacheManager.getCache(name);
if (cache != null) {
cache.clear();
}
});
} else { // tenant specific cache
tenant2CacheManager.remove(event.getTenant());
}
} else { } else {
tenant2CacheManager.remove(tenant); final Cache cache = cacheManager.getCache(event.getCacheName());
if (cache != null) {
if (event.getCacheKey() == null) { // evict all keys
cache.clear();
} else { // evict specific key
cache.evict(event.getCacheKey());
}
}
}
}
}
public interface CacheEvictEvent {
String getTenant();
String getCacheName(); // null means - all caches shall be evicted
Object getCacheKey(); // null means - all keys shall be evicted
@Value
class Default implements CacheEvictEvent {
String tenant;
String cacheName;
Object cacheKey;
} }
} }
@@ -118,7 +164,17 @@ public class TenantAwareCacheManager implements CacheManager {
} }
} }
try { try {
return new CaffeineCache(n, Caffeine.from(spec).build(), false); return new CaffeineCache(n, Caffeine.from(spec).build(), false) {
@Nullable
@Override
@SuppressWarnings("java:S2638") // used internally in hawkbit and want to return null instead of error
protected Object toStoreValue(@Nullable Object userValue) {
// we want to return pure null to caffeine when null, in order to do not cache
// we want to allow null results but not to be cached!
return userValue;
}
};
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
log.error("Invalid cache spec: {}", spec, e); log.error("Invalid cache spec: {}", spec, e);
throw new IllegalStateException("Invalid cache spec: " + spec, e); throw new IllegalStateException("Invalid cache spec: " + spec, e);
@@ -132,4 +188,52 @@ public class TenantAwareCacheManager implements CacheManager {
return caches.keySet(); return caches.keySet();
} }
} }
@Value
private static class Nop implements Cache {
String name;
@NonNull
@Override
public String getName() {
return name;
}
@NonNull
@Override
public Object getNativeCache() {
return this;
}
@Override
public ValueWrapper get(@NonNull final Object key) {
return null;
}
@Override
public <T> T get(@NonNull final Object key, final Class<T> type) {
return null;
}
@Override
public <T> T get(@NonNull final Object key, @NonNull final Callable<T> valueLoader) {
return null;
}
@Override
public void put(@NonNull final Object key, final Object value) {
// nop
}
@Override
public void evict(@NonNull final Object key) {
// nop
}
@Override
public void clear() {
// nop
}
}
} }

View File

@@ -80,9 +80,9 @@ import org.springframework.beans.factory.annotation.Autowired;
*/ */
class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest { class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
static final String DMF_REGISTER_TEST_CONTROLLER_ID = "Dmf_hand_registerTargets_1"; private static final String DMF_REGISTER_TEST_CONTROLLER_ID = "Dmf_hand_registerTargets_1";
static final String DMF_ATTR_TEST_CONTROLLER_ID = "Dmf_hand_updateAttributes"; private static final String DMF_ATTR_TEST_CONTROLLER_ID = "Dmf_hand_updateAttributes";
static final String UPDATE_ATTR_TEST_CONTROLLER_ID = "ControllerAttributeTestTarget"; private static final String UPDATE_ATTR_TEST_CONTROLLER_ID = "ControllerAttributeTestTarget";
private static final String TARGET_PREFIX = "Dmf_hand_"; private static final String TARGET_PREFIX = "Dmf_hand_";
@Autowired @Autowired

View File

@@ -36,7 +36,7 @@ public interface TenantConfigurationManagement extends PermissionSupport {
/** /**
* Adds or updates a specific configuration for a specific tenant. * Adds or updates a specific configuration for a specific tenant.
* *
* @param configurationKeyName the key of the configuration * @param keyName the key of the configuration
* @param value the configuration value which will be written into the database. * @param value the configuration value which will be written into the database.
* @return the configuration value which was just written into the database. * @return the configuration value which was just written into the database.
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not match the expected type and * @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not match the expected type and
@@ -44,7 +44,7 @@ public interface TenantConfigurationManagement extends PermissionSupport {
* @throws ConversionFailedException if the property cannot be converted to the given * @throws ConversionFailedException if the property cannot be converted to the given
*/ */
@PreAuthorize(value = SpringEvalExpressions.HAS_UPDATE_REPOSITORY) @PreAuthorize(value = SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
<T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(String configurationKeyName, T value); <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(String keyName, T value);
/** /**
* Adds or updates a specific configuration for a specific tenant. * Adds or updates a specific configuration for a specific tenant.
@@ -58,6 +58,36 @@ public interface TenantConfigurationManagement extends PermissionSupport {
@PreAuthorize(value = SpringEvalExpressions.HAS_UPDATE_REPOSITORY) @PreAuthorize(value = SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
<T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(Map<String, T> configurations); <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(Map<String, T> configurations);
/**
* Retrieves a configuration value from the e.g. tenant overwritten configuration values or in case the tenant does not a have a specific
* configuration the global default value hold in the {@link Environment}.
*
* @param keyName the key of the configuration
* @return the converted configuration value either from the tenant specific configuration stored or from the fallback default values or
* {@code null} in case key has not been configured and not default value exists
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not
* match the expected type and format defined by the Key
* @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String keyName);
/**
* Retrieves a configuration value from the e.g. tenant overwritten configuration values or in case the tenant does not a have a specific
* configuration the global default value hold in the {@link Environment}.
*
* @param <T> the type of the configuration value
* @param keyName the key of the configuration
* @param propertyType the type of the configuration value, e.g. {@code String.class}, {@code Integer.class}, etc
* @return the converted configuration value either from the tenant specific configuration stored or from the fallback default values or
* {@code null} in case key has not been configured and not default value exists
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not
* match the expected type and format defined by the Key
* @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String keyName, Class<T> propertyType);
/** /**
* Deletes a specific configuration for the current tenant. Does nothing in case there is no tenant specific configuration value. * Deletes a specific configuration for the current tenant. Does nothing in case there is no tenant specific configuration value.
* *
@@ -66,51 +96,6 @@ public interface TenantConfigurationManagement extends PermissionSupport {
@PreAuthorize(value = SpringEvalExpressions.HAS_DELETE_REPOSITORY) @PreAuthorize(value = SpringEvalExpressions.HAS_DELETE_REPOSITORY)
void deleteConfiguration(String configurationKey); void deleteConfiguration(String configurationKey);
/**
* Retrieves a configuration value from the e.g. tenant overwritten configuration values or in case the tenant does not a have a specific
* configuration the global default value hold in the {@link Environment}.
*
* @param configurationKeyName the key of the configuration
* @return the converted configuration value either from the tenant specific configuration stored or from the fallback default values or
* {@code null} in case key has not been configured and not default value exists
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not
* match the expected type and format defined by the Key
* @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName);
/**
* Retrieves a configuration value from the e.g. tenant overwritten configuration values or in case the tenant does not a have a specific
* configuration the global default value hold in the {@link Environment}.
*
* @param <T> the type of the configuration value
* @param configurationKeyName the key of the configuration
* @param propertyType the type of the configuration value, e.g. {@code String.class}, {@code Integer.class}, etc
* @return the converted configuration value either from the tenant specific configuration stored or from the fallback default values or
* {@code null} in case key has not been configured and not default value exists
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not
* match the expected type and format defined by the Key
* @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName,
Class<T> propertyType);
/**
* returns the global configuration property either defined in the property file or a default value otherwise.
*
* @param <T> the type of the configuration value
* @param configurationKeyName the key of the configuration
* @param propertyType the type of the configuration value, e.g. {@code String.class}, {@code Integer.class}, etc
* @return the global configured value
* @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in the property file or the default value
* does not match the expected type and format defined by the Key
* @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY)
<T> T getGlobalConfigurationValue(String configurationKeyName, Class<T> propertyType);
@PreAuthorize(value = "hasAuthority('READ_" + SpPermission.TARGET + "')") @PreAuthorize(value = "hasAuthority('READ_" + SpPermission.TARGET + "')")
Function<Target, PollStatus> pollStatusResolver(); Function<Target, PollStatus> pollStatusResolver();
} }

View File

@@ -20,6 +20,10 @@ import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
/** /**
* A base definition class for an event which contains an id. * A base definition class for an event which contains an id.
* <p/>
*
* Note: it implements {@link org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent} methods but in order
* to be really include in the cache eviction process the subclasses must declare that it implements that interface.
*/ */
@NoArgsConstructor(access = AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter @Getter
@@ -38,4 +42,13 @@ public abstract class RemoteIdEvent extends RemoteTenantAwareEvent {
this.entityClass = entityClass.getName(); this.entityClass = entityClass.getName();
this.entityId = entityId; this.entityId = entityId;
} }
public String getCacheName() {
final int index = entityClass.lastIndexOf('.');
return index < 0 ? entityClass : entityClass.substring(index + 1);
}
public Object getCacheKey() {
return entityId;
}
} }

View File

@@ -17,6 +17,8 @@ import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent; import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/** /**
* Defines the remote event of deleting a {@link org.eclipse.hawkbit.repository.model.TenantConfiguration}. * Defines the remote event of deleting a {@link org.eclipse.hawkbit.repository.model.TenantConfiguration}.
@@ -25,7 +27,7 @@ import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
@Getter @Getter
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
public class TenantConfigurationDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent { public class TenantConfigurationDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent, CacheEvictEvent {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -41,4 +43,10 @@ public class TenantConfigurationDeletedEvent extends RemoteIdEvent implements En
this.configKey = configKey; this.configKey = configKey;
this.configValue = configValue; this.configValue = configValue;
} }
// overrides since the default impl is based on entity id while the tenant cache is key (string) based
@Override
public String getCacheKey() {
return configKey;
}
} }

View File

@@ -27,4 +27,10 @@ public class TenantConfigurationCreatedEvent extends RemoteEntityEvent<TenantCon
public TenantConfigurationCreatedEvent(final TenantConfiguration tenantConfiguration) { public TenantConfigurationCreatedEvent(final TenantConfiguration tenantConfiguration) {
super(tenantConfiguration); super(tenantConfiguration);
} }
// overrides since the default impl is based on entity id while the tenant cache is key (string) based
@Override
public String getCacheKey() {
return getEntity().map(TenantConfiguration::getKey).orElse(null); // null will clean all tenant cache
}
} }

View File

@@ -14,12 +14,13 @@ import java.io.Serial;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent;
import org.eclipse.hawkbit.repository.model.TenantConfiguration; import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/** /**
* Defines the remote event of updating a {@link TenantConfiguration}. * Defines the remote event of updating a {@link TenantConfiguration}.
*/ */
@NoArgsConstructor // for serialization libs like jackson @NoArgsConstructor // for serialization libs like jackson
public class TenantConfigurationUpdatedEvent extends RemoteEntityEvent<TenantConfiguration> implements EntityUpdatedEvent { public class TenantConfigurationUpdatedEvent extends RemoteEntityEvent<TenantConfiguration> implements EntityUpdatedEvent, CacheEvictEvent {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -27,4 +28,10 @@ public class TenantConfigurationUpdatedEvent extends RemoteEntityEvent<TenantCon
public TenantConfigurationUpdatedEvent(final TenantConfiguration tenantConfiguration) { public TenantConfigurationUpdatedEvent(final TenantConfiguration tenantConfiguration) {
super(tenantConfiguration); super(tenantConfiguration);
} }
// overrides since the default impl is based on entity id while the tenant cache is key (string) based
@Override
public String getCacheKey() {
return getEntity().map(TenantConfiguration::getKey).orElse(null); // null will clean all tenant cache
}
} }

View File

@@ -13,6 +13,7 @@ import java.util.Set;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent; import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
@@ -107,12 +108,12 @@ public final class EventPublisherHolder {
} }
@Override @Override
public void publishEvent(final Object event) { public void publishEvent(@NonNull final Object event) {
routeEvent(event); routeEvent(event);
} }
@Override @Override
public void publishEvent(final ApplicationEvent event) { public void publishEvent(@NonNull final ApplicationEvent event) {
routeEvent(event); routeEvent(event);
} }
@@ -137,8 +138,8 @@ public final class EventPublisherHolder {
log.debug("Publishing Service event: {} to remote channel: {}", serviceEvent, serviceEventChannel); log.debug("Publishing Service event: {} to remote channel: {}", serviceEvent, serviceEventChannel);
streamBridge.send(serviceEventChannel, serviceEvent); streamBridge.send(serviceEventChannel, serviceEvent);
} else { } else {
log.error("No Service event created for: {}. Skipping send Service event to Service channel. {}", remoteEvent.getClass(), log.error("No Service event created for: {}. Skipping send Service event to Service channel. {}",
serviceEventChannel); remoteEvent.getClass(), serviceEventChannel);
} }
} }
} }

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY; import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY;
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@@ -38,8 +39,8 @@ import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.Identifiable; import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.RepositoryManagement;
import org.eclipse.hawkbit.repository.QueryField; import org.eclipse.hawkbit.repository.QueryField;
import org.eclipse.hawkbit.repository.RepositoryManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.Jpa; import org.eclipse.hawkbit.repository.jpa.Jpa;
@@ -48,9 +49,10 @@ import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport; import org.eclipse.hawkbit.repository.jpa.ql.QLSupport;
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
import org.eclipse.hawkbit.utils.ObjectCopyUtil; import org.eclipse.hawkbit.utils.ObjectCopyUtil;
import org.springframework.cache.Cache;
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;
@@ -132,6 +134,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public T create(final C create) { public T create(final C create) {
getCache().ifPresent(cache -> afterCommit(cache::clear));
return jpaRepository.save(AccessController.Operation.CREATE, jpaEntity(create)); return jpaRepository.save(AccessController.Operation.CREATE, jpaEntity(create));
} }
@@ -139,17 +142,47 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public List<T> create(final Collection<C> create) { public List<T> create(final Collection<C> create) {
getCache().ifPresent(cache -> afterCommit(cache::clear));
return jpaRepository.saveAll(AccessController.Operation.CREATE, create.stream().map(this::jpaEntity).toList()); return jpaRepository.saveAll(AccessController.Operation.CREATE, create.stream().map(this::jpaEntity).toList());
} }
@Override @Override
@SuppressWarnings("unchecked")
public T get(final long id) { public T get(final long id) {
final Cache cache = getCache().orElse(null);
if (cache == null) {
return jpaRepository.getById(id); return jpaRepository.getById(id);
} else {
final T cached = (T) cache.get(id);
if (cached == null) {
final T entity = jpaRepository.getById(id);
if (entity != null) { // should not be null - but throw exception - but for sure check
cache.put(id, entity);
}
return entity;
} else {
return cached;
}
}
} }
@Override @Override
@SuppressWarnings("unchecked")
public Optional<T> find(final long id) { public Optional<T> find(final long id) {
final Cache cache = getCache().orElse(null);
if (cache == null) {
return jpaRepository.findById(id); return jpaRepository.findById(id);
} else {
final T cached = (T) cache.get(id);
if (cached == null) {
final Optional<T> entity = jpaRepository.findById(id);
// should not be null - but throw exception - but for sure check
entity.ifPresent(t -> cache.put(id, t));
return entity;
} else {
return Optional.of(cached);
}
}
} }
@Override @Override
@@ -193,6 +226,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@SuppressWarnings("java:S1066") // javaS1066 - better readable that way @SuppressWarnings("java:S1066") // javaS1066 - better readable that way
public T update(final U update) { public T update(final U update) {
getCache().ifPresent(cache -> afterCommit(() -> cache.evict(update.getId())));
final T entity = getValid(update.getId()); final T entity = getValid(update.getId());
// update getId has no setter in target JPA entity but shall have getter and the value shall be the same // update getId has no setter in target JPA entity but shall have getter and the value shall be the same
// otherwise the Utils will throw an exception that there is no counterpart setter for getId // otherwise the Utils will throw an exception that there is no counterpart setter for getId
@@ -230,6 +264,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
if (toSave.isEmpty()) { if (toSave.isEmpty()) {
return toUpdate; return toUpdate;
} else { } else {
getCache().ifPresent(cache -> afterCommit(() -> toSave.forEach(updated -> cache.evict(updated.getId()))));
final List<T> savedEntities = jpaRepository.saveAll(toSave); final List<T> savedEntities = jpaRepository.saveAll(toSave);
final Map<Long, T> result = new HashMap<>(toUpdate); final Map<Long, T> result = new HashMap<>(toUpdate);
for (final T savedEntity : savedEntities) { for (final T savedEntity : savedEntities) {
@@ -243,6 +278,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void delete(final long id) { public void delete(final long id) {
getCache().ifPresent(cache -> afterCommit(() -> cache.evict(id)));
delete0(List.of(id)); delete0(List.of(id));
} }
@@ -250,6 +286,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void delete(final Collection<Long> ids) { public void delete(final Collection<Long> ids) {
getCache().ifPresent(cache -> afterCommit(() -> ids.forEach(cache::evict)));
delete0(ids); delete0(ids);
} }
@@ -314,6 +351,11 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
} }
} }
// if cache is supported for the entities - override this method
protected Optional<Cache> getCache() {
return Optional.empty();
}
private List<T> findAllById(final Collection<Long> ids, final boolean throwIfNotFound) { private List<T> findAllById(final Collection<Long> ids, final boolean throwIfNotFound) {
final List<T> foundDs = jpaRepository.findAllById(ids); final List<T> foundDs = jpaRepository.findAllById(ids);
if (throwIfNotFound && foundDs.size() != ids.size()) { if (throwIfNotFound && foundDs.size() != ids.size()) {

View File

@@ -16,10 +16,10 @@ import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.ArtifactStorage; import org.eclipse.hawkbit.artifact.ArtifactStorage;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantStatsManagement; import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.CurrentTenantCacheKeyGenerator; import org.eclipse.hawkbit.repository.jpa.CurrentTenantCacheKeyGenerator;
import org.eclipse.hawkbit.repository.jpa.SystemManagementCacheKeyGenerator; import org.eclipse.hawkbit.repository.jpa.SystemManagementCacheKeyGenerator;
@@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.model.report.SystemUsageReport;
import org.eclipse.hawkbit.repository.model.report.SystemUsageReportWithTenants; import org.eclipse.hawkbit.repository.model.report.SystemUsageReportWithTenants;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
@@ -160,7 +161,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
} }
final String tenant = t.toUpperCase(); final String tenant = t.toUpperCase();
TenantAwareCacheManager.getInstance().evictTenant(tenant);
tenantAware.runAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> { tenantAware.runAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> {
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
tenantConfigurationRepository.deleteByTenant(tenant); tenantConfigurationRepository.deleteByTenant(tenant);
@@ -177,6 +177,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
softwareModuleTypeRepository.deleteByTenant(tenant); softwareModuleTypeRepository.deleteByTenant(tenant);
return null; return null;
})); }));
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CacheEvictEvent.Default(tenant, null, null));
} }
@Override @Override

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository.jpa.management; package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_GATEWAY_SECURITY_TOKEN; import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
@@ -26,14 +25,13 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RemoteEntityEvent;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException; import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException; import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
@@ -51,14 +49,11 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager; import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.PollingTime; import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
import org.eclipse.hawkbit.tenancy.configuration.PollingTime.PollingInterval;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
@@ -79,7 +74,7 @@ import org.springframework.validation.annotation.Validated;
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "tenant-configuration-management" }, matchIfMissing = true) @ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "tenant-configuration-management" }, matchIfMissing = true)
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement { public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
private static final String CACHE_TENANT_CONFIGURATION_NAME = "tenantConfiguration"; private static final String CACHE_TENANT_CONFIGURATION_NAME = JpaTenantConfiguration.class.getSimpleName();
private static final ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService(); private static final ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService();
private final TenantConfigurationRepository tenantConfigurationRepository; private final TenantConfigurationRepository tenantConfigurationRepository;
@@ -96,12 +91,11 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
} }
@Override @Override
@CacheEvict(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(final String configurationKeyName, final T value) { public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(final String keyName, final T value) {
return addOrUpdateConfiguration0(Map.of(configurationKeyName, value)).values().iterator().next(); return addOrUpdateConfiguration0(Map.of(keyName, value)).values().iterator().next();
} }
@Override @Override
@@ -109,89 +103,38 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(final Map<String, T> configurations) { public <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(final Map<String, T> configurations) {
// Register a callback to be invoked after the transaction is committed - for cache eviction
afterCommit(() -> {
final Cache cache = TenantAwareCacheManager.getInstance().getCache(CACHE_TENANT_CONFIGURATION_NAME);
if (cache != null) {
configurations.keySet().forEach(cache::evict);
}
});
return addOrUpdateConfiguration0(configurations); return addOrUpdateConfiguration0(configurations);
} }
@Override @Override
@CacheEvict(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName") public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String keyName) {
return getConfigurationValue0(keyName, null);
}
@Override
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String keyName, final Class<T> propertyType) {
return getConfigurationValue0(keyName, propertyType);
}
@Override
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteConfiguration(final String configurationKeyName) { public void deleteConfiguration(final String keyName) {
tenantConfigurationRepository.deleteByKey(configurationKeyName); tenantConfigurationRepository.deleteByKey(keyName);
}
@Override
// TODO - check if cache works
// @Cacheable(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String configurationKeyName) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
return getConfigurationValue(configurationKeyName, (Class<T>) configurationKey.getDataType());
}
@Override
@Cacheable(value = CACHE_TENANT_CONFIGURATION_NAME, key = "#configurationKeyName")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
final String configurationKeyName, final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository.findByKey(configurationKey.getKeyName());
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
}
@Override
public <T> T getGlobalConfigurationValue(final String configurationKeyName, final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
if (!key.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
}
return CONVERSION_SERVICE.convert(key.getDefaultValue(), propertyType);
}
/**
* Ensures that cache eviction takes place in microservice mode in case of deletions.
*
* @param event The event indicating that a configuration value has been deleted.
*/
@EventListener
public void onTenantConfigurationDeletedEvent(final TenantConfigurationDeletedEvent event) {
evictCacheEntryByKeyIfPresent(event.getConfigKey());
}
/**
* Ensures that cache eviction takes place in microservice mode in case of creation or update events.
*
* @param event The event indicating that a configuration value has been created or updated.
*/
@EventListener
public void onTenantConfigurationRemoteEntityEvent(final RemoteEntityEvent<TenantConfiguration> event) {
event.getEntity().ifPresent(tenantConfiguration -> evictCacheEntryByKeyIfPresent(tenantConfiguration.getKey()));
} }
@Override @Override
@SuppressWarnings("java:S3776") // java:S3776 - not really too complex
public Function<Target, PollStatus> pollStatusResolver() { public Function<Target, PollStatus> pollStatusResolver() {
final PollingTime pollingTime = new PollingTime( final PollingTime pollingTime = new PollingTime(
getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class).getValue()); Objects.requireNonNull(getConfigurationValue0(TenantConfigurationKey.POLLING_TIME, String.class),
"Polling time shall always be non-null")
.getValue());
final Duration pollingOverdueTime = DurationHelper.fromString( final Duration pollingOverdueTime = DurationHelper.fromString(
getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class).getValue()); Objects.requireNonNull(getConfigurationValue0(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class),
"Polling overdue time shall always be non-null")
.getValue());
return target -> { return target -> {
final Long lastTargetQuery = target.getLastTargetQuery(); final Long lastTargetQuery = target.getLastTargetQuery();
if (lastTargetQuery == null) { if (lastTargetQuery == null) {
@@ -214,35 +157,18 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
}; };
} }
private static PollStatus pollStatus(
final long lastTargetQuery,
final PollingTime.PollingInterval pollingInterval, final Duration pollingOverdueTime) {
final LocalDateTime currentDate = LocalDateTime.now();
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault());
LocalDateTime nextPollDate = lastPollDate.plus(pollingInterval.getInterval());
if (pollingInterval.getDeviationPercent() > 0) {
nextPollDate = nextPollDate.plus(
pollingInterval.getInterval().toMillis() * pollingInterval.getDeviationPercent() / 100,
ChronoUnit.MILLIS);
}
final LocalDateTime overdueDate = nextPollDate.plus(pollingOverdueTime);
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
}
/** /**
* Validates the data type of the tenant configuration. If it is possible to cast to the given data type. * Validates the data type of the tenant configuration. If it is possible to cast to the given data type.
*/ */
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey, final Class<T> propertyType) { private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey, final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) { if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException( throw new TenantConfigurationValidatorException(String.format(
String.format( "Cannot parse the database value of type %s into the type %s.", configurationKey.getDataType(), propertyType));
"Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
} }
} }
private void checkAccess(final String configurationKeyName) { private void checkAccess(final String keyName) {
if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(configurationKeyName)) { if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(keyName)) {
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext(); final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (!SystemSecurityContext.isCurrentThreadSystemCode() && !systemSecurityContext.hasPermission(READ_GATEWAY_SECURITY_TOKEN)) { if (!SystemSecurityContext.isCurrentThreadSystemCode() && !systemSecurityContext.hasPermission(READ_GATEWAY_SECURITY_TOKEN)) {
throw new InsufficientPermissionException( throw new InsufficientPermissionException(
@@ -251,14 +177,15 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
} }
} }
@SuppressWarnings("unchecked")
private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(final Map<String, T> configurations) { private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(final Map<String, T> configurations) {
final List<JpaTenantConfiguration> configurationList = new ArrayList<>(); final List<JpaTenantConfiguration> configurationList = new ArrayList<>();
configurations.forEach((configurationKeyName, value) -> { configurations.forEach((keyName, value) -> {
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName); final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(keyName);
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) { if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
throw new TenantConfigurationValidatorException(String.format( throw new TenantConfigurationValidatorException(String.format(
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value, "Cannot parse the value %s of type %s into the type %s defined by the configuration key.",
value.getClass(), configurationKey.getDataType())); value, value.getClass(), configurationKey.getDataType()));
} }
configurationKey.validate(value, applicationContext); configurationKey.validate(value, applicationContext);
// additional validation for specific configuration keys // additional validation for specific configuration keys
@@ -279,43 +206,72 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
tenantConfiguration.setValue(value.toString()); tenantConfiguration.setValue(value.toString());
} }
assertValueChangeIsAllowed(configurationKeyName, tenantConfiguration); assertValueChangeIsAllowed(keyName, tenantConfiguration);
configurationList.add(tenantConfiguration); configurationList.add(tenantConfiguration);
}); });
return tenantConfigurationRepository.saveAll(configurationList).
final List<JpaTenantConfiguration> jpaTenantConfigurations = tenantConfigurationRepository.saveAll(configurationList); stream().
return jpaTenantConfigurations.stream().collect(Collectors.toMap( collect(Collectors.toMap(
JpaTenantConfiguration::getKey, JpaTenantConfiguration::getKey,
updatedTenantConfiguration -> { updatedTenantConfiguration -> TenantConfigurationValue.<T> builder()
@SuppressWarnings("unchecked") final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey()) .global(false)
.getClass();
return TenantConfigurationValue.<T> builder().global(false)
.createdBy(updatedTenantConfiguration.getCreatedBy()) .createdBy(updatedTenantConfiguration.getCreatedBy())
.createdAt(updatedTenantConfiguration.getCreatedAt()) .createdAt(updatedTenantConfiguration.getCreatedAt())
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt()) .lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy()) .lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
.value(CONVERSION_SERVICE.convert(updatedTenantConfiguration.getValue(), clazzT)) .value(CONVERSION_SERVICE.convert(
.build(); updatedTenantConfiguration.getValue(),
})); (Class<T>) configurations.get(updatedTenantConfiguration.getKey()).getClass()))
.build()));
}
@SuppressWarnings("unchecked")
private <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue0(final String keyName, final Class<T> propertyType) {
checkAccess(keyName);
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(keyName);
if (propertyType != null) {
validateTenantConfigurationDataType(key, propertyType);
}
final TenantConfiguration tenantConfiguration = TenantAwareCacheManager.getInstance().getCache(CACHE_TENANT_CONFIGURATION_NAME)
.get(key.getKeyName(), () -> tenantConfigurationRepository.findByKey(key.getKeyName()));
return buildTenantConfigurationValueByKey(key, propertyType == null ? (Class<T>) key.getDataType() : propertyType, tenantConfiguration);
} }
private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey( private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
final TenantConfigurationKey configurationKey, final Class<T> propertyType, final TenantConfiguration tenantConfiguration) { final TenantConfigurationKey configurationKey, final Class<T> propertyType, final TenantConfiguration tenantConfiguration) {
if (tenantConfiguration != null) { if (tenantConfiguration != null) {
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy()) return TenantConfigurationValue.<T> builder().global(false)
.createdBy(tenantConfiguration.getCreatedBy())
.createdAt(tenantConfiguration.getCreatedAt()) .createdAt(tenantConfiguration.getCreatedAt())
.lastModifiedAt(tenantConfiguration.getLastModifiedAt()) .lastModifiedAt(tenantConfiguration.getLastModifiedAt())
.lastModifiedBy(tenantConfiguration.getLastModifiedBy()) .lastModifiedBy(tenantConfiguration.getLastModifiedBy())
.value(CONVERSION_SERVICE.convert(tenantConfiguration.getValue(), propertyType)).build(); .value(CONVERSION_SERVICE.convert(tenantConfiguration.getValue(), propertyType)).build();
} else if (configurationKey.getDefaultValue() != null) { } else if (configurationKey.getDefaultValue() != null) {
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null) return TenantConfigurationValue.<T> builder().global(true)
.lastModifiedAt(null).lastModifiedBy(null) .createdBy(null)
.value(getGlobalConfigurationValue(configurationKey.getKeyName(), propertyType)).build(); .createdAt(null)
.lastModifiedAt(null)
.lastModifiedBy(null)
.value(getGlobalConfigurationValue0(configurationKey.getKeyName(), propertyType)).build();
} else { } else {
return null; return null;
} }
} }
private <T> T getGlobalConfigurationValue0(final String keyName, final Class<T> propertyType) {
checkAccess(keyName);
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(keyName);
if (!key.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
}
return CONVERSION_SERVICE.convert(key.getDefaultValue(), propertyType);
}
/** /**
* Asserts that the requested configuration value change is allowed. Throws a {@link TenantConfigurationValueChangeNotAllowedException} * Asserts that the requested configuration value change is allowed. Throws a {@link TenantConfigurationValueChangeNotAllowedException}
* otherwise. * otherwise.
@@ -326,19 +282,10 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
*/ */
private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) { private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) {
assertMultiAssignmentsValueChange(key, valueChange); assertMultiAssignmentsValueChange(key, valueChange);
assertAutoCloseValueChange(key, valueChange); assertAutoCloseValueChange(key);
assertBatchAssignmentValueChange(key, valueChange); assertBatchAssignmentValueChange(key, valueChange);
} }
@SuppressWarnings("squid:S1172")
private void assertAutoCloseValueChange(final String key, final JpaTenantConfiguration valueChange) {
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
&& Boolean.TRUE.equals(getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue())) {
log.debug("The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.", key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
}
private void assertMultiAssignmentsValueChange(final String key, final JpaTenantConfiguration valueChange) { private void assertMultiAssignmentsValueChange(final String key, final JpaTenantConfiguration valueChange) {
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && !Boolean.parseBoolean(valueChange.getValue())) { if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && !Boolean.parseBoolean(valueChange.getValue())) {
log.debug("The Multi-Assignments '{}' feature cannot be disabled.", key); log.debug("The Multi-Assignments '{}' feature cannot be disabled.", key);
@@ -355,6 +302,14 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
} }
} }
private void assertAutoCloseValueChange(final String key) {
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
&& Boolean.TRUE.equals(getConfigurationValue0(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue())) {
log.debug("The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.", key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
}
private void assertBatchAssignmentValueChange(final String key, final JpaTenantConfiguration valueChange) { private void assertBatchAssignmentValueChange(final String key, final JpaTenantConfiguration valueChange) {
if (BATCH_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) { if (BATCH_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
JpaTenantConfiguration multiConfig = tenantConfigurationRepository.findByKey(MULTI_ASSIGNMENTS_ENABLED); JpaTenantConfiguration multiConfig = tenantConfigurationRepository.findByKey(MULTI_ASSIGNMENTS_ENABLED);
@@ -367,10 +322,15 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
} }
} }
private void evictCacheEntryByKeyIfPresent(final String key) { private static PollStatus pollStatus(final long lastTargetQuery, final PollingInterval pollingInterval, final Duration pollingOverdueTime) {
final Cache cache = TenantAwareCacheManager.getInstance().getCache(CACHE_TENANT_CONFIGURATION_NAME); final LocalDateTime currentDate = LocalDateTime.now();
if (cache != null) { final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault());
cache.evictIfPresent(key); LocalDateTime nextPollDate = lastPollDate.plus(pollingInterval.getInterval());
if (pollingInterval.getDeviationPercent() > 0) {
nextPollDate = nextPollDate.plus(
pollingInterval.getInterval().toMillis() * pollingInterval.getDeviationPercent() / 100, ChronoUnit.MILLIS);
} }
final LocalDateTime overdueDate = nextPollDate.plus(pollingOverdueTime);
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
} }
} }

View File

@@ -58,7 +58,6 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti
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;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
@@ -158,8 +157,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget(NOT_EXIST_ID), "Target"); verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget(NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget("xxx", NOT_EXIST_ID), "Target"); verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget("xxx", NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> findActionsByDistributionSet(PAGE, NOT_EXIST_IDL), verifyThrownExceptionBy(() -> findActionsByDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(() -> deploymentManagement.findActionsByTarget(NOT_EXIST_ID, PAGE), "Target"); verifyThrownExceptionBy(() -> deploymentManagement.findActionsByTarget(NOT_EXIST_ID, PAGE), "Target");
verifyThrownExceptionBy(() -> deploymentManagement.findActionsByTarget("id==*", NOT_EXIST_ID, PAGE), "Target"); verifyThrownExceptionBy(() -> deploymentManagement.findActionsByTarget("id==*", NOT_EXIST_ID, PAGE), "Target");
@@ -169,20 +167,19 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
/** /**
* Test verifies that the repistory retrieves the action including all defined (lazy) details. * Test verifies that the repository retrieves the action including all defined (lazy) details.
*/ */
@Test @Test
void findActionWithLazyDetails() { void findActionWithLazyDetails() {
final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", List.of());
new ArrayList<>());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget)); final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget));
final Action action = deploymentManagement.findActionWithDetails(actionId).get(); final Action action = deploymentManagement.findActionWithDetails(actionId).orElseThrow();
assertThat(action.getDistributionSet()).as("DistributionSet in action").isNotNull(); assertThat(action.getDistributionSet()).as("DistributionSet in action").isNotNull();
assertThat(action.getTarget()).as("Target in action").isNotNull(); assertThat(action.getTarget()).as("Target in action").isNotNull();
assertThat(deploymentManagement.findAssignedDistributionSet(action.getTarget().getControllerId()).get()) assertThat(deploymentManagement.findAssignedDistributionSet(action.getTarget().getControllerId()).orElseThrow())
.as("AssignedDistributionSet of target in action") .as("AssignedDistributionSet of target in action")
.isNotNull(); .isNotNull();
} }
@@ -192,7 +189,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/ */
@Test @Test
void findActionByTargetId() { void findActionByTargetId() {
final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", new ArrayList<>()); final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", List.of());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget)); final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget));
@@ -255,7 +252,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// act // act
final Page<ActionStatus> actionStates = deploymentManagement.findActionStatusByAction(actionId, PAGE); final Page<ActionStatus> actionStates = deploymentManagement.findActionStatusByAction(actionId, PAGE);
assertThat(actionStates.getContent()).hasSize(1); assertThat(actionStates.getContent()).hasSize(1);
assertThat(actionStates.getContent().get(0)).as("Action-status of action").isEqualTo(expectedActionStatus); assertThat(actionStates.getContent().get(0)).as("Action-status of action").isEqualTo(expectedActionStatus);
} }
@@ -265,25 +261,24 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/ */
@Test @Test
void findMessagesByActionStatusId() { void findMessagesByActionStatusId() {
final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", new ArrayList<>()); final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", List.of());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget)); final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget));
// create action-status entry with one message // create action-status entry with one message
controllerManagement.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionId) controllerManagement.addUpdateActionStatus(ActionStatusCreate.builder()
.status(Action.Status.FINISHED).messages(List.of("finished message")).build()); .actionId(actionId).status(Action.Status.FINISHED).messages(List.of("finished message")).build());
final Page<ActionStatus> actionStates = deploymentManagement.findActionStatusByAction(actionId, PAGE); final Page<ActionStatus> actionStates = deploymentManagement.findActionStatusByAction(actionId, PAGE);
// find newly created action-status entry with message // find newly created action-status entry with message
final JpaActionStatus actionStatusWithMessage = actionStates.getContent().stream() final JpaActionStatus actionStatusWithMessage = actionStates.getContent().stream()
.map(JpaActionStatus.class::cast) .map(JpaActionStatus.class::cast)
.filter(entry -> entry.getMessages() != null && !entry.getMessages().isEmpty()) .filter(entry -> entry.getMessages() != null && !entry.getMessages().isEmpty())
.findFirst().get(); .findFirst().orElseThrow();
final String expectedMsg = actionStatusWithMessage.getMessages().get(0); final String expectedMsg = actionStatusWithMessage.getMessages().get(0);
// act // act
final Page<String> messages = deploymentManagement.findMessagesByActionStatusId(actionStatusWithMessage.getId(), PAGE); final Page<String> messages = deploymentManagement.findMessagesByActionStatusId(actionStatusWithMessage.getId(), PAGE);
assertThat(actionStates.getTotalElements()).as("Two action-states in total").isEqualTo(2L); assertThat(actionStates.getTotalElements()).as("Two action-states in total").isEqualTo(2L);
assertThat(messages.getContent().get(0)).as("Message of action-status").isEqualTo(expectedMsg); assertThat(messages.getContent().get(0)).as("Message of action-status").isEqualTo(expectedMsg);
} }
@@ -301,7 +296,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assignDS.add(100_000L); assignDS.add(100_000L);
final Long tagId = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("Tag1").build()).getId(); final Long tagId = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("Tag1").build()).getId();
assertThatExceptionOfType(EntityNotFoundException.class) assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> distributionSetManagement.assignTag(assignDS, tagId)) .isThrownBy(() -> distributionSetManagement.assignTag(assignDS, tagId))
.withMessageContaining("DistributionSet") .withMessageContaining("DistributionSet")
@@ -363,7 +357,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// we cancel second -> back to first // we cancel second -> back to first
deploymentManagement.cancelAction(secondAction.getId()); deploymentManagement.cancelAction(secondAction.getId());
secondAction = (JpaAction) deploymentManagement.findActionWithDetails(secondAction.getId()).get(); secondAction = (JpaAction) deploymentManagement.findActionWithDetails(secondAction.getId()).orElseThrow();
// confirm cancellation // confirm cancellation
controllerManagement.addCancelActionStatus(ActionStatusCreate.builder() controllerManagement.addCancelActionStatus(ActionStatusCreate.builder()
.actionId(secondAction.getId()).status(Status.CANCELED).build()); .actionId(secondAction.getId()).status(Status.CANCELED).build());
@@ -375,14 +369,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// we cancel first -> back to installed // we cancel first -> back to installed
deploymentManagement.cancelAction(firstAction.getId()); deploymentManagement.cancelAction(firstAction.getId());
firstAction = (JpaAction) deploymentManagement.findActionWithDetails(firstAction.getId()).get(); firstAction = (JpaAction) deploymentManagement.findActionWithDetails(firstAction.getId()).orElseThrow();
// confirm cancellation // confirm cancellation
controllerManagement.addCancelActionStatus( controllerManagement.addCancelActionStatus(
ActionStatusCreate.builder().actionId(firstAction.getId()).status(Status.CANCELED).build()); ActionStatusCreate.builder().actionId(firstAction.getId()).status(Status.CANCELED).build());
assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(9); assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(9);
assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds") assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds").contains(dsInstalled);
.contains(dsInstalled); assertThat(targetManagement.getByControllerId("4712").getUpdateStatus())
assertThat(targetManagement.getByControllerId("4712").getUpdateStatus()).as("wrong update status") .as("wrong update status")
.isEqualTo(TargetUpdateStatus.IN_SYNC); .isEqualTo(TargetUpdateStatus.IN_SYNC);
} }
@@ -413,12 +407,12 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// we cancel first -> second is left // we cancel first -> second is left
deploymentManagement.cancelAction(firstAction.getId()); deploymentManagement.cancelAction(firstAction.getId());
// confirm cancellation // confirm cancellation
firstAction = (JpaAction) deploymentManagement.findActionWithDetails(firstAction.getId()).get(); firstAction = (JpaAction) deploymentManagement.findActionWithDetails(firstAction.getId()).orElseThrow();
controllerManagement.addCancelActionStatus( controllerManagement.addCancelActionStatus(ActionStatusCreate.builder().actionId(firstAction.getId()).status(Status.CANCELED).build());
ActionStatusCreate.builder().actionId(firstAction.getId()).status(Status.CANCELED).build());
assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(7); assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(7);
assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds").contains(dsSecond); assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds").contains(dsSecond);
assertThat(targetManagement.getByControllerId("4712").getUpdateStatus()).as("wrong target update status") assertThat(targetManagement.getByControllerId("4712").getUpdateStatus())
.as("wrong target update status")
.isEqualTo(TargetUpdateStatus.PENDING); .isEqualTo(TargetUpdateStatus.PENDING);
// we cancel second -> remain assigned until finished cancellation // we cancel second -> remain assigned until finished cancellation
@@ -427,8 +421,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(8); assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(8);
assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds").contains(dsSecond); assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds").contains(dsSecond);
// confirm cancellation // confirm cancellation
controllerManagement.addCancelActionStatus( controllerManagement.addCancelActionStatus(ActionStatusCreate.builder().actionId(secondAction.getId()).status(Status.CANCELED).build());
ActionStatusCreate.builder().actionId(secondAction.getId()).status(Status.CANCELED).build());
// cancelled success -> back to dsInstalled // cancelled success -> back to dsInstalled
assertThat(deploymentManagement.findAssignedDistributionSet("4712")) assertThat(deploymentManagement.findAssignedDistributionSet("4712"))
.as("wrong installed ds") .as("wrong installed ds")
@@ -469,10 +462,10 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// verify // verify
assertThat(assigningAction.getStatus()).as("wrong size of status").isEqualTo(Status.CANCELED); assertThat(assigningAction.getStatus()).as("wrong size of status").isEqualTo(Status.CANCELED);
assertThat(deploymentManagement.findAssignedDistributionSet("4712")).as("wrong assigned ds") assertThat(deploymentManagement.findAssignedDistributionSet("4712"))
.contains(dsInstalled); .as("wrong assigned ds").contains(dsInstalled);
assertThat(targetManagement.getByControllerId("4712").getUpdateStatus()).as("wrong target update status") assertThat(targetManagement.getByControllerId("4712").getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC); .as("wrong target update status").isEqualTo(TargetUpdateStatus.IN_SYNC);
} }
/** /**
@@ -482,8 +475,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
void forceQuitNotAllowedThrowsException() { void forceQuitNotAllowedThrowsException() {
final Action action = prepareFinishedUpdate("4712", "installed", true); final Action action = prepareFinishedUpdate("4712", "installed", true);
// verify initial status // verify initial status
assertThat(targetManagement.getByControllerId("4712").getUpdateStatus()).as("wrong update status") assertThat(targetManagement.getByControllerId("4712").getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC); .as("wrong update status").isEqualTo(TargetUpdateStatus.IN_SYNC);
final Target target = action.getTarget(); final Target target = action.getTarget();
final DistributionSet ds = testdataFactory.createDistributionSet("newDS", true); final DistributionSet ds = testdataFactory.createDistributionSet("newDS", true);
@@ -600,8 +593,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1), @Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
@Expect(type = TenantConfigurationUpdatedEvent.class, count = 1) }) @Expect(type = TenantConfigurationUpdatedEvent.class, count = 1) })
void assignDistributionSetAndAutoCloseActiveActions() { void assignDistributionSetAndAutoCloseActiveActions() {
tenantConfigurationManagement tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, true);
.addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, true);
try { try {
final List<Target> targets = testdataFactory.createTargets(10); final List<Target> targets = testdataFactory.createTargets(10);
@@ -623,8 +615,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.as("InstallationDate not set").allMatch(target -> (target.getInstallationDate() == null)); .as("InstallationDate not set").allMatch(target -> (target.getInstallationDate() == null));
} finally { } finally {
tenantConfigurationManagement tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, false);
.addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, false);
} }
} }
@@ -719,12 +710,10 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
void cancelMultiAssignmentActions() { void cancelMultiAssignmentActions() {
final List<Target> targets = testdataFactory.createTargets(1); final List<Target> targets = testdataFactory.createTargets(1);
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(4); final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(4);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34, final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34, false);
false);
enableMultiAssignments(); enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests);
.assignDistributionSets(deploymentRequests);
assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size()); assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size());
@@ -732,8 +721,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
targets.forEach(target -> targets.forEach(target ->
deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).forEach(action -> { deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).forEach(action -> {
assertThat(action.getDistributionSet().getId()).isIn(dsIds); assertThat(action.getDistributionSet().getId()).isIn(dsIds);
assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") assertThat(action.getInitiatedBy())
.isEqualTo(tenantAware.getCurrentUsername()); .as("Should be Initiated by current user").isEqualTo(tenantAware.getCurrentUsername());
deploymentManagement.cancelAction(action.getId()); deploymentManagement.cancelAction(action.getId());
})); }));
} }
@@ -757,8 +746,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests)); .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests));
enableMultiAssignments(); enableMultiAssignments();
assertThat(getResultingActionCount( assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1)))).isEqualTo(2);
deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1)))).isEqualTo(2);
} }
/** /**
@@ -798,16 +786,15 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet = testdataFactory.createDistributionSet(); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
enableConfirmationFlow(); enableConfirmationFlow();
List<DistributionSetAssignmentResult> results = assignDistributionSetToTargets(distributionSet, controllerIds, List<DistributionSetAssignmentResult> results = assignDistributionSetToTargets(distributionSet, controllerIds, confirmationRequired);
confirmationRequired);
assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size()); assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size());
controllerIds.forEach(controllerId -> controllerIds.forEach(controllerId ->
deploymentManagement.findActionsByTarget(controllerId, PAGE).forEach(action -> { deploymentManagement.findActionsByTarget(controllerId, PAGE).forEach(action -> {
assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId()); assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId());
assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") assertThat(action.getInitiatedBy())
.isEqualTo(tenantAware.getCurrentUsername()); .as("Should be Initiated by current user").isEqualTo(tenantAware.getCurrentUsername());
if (confirmationRequired) { if (confirmationRequired) {
assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION); assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
} else { } else {
@@ -833,8 +820,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet = testdataFactory.createDistributionSet(); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
assignDistributionSets(Collections assignDistributionSets(List.of(
.singletonList(DeploymentRequest.builder(target.getControllerId(), distributionSet.getId()) DeploymentRequest.builder(target.getControllerId(), distributionSet.getId())
.confirmationRequired(confirmationRequired).build())); .confirmationRequired(confirmationRequired).build()));
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent()).hasSize(1) assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent()).hasSize(1)
@@ -877,8 +864,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet secondDs = testdataFactory.createDistributionSet(); final DistributionSet secondDs = testdataFactory.createDistributionSet();
enableConfirmationFlow(); enableConfirmationFlow();
final List<Action> resultActions = assignDistributionSet(firstDs.getId(), target.getControllerId()) final List<Action> resultActions = assignDistributionSet(firstDs.getId(), target.getControllerId()).getAssignedEntity();
.getAssignedEntity();
assertThat(resultActions).hasSize(1); assertThat(resultActions).hasSize(1);
assertThat(resultActions.get(0)).satisfies(action -> { assertThat(resultActions.get(0)).satisfies(action -> {
@@ -886,23 +872,19 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION); assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
}); });
final List<Action> resultActions2 = assignDistributionSet(secondDs.getId(), target.getControllerId()) final List<Action> resultActions2 = assignDistributionSet(secondDs.getId(), target.getControllerId()).getAssignedEntity();
.getAssignedEntity();
assertThat(resultActions2).hasSize(1); assertThat(resultActions2).hasSize(1);
assertThat(resultActions2.get(0)).satisfies(action -> { assertThat(resultActions2.get(0)).satisfies(action -> {
assertThat(action.getDistributionSet().getId()).isEqualTo(secondDs.getId()); assertThat(action.getDistributionSet().getId()).isEqualTo(secondDs.getId());
assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION); assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
}); });
final List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE) final List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
.getContent();
assertThat(actions).hasSize(2) assertThat(actions).hasSize(2)
.anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), firstDs.getId()) .anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), firstDs.getId())
&& action.getStatus() == Status.CANCELING) && action.getStatus() == Status.CANCELING)
.anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), secondDs.getId()) .anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), secondDs.getId())
&& action.getStatus() == Status.WAIT_FOR_CONFIRMATION); && action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
} }
/** /**
@@ -915,25 +897,23 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet = testdataFactory.createDistributionSet(); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<DistributionSetAssignmentResult> results = Stream final List<DistributionSetAssignmentResult> results = Stream
.concat(assignDistributionSetToTargets(distributionSet, targets1, true).stream(), // .concat(assignDistributionSetToTargets(distributionSet, targets1, true).stream(),
assignDistributionSetToTargets(distributionSet, targets2, false).stream()) // assignDistributionSetToTargets(distributionSet, targets2, false).stream())
.toList(); .toList();
final List<String> controllerIds = Stream.concat(targets1.stream(), targets2.stream()).toList(); final List<String> controllerIds = Stream.concat(targets1.stream(), targets2.stream()).toList();
assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size()); assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size());
controllerIds.forEach(controllerId -> controllerIds.forEach(controllerId ->
deploymentManagement.findActionsByTarget(controllerId, PAGE).forEach(action -> { deploymentManagement.findActionsByTarget(controllerId, PAGE).forEach(action -> {
assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId()); assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId());
assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") assertThat(action.getInitiatedBy())
.isEqualTo(tenantAware.getCurrentUsername()); .as("Should be Initiated by current user").isEqualTo(tenantAware.getCurrentUsername());
assertThat(action.getStatus()).isEqualTo(RUNNING); assertThat(action.getStatus()).isEqualTo(RUNNING);
})); }));
} }
/** /**
* Duplicate Assignments are removed from a request when multiassignment is disabled, otherwise not * Duplicate Assignments are removed from a request when multi-assignment is disabled, otherwise not
*/ */
@Test @Test
@ExpectEvents({ @ExpectEvents({
@@ -950,18 +930,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
void duplicateAssignmentsInRequestAreRemovedIfMultiassignmentEnabled() { void duplicateAssignmentsInRequestAreRemovedIfMultiassignmentEnabled() {
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2, final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2, DeploymentRequest.builder(targetId, dsId).build());
DeploymentRequest.builder(targetId, dsId).build()); assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments))).isEqualTo(1);
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments)))
.isEqualTo(1);
enableMultiAssignments(); enableMultiAssignments();
final List<DeploymentRequest> twoEqualAssignmentsWithWeight = Collections.nCopies(2, final List<DeploymentRequest> twoEqualAssignmentsWithWeight = Collections.nCopies(
DeploymentRequest.builder(targetId, dsId).weight(555).build()); 2, DeploymentRequest.builder(targetId, dsId).weight(555).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignmentsWithWeight))) assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignmentsWithWeight))).isEqualTo(1);
.isEqualTo(1);
} }
/** /**
@@ -985,8 +961,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>(); final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
deploymentRequests.add( deploymentRequests.add(DeploymentRequest.builder(controllerId, dsId).weight(24).build());
DeploymentRequest.builder(controllerId, dsId).weight(24).build());
} }
enableMultiAssignments(); enableMultiAssignments();
@@ -1039,8 +1014,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
void weightValidatedAndSaved() { void weightValidatedAndSaved() {
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
final DeploymentRequest valideRequest1 = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MAX).build(); final DeploymentRequest validRequest1 = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MAX).build();
final DeploymentRequest valideRequest2 = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MIN).build(); final DeploymentRequest validRequest2 = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MIN).build();
final DeploymentRequest weightTooLow = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MIN - 1).build(); final DeploymentRequest weightTooLow = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MIN - 1).build();
final DeploymentRequest weightTooHigh = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MAX + 1).build(); final DeploymentRequest weightTooHigh = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MAX + 1).build();
enableMultiAssignments(); enableMultiAssignments();
@@ -1051,21 +1026,17 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
Assertions.assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy( Assertions.assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> deploymentManagement.assignDistributionSets(deploymentRequestsTooHigh)); () -> deploymentManagement.assignDistributionSets(deploymentRequestsTooHigh));
final Long validActionId1 = getFirstAssignedAction( final Long validActionId1 = getFirstAssignedAction(
deploymentManagement.assignDistributionSets(Collections.singletonList(valideRequest1)).get(0)).getId(); deploymentManagement.assignDistributionSets(Collections.singletonList(validRequest1)).get(0)).getId();
final Long validActionId2 = getFirstAssignedAction( final Long validActionId2 = getFirstAssignedAction(
deploymentManagement.assignDistributionSets(Collections.singletonList(valideRequest2)).get(0)).getId(); deploymentManagement.assignDistributionSets(Collections.singletonList(validRequest2)).get(0)).getId();
assertThat(actionRepository.findById(validActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX); assertThat(actionRepository.findById(validActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX);
assertThat(actionRepository.findById(validActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN); assertThat(actionRepository.findById(validActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN);
} }
/** /**
* test a simple deployment by calling the * Test a simple deployment by calling the {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} and
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} and
* checking the active action and the action history of the targets. * checking the active action and the action history of the targets.
*/ */
/**
* Simple deployment or distribution set to target assignment test.
*/
@Test @Test
@ExpectEvents({ @ExpectEvents({
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@@ -1077,7 +1048,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = ActionCreatedEvent.class, count = 20), @Expect(type = ActionCreatedEvent.class, count = 20),
@Expect(type = TargetUpdatedEvent.class, count = 20) }) @Expect(type = TargetUpdatedEvent.class, count = 20) })
void assignDistributionSet2Targets() { void assignDistributionSet2Targets() {
final String myCtrlIDPref = "myCtrlID"; final String myCtrlIDPref = "myCtrlID";
final Iterable<Target> savedNakedTargets = testdataFactory.createTargets(10, myCtrlIDPref, "first description"); final Iterable<Target> savedNakedTargets = testdataFactory.createTargets(10, myCtrlIDPref, "first description");
@@ -1152,16 +1122,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.as("expected IncompleteDistributionSetException") .as("expected IncompleteDistributionSetException")
.isThrownBy(() -> assignDistributionSet(incomplete, targets)); .isThrownBy(() -> assignDistributionSet(incomplete, targets));
final DistributionSet nowComplete = distributionSetManagement.assignSoftwareModules(incomplete.getId(), final DistributionSet nowComplete = distributionSetManagement.assignSoftwareModules(incomplete.getId(), Set.of(os.getId()));
Set.of(os.getId()));
assertThat(assignDistributionSet(nowComplete, targets).getAssigned()).as("assign ds doesn't work") assertThat(assignDistributionSet(nowComplete, targets).getAssigned()).as("assign ds doesn't work").isEqualTo(10);
.isEqualTo(10);
} }
/** /**
* Multiple deployments or distribution set to target assignment test. Expected behaviour is that a new deployment * Multiple deployments or distribution set to target assignment test. Expected behaviour is that a new deployment
* overides unfinished old one which are canceled as part of the operation. * overrides unfinished old one which are canceled as part of the operation.
*/ */
@Test @Test
@ExpectEvents({ @ExpectEvents({
@@ -1287,10 +1255,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.as("no actions should be active").isEmpty(); .as("no actions should be active").isEmpty();
} }
// deploy dsA to the target which already have dsB deployed -> must // deploy dsA to the target which already have dsB deployed -> must remove updActB from
// remove updActB from // activeActions, add a corresponding cancelAction and another UpdateAction for dsA
// activeActions, add a corresponding cancelAction and another
// UpdateAction for dsA
final List<Target> deployed2DS = assignDistributionSet(dsA, deployResWithDsB.getDeployedTargets()) final List<Target> deployed2DS = assignDistributionSet(dsA, deployResWithDsB.getDeployedTargets())
.getAssignedEntity().stream().map(Action::getTarget).toList(); .getAssignedEntity().stream().map(Action::getTarget).toList();
findActionsByDistributionSet(pageRequest, dsA.getId()).getContent().get(1); findActionsByDistributionSet(pageRequest, dsA.getId()).getContent().get(1);
@@ -1316,14 +1282,10 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
/** /**
* test the deletion of {@link DistributionSet}s including exception in case of * Test the deletion of {@link DistributionSet}s including exception in case of
* {@link Target}s are assigned by {@link Target#getAssignedDistributionSet()} * {@link Target}s are assigned by {@link Target#getAssignedDistributionSet()}
* or {@link Target#getInstalledDistributionSet()} * or {@link Target#getInstalledDistributionSet()}
*/ */
/**
* Deletes distribution set. Expected behaviour is that a soft delete is performed
* if the DS is assigned to a target and a hard delete if the DS is not in use at all.
*/
@Test @Test
void deleteDistributionSet() { void deleteDistributionSet() {
final PageRequest pageRequest = PageRequest.of(0, 100, Direction.ASC, "id"); final PageRequest pageRequest = PageRequest.of(0, 100, Direction.ASC, "id");
@@ -1406,7 +1368,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/ */
@Test @Test
void alternatingAssignmentAndAddUpdateActionStatus() { void alternatingAssignmentAndAddUpdateActionStatus() {
final DistributionSet dsA = testdataFactory.createDistributionSet("a"); final DistributionSet dsA = testdataFactory.createDistributionSet("a");
final DistributionSet dsB = testdataFactory.createDistributionSet("b"); final DistributionSet dsB = testdataFactory.createDistributionSet("b");
List<Target> targs = Collections.singletonList(testdataFactory.createTarget("target-id-A")); List<Target> targs = Collections.singletonList(testdataFactory.createTarget("target-id-A"));
@@ -1445,29 +1406,24 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
"active target actions are wrong"); "active target actions are wrong");
assertEquals(TargetUpdateStatus.IN_SYNC, targ.getUpdateStatus(), "tagret update status is not correct"); assertEquals(TargetUpdateStatus.IN_SYNC, targ.getUpdateStatus(), "tagret update status is not correct");
assertEquals(dsA, deploymentManagement.findAssignedDistributionSet(targ.getControllerId()).get(), assertEquals(dsA, deploymentManagement.findAssignedDistributionSet(targ.getControllerId()).get(), "wrong assigned ds");
"wrong assigned ds"); assertEquals(dsA, deploymentManagement.findInstalledDistributionSet(targ.getControllerId()).get(), "wrong installed ds");
assertEquals(dsA, deploymentManagement.findInstalledDistributionSet(targ.getControllerId()).get(),
"wrong installed ds");
targs = assignDistributionSet(dsB.getId(), "target-id-A").getAssignedEntity().stream().map(Action::getTarget) targs = assignDistributionSet(dsB.getId(), "target-id-A").getAssignedEntity().stream().map(Action::getTarget).toList();
.toList();
implicitLock(dsB); implicitLock(dsB);
targ = targs.iterator().next(); targ = targs.iterator().next();
assertEquals(1, deploymentManagement.findActiveActionsByTarget(targ.getControllerId(), PAGE).getTotalElements(), assertEquals(1, deploymentManagement.findActiveActionsByTarget(targ.getControllerId(), PAGE).getTotalElements(),
"active actions are wrong"); "active actions are wrong");
assertEquals(TargetUpdateStatus.PENDING, assertEquals(TargetUpdateStatus.PENDING, targetManagement.getByControllerId(targ.getControllerId()).getUpdateStatus(),
targetManagement.getByControllerId(targ.getControllerId()).getUpdateStatus(),
"target status is wrong"); "target status is wrong");
assertEquals(dsB, deploymentManagement.findAssignedDistributionSet(targ.getControllerId()).get(), assertEquals(dsB, deploymentManagement.findAssignedDistributionSet(targ.getControllerId()).get(),
"wrong assigned ds"); "wrong assigned ds");
assertEquals(dsA.getId(), assertEquals(dsA.getId(), deploymentManagement.findInstalledDistributionSet(targ.getControllerId()).get().getId(),
deploymentManagement.findInstalledDistributionSet(targ.getControllerId()).get().getId(),
"Installed ds is wrong"); "Installed ds is wrong");
assertEquals(dsB, deploymentManagement.findActiveActionsByTarget(targ.getControllerId(), PAGE).getContent() assertEquals(dsB, deploymentManagement.findActiveActionsByTarget(targ.getControllerId(), PAGE).getContent().get(0).getDistributionSet(),
.get(0).getDistributionSet(), "Active ds is wrong"); "Active ds is wrong");
} }
/** /**
@@ -1545,19 +1501,17 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/ */
@Test @Test
void testAlreadyAssignedAndAssignedActionsInAssignmentResult() { void testAlreadyAssignedAndAssignedActionsInAssignmentResult() {
// create target1, distributionSet, assign ds to target1 and finish // create target1, distributionSet, assign ds to target1 and finish update (close all actions)
// update (close all actions)
final Action action = prepareFinishedUpdate("target1", "ds", false); final Action action = prepareFinishedUpdate("target1", "ds", false);
final Target target2 = testdataFactory.createTarget("target2"); final Target target2 = testdataFactory.createTarget("target2");
final Target target3 = testdataFactory.createTarget("target3"); final Target target3 = testdataFactory.createTarget("target3");
// assign ds to target2, but don't finish update (actions should be // assign ds to target2, but don't finish update (actions should be still open)
// still open)
assignDistributionSet(action.getDistributionSet().getId(), target2.getControllerId()); assignDistributionSet(action.getDistributionSet().getId(), target2.getControllerId());
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet( final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(
action.getDistributionSet().getId(), action.getDistributionSet().getId(),
Arrays.asList(action.getTarget().getControllerId(), target3.getControllerId()), ActionType.FORCED); List.of(action.getTarget().getControllerId(), target3.getControllerId()), ActionType.FORCED);
assertThat(assignmentResult).isNotNull(); assertThat(assignmentResult).isNotNull();
assertThat(assignmentResult.getTotal()).as("Total count of assigned and already assigned targets").isEqualTo(2); assertThat(assignmentResult.getTotal()).as("Total count of assigned and already assigned targets").isEqualTo(2);
@@ -1567,8 +1521,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet actionDistributionSet = distributionSetRepository.getById(action.getDistributionSet().getId()); final DistributionSet actionDistributionSet = distributionSetRepository.getById(action.getDistributionSet().getId());
assertThat(assignmentResult.getAssignedEntity()).allMatch( assertThat(assignmentResult.getAssignedEntity()).allMatch(
a -> a.getTarget().equals(target3) && a.getDistributionSet().equals(actionDistributionSet)); a -> a.getTarget().equals(target3) && a.getDistributionSet().equals(actionDistributionSet));
assertThat(assignmentResult.getAssignedEntity()) assertThat(assignmentResult.getAssignedEntity()).noneMatch(a -> a.getTarget().getControllerId().equals("target1"));
.noneMatch(a -> a.getTarget().getControllerId().equals("target1"));
} }
/** /**
@@ -1580,8 +1533,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// create assigned DS // create assigned DS
final Target savedTarget = testdataFactory.createTarget(); final Target savedTarget = testdataFactory.createTarget();
DistributionSetAssignmentResult assignmentResult = assignDistributionSet(dsToTargetAssigned.getId(), DistributionSetAssignmentResult assignmentResult = assignDistributionSet(dsToTargetAssigned.getId(), savedTarget.getControllerId());
savedTarget.getControllerId());
assertThat(assignmentResult.getAssignedEntity()).hasSize(1); assertThat(assignmentResult.getAssignedEntity()).hasSize(1);
assignmentResult = assignDistributionSet(dsToTargetAssigned.getId(), savedTarget.getControllerId()); assignmentResult = assignDistributionSet(dsToTargetAssigned.getId(), savedTarget.getControllerId());
@@ -1601,8 +1553,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>(); final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
for (int i = 0; i < quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment(); i++) { for (int i = 0; i < quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment(); i++) {
final Target target = testdataFactory.createTarget("test-target-" + i, "test-target-" + i, targetType); final Target target = testdataFactory.createTarget("test-target-" + i, "test-target-" + i, targetType);
final DeploymentRequest deployment = DeploymentRequest final DeploymentRequest deployment = DeploymentRequest.builder(target.getControllerId(), ds.getId()).build();
.builder(target.getControllerId(), ds.getId()).build();
deploymentRequests.add(deployment); deploymentRequests.add(deployment);
} }
@@ -1688,7 +1639,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertEquals(20, actions); assertEquals(20, actions);
// extract the first 5 action ids // extract the first 5 action ids
List<Action> firstSample = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent(); final List<Action> firstSample = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
List<Long> shouldBePurgedActionsList = firstSample.stream().map(Identifiable::getId).limit(5).toList(); List<Long> shouldBePurgedActionsList = firstSample.stream().map(Identifiable::getId).limit(5).toList();
DistributionSet exceededQuotaDsAssign = testdataFactory.createDistributionSet("exceededQuotaAssignment"); DistributionSet exceededQuotaDsAssign = testdataFactory.createDistributionSet("exceededQuotaAssignment");
@@ -1706,7 +1657,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
actions = deploymentManagement.countActionsByTarget(target.getControllerId()); actions = deploymentManagement.countActionsByTarget(target.getControllerId());
assertEquals(16, actions); assertEquals(16, actions);
List<Action> actionsList = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent(); final List<Action> actionsList = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
// first 5 should have been purged so the first actionId should be the last purged action id + 1 // first 5 should have been purged so the first actionId should be the last purged action id + 1
assertEquals(shouldBePurgedActionsList.get(shouldBePurgedActionsList.size() - 1) + 1, actionsList.get(0).getId()); assertEquals(shouldBePurgedActionsList.get(shouldBePurgedActionsList.size() - 1) + 1, actionsList.get(0).getId());
assertEquals(firstSample.get(firstSample.size() - 1).getId() + 1, actionsList.get(15).getId()); assertEquals(firstSample.get(firstSample.size() - 1).getId() + 1, actionsList.get(15).getId());
@@ -1717,9 +1668,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
DistributionSet distributionSet = testdataFactory.createDistributionSet(); DistributionSet distributionSet = testdataFactory.createDistributionSet();
Rollout rollout = testdataFactory.createRolloutByVariables("rollout-" + i, "Description", 1, "controllerId==" + target Rollout rollout = testdataFactory.createRolloutByVariables(
.getControllerId(), "rollout-" + i, "Description", 1, "controllerId==" + target.getControllerId(), distributionSet, "50", "50");
distributionSet, "50", "50");
rolloutManagement.start(rollout.getId()); rolloutManagement.start(rollout.getId());
rolloutHandler.handleAll(); rolloutHandler.handleAll();
} }
@@ -1729,9 +1679,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
List<Long> shouldBePurgedActionsList = firstSample.stream().map(Identifiable::getId).limit(5).toList(); List<Long> shouldBePurgedActionsList = firstSample.stream().map(Identifiable::getId).limit(5).toList();
DistributionSet distributionSet = testdataFactory.createDistributionSet(); DistributionSet distributionSet = testdataFactory.createDistributionSet();
Rollout rollout = testdataFactory.createRolloutByVariables("rollout-quota", "Description", 1, "controllerId==" + target Rollout rollout = testdataFactory.createRolloutByVariables(
.getControllerId(), "rollout-quota", "Description", 1, "controllerId==" + target.getControllerId(), distributionSet, "50", "50");
distributionSet, "50", "50");
rolloutManagement.start(rollout.getId()); rolloutManagement.start(rollout.getId());
// don't assert quota exception here because rollout executor does not throw such in order to not interrupt other executions // don't assert quota exception here because rollout executor does not throw such in order to not interrupt other executions
rolloutHandler.handleAll(); rolloutHandler.handleAll();
@@ -1747,7 +1696,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// first 5 should have been purged so the first actionId should be the last purged action id + 1 // first 5 should have been purged so the first actionId should be the last purged action id + 1
assertEquals(shouldBePurgedActionsList.get(shouldBePurgedActionsList.size() - 1) + 1, actionsList.get(0).getId()); assertEquals(shouldBePurgedActionsList.get(shouldBePurgedActionsList.size() - 1) + 1, actionsList.get(0).getId());
assertEquals(firstSample.get(firstSample.size() - 1).getId() + 1, actionsList.get(15).getId()); assertEquals(firstSample.get(firstSample.size() - 1).getId() + 1, actionsList.get(15).getId());
} }
@Test @Test
@@ -1755,9 +1703,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
for (int i = 0; i < 18; i++) { for (int i = 0; i < 18; i++) {
DistributionSet distributionSet = testdataFactory.createDistributionSet(); DistributionSet distributionSet = testdataFactory.createDistributionSet();
Rollout rollout = testdataFactory.createRolloutByVariables("rollout-" + i, "Description", 1, "controllerId==" + target Rollout rollout = testdataFactory.createRolloutByVariables(
.getControllerId(), "rollout-" + i, "Description", 1, "controllerId==" + target.getControllerId(), distributionSet, "50", "50");
distributionSet, "50", "50");
rolloutManagement.start(rollout.getId()); rolloutManagement.start(rollout.getId());
rolloutHandler.handleAll(); rolloutHandler.handleAll();
} }
@@ -1781,8 +1728,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
private List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets, private List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
final Collection<Target> targets, final int weight, final boolean confirmationRequired) { final Collection<Target> targets, final int weight, final boolean confirmationRequired) {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>(); final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
distributionSets.forEach(ds -> targets.forEach(target -> deploymentRequests distributionSets.forEach(ds -> targets.forEach(target -> deploymentRequests.add(
.add(DeploymentRequest.builder(target.getControllerId(), ds.getId()) DeploymentRequest.builder(target.getControllerId(), ds.getId())
.weight(weight).confirmationRequired(confirmationRequired).build()))); .weight(weight).confirmationRequired(confirmationRequired).build())));
return deploymentRequests; return deploymentRequests;
} }
@@ -1805,8 +1752,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
return action; return action;
} }
private void assertDsExclusivelyAssignedToTargets(final List<Target> targets, final long dsId, final boolean active, private void assertDsExclusivelyAssignedToTargets(final List<Target> targets, final long dsId, final boolean active, final Status status) {
final Status status) {
final List<Action> assignment = findActionsByDistributionSet(PAGE, dsId).getContent(); final List<Action> assignment = findActionsByDistributionSet(PAGE, dsId).getContent();
final String currentUsername = tenantAware.getCurrentUsername(); final String currentUsername = tenantAware.getCurrentUsername();
@@ -1819,8 +1765,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray()); assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray());
} }
private List<DistributionSetAssignmentResult> assignDistributionSetToTargets(final DistributionSet distributionSet, private List<DistributionSetAssignmentResult> assignDistributionSetToTargets(
final Iterable<String> targetIds, final boolean confirmationRequired) { final DistributionSet distributionSet, final Iterable<String> targetIds, final boolean confirmationRequired) {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>(); final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
for (final String controllerId : targetIds) { for (final String controllerId : targetIds) {
deploymentRequests.add(new DeploymentRequest(controllerId, distributionSet.getId(), ActionType.FORCED, 0, deploymentRequests.add(new DeploymentRequest(controllerId, distributionSet.getId(), ActionType.FORCED, 0,
@@ -1846,11 +1792,11 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
* @param noOfDeployedTargets number of targets to which the created distribution sets assigned * @param noOfDeployedTargets number of targets to which the created distribution sets assigned
* @param noOfDistributionSets number of distribution sets * @param noOfDistributionSets number of distribution sets
* @param distributionSetPrefix prefix for the created distribution sets * @param distributionSetPrefix prefix for the created distribution sets
* @return the {@link DeploymentResult} containing all created targets, the * @return the {@link DeploymentResult} containing all created targets, the distribution sets, the corresponding IDs for later evaluation in
* distribution sets, the corresponding IDs for later evaluation in
* tests * tests
*/ */
private DeploymentResult prepareComplexRepo(final String undeployedTargetPrefix, final int noOfUndeployedTargets, private DeploymentResult prepareComplexRepo(
final String undeployedTargetPrefix, final int noOfUndeployedTargets,
final String deployedTargetPrefix, final int noOfDeployedTargets, final int noOfDistributionSets, final String deployedTargetPrefix, final int noOfDeployedTargets, final int noOfDistributionSets,
final String distributionSetPrefix) { final String distributionSetPrefix) {
final Iterable<Target> nakedTargets = testdataFactory.createTargets(noOfUndeployedTargets, final Iterable<Target> nakedTargets = testdataFactory.createTargets(noOfUndeployedTargets,
@@ -1865,8 +1811,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// assigning all DistributionSet to the Target in the list // assigning all DistributionSet to the Target in the list
// deployedTargets // deployedTargets
for (final DistributionSet ds : dsList) { for (final DistributionSet ds : dsList) {
deployedTargets = assignDistributionSet(ds, deployedTargets).getAssignedEntity().stream() deployedTargets = assignDistributionSet(ds, deployedTargets).getAssignedEntity().stream().map(Action::getTarget).toList();
.map(Action::getTarget).toList();
implicitLock(ds); implicitLock(ds);
} }
@@ -1874,11 +1819,9 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
private Slice<Action> findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) { private Slice<Action> findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) {
distributionSetManagement.find(distributionSetId).orElseThrow(() -> distributionSetManagement.find(distributionSetId)
new EntityNotFoundException(DistributionSet.class, distributionSetId)); .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distributionSetId));
return actionRepository return actionRepository.findAll(byDistributionSetId(distributionSetId), pageable).map(Action.class::cast);
.findAll(byDistributionSetId(distributionSetId), pageable)
.map(Action.class::cast);
} }
@Getter @Getter

View File

@@ -27,18 +27,20 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.T
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
/** /**
* Feature: Component Tests - Repository<br/> * Feature: Component Tests - Repository<br/>
* Story: Tenant Configuration Management * Story: Tenant Configuration Management
*/ */
@TestPropertySource(properties = {"hawkbit.cache.JpaTenantConfiguration.spec=maximumSize=0"}) // disable cache, its async processed
class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware { class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware {
private Environment environment; private Environment env;
@Override @Override
public void setEnvironment(final Environment environment) { public void setEnvironment(final Environment env) {
this.environment = environment; this.env = env;
} }
/** /**
@@ -46,7 +48,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
*/ */
@Test @Test
void storeTenantSpecificConfigurationAsString() { void storeTenantSpecificConfigurationAsString() {
final String envPropertyDefault = environment.getProperty("hawkbit.server.ddi.security.authentication.gatewaytoken.key"); final String envPropertyDefault = env.getProperty("hawkbit.server.ddi.security.authentication.gatewaytoken.key");
assertThat(envPropertyDefault).isNotNull(); assertThat(envPropertyDefault).isNotNull();
// get the configuration from the system management // get the configuration from the system management
@@ -56,7 +58,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
assertThat(defaultConfigValue.isGlobal()).isTrue(); assertThat(defaultConfigValue.isGlobal()).isTrue();
assertThat(defaultConfigValue.getValue()).isEqualTo(envPropertyDefault); assertThat(defaultConfigValue.getValue()).isEqualTo(envPropertyDefault);
// update the tenant specific configuration // update the tenant specific configuration, create
final String newConfigurationValue = "thisIsAnotherTokenName"; final String newConfigurationValue = "thisIsAnotherTokenName";
assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue.getValue()); assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue.getValue());
tenantConfigurationManagement.addOrUpdateConfiguration( tenantConfigurationManagement.addOrUpdateConfiguration(
@@ -68,6 +70,19 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
assertThat(updatedConfigurationValue.isGlobal()).isFalse(); assertThat(updatedConfigurationValue.isGlobal()).isFalse();
assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue); assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue);
// update the tenant specific configuration, create
final String newConfigurationValue2 = "thisIsAnotherTokenName2";
assertThat(newConfigurationValue2).isNotEqualTo(updatedConfigurationValue.getValue());
tenantConfigurationManagement.addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, newConfigurationValue2);
// verify that new configuration value is used
final TenantConfigurationValue<String> updatedConfigurationValue2 = tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class);
assertThat(updatedConfigurationValue2.isGlobal()).isFalse();
assertThat(updatedConfigurationValue2.getValue()).isEqualTo(newConfigurationValue2);
// assertThat(tenantConfigurationManagement.getTenantConfigurations()).hasSize(1); // assertThat(tenantConfigurationManagement.getTenantConfigurations()).hasSize(1);
} }
@@ -94,7 +109,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
*/ */
@Test @Test
void batchUpdateTenantSpecificConfiguration() { void batchUpdateTenantSpecificConfiguration() {
Map<String, Serializable> configuration = new HashMap<>() {{ final Map<String, Serializable> configuration = new HashMap<>() {{
put(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, "token_123"); put(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true); put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
}}; }};
@@ -185,8 +200,7 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
// delete the tenant specific configuration // delete the tenant specific configuration
tenantConfigurationManagement.deleteConfiguration(configKey); tenantConfigurationManagement.deleteConfiguration(configKey);
// ensure that now gateway token is set again, because is deleted and // ensure that now gateway token is set again, because is deleted and must be empty now
// must be empty now
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isEmpty(); assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isEmpty();
} }

View File

@@ -409,8 +409,7 @@ public abstract class AbstractIntegrationTest {
return assignDistributionSet(pset, Collections.singletonList(target)); return assignDistributionSet(pset, Collections.singletonList(target));
} }
protected DistributionSetAssignmentResult assignDistributionSet(final long dsId, final String targetId, protected DistributionSetAssignmentResult assignDistributionSet(final long dsId, final String targetId, final int weight) {
final int weight) {
return assignDistributionSet(dsId, Collections.singletonList(targetId), weight); return assignDistributionSet(dsId, Collections.singletonList(targetId), weight);
} }

View File

@@ -15,8 +15,9 @@ import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager; import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -55,6 +56,7 @@ public class CleanupTestExecutionListener extends AbstractTestExecutionListener
log.error("Error while delete tenant", e); log.error("Error while delete tenant", e);
} }
}); });
TenantAwareCacheManager.getInstance().evictTenant(null); // evict global cache
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CacheEvictEvent.Default(null, null, null));
} }
} }