Fix nop cache using (especially for testing) (#2841)

+ fix flaky tests that requires no caches

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-12-01 13:53:08 +02:00
committed by GitHub
parent b8a05e3cbf
commit 6988f5eafb
6 changed files with 100 additions and 27 deletions

View File

@@ -1,5 +1,4 @@
# org.springframework:spring-web:5.3.31.RELEASE, ineffective vulnerability - hawkBit doesn't use beans of type HttpInvokerServiceExporter in applications
CVE-2016-1000027
# org.yaml:snakeyaml:1.33, ineffective vulnerability - Not applicable. Applications does not consume user-provided YAML data
CVE-2022-1471
# org.eclipse.hawkbit:*
# this trivy finding is false positive since the current master (and other, new temporary, branches) doesn't contain http dependencies anymore
# trivy detects 0-SNAPSHOT version and could not 'understand' that it is newer then 0.3.0M2 (since which the http dependencies were removed)
CVE-2019-10240

View File

@@ -18,6 +18,7 @@ import java.util.concurrent.ConcurrentHashMap;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Value;
@@ -27,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.lang.Nullable;
@@ -122,6 +124,26 @@ public class TenantAwareCacheManager implements CacheManager {
}
}
// Nop if, null, blank, "nop", "none", "maximumSize=0" or "expireAfterWrite=0"
static boolean isNop(@Nullable String spec) {
if (spec == null || spec.isBlank()) {
return true;
}
final String trimmed = spec.replaceAll("\\s", "");
return "nop".equalsIgnoreCase(trimmed) ||
"none".equalsIgnoreCase(trimmed) ||
(trimmed.contains("maximumSize=0") &&
("maximumSize=0".equals(trimmed) ||
trimmed.startsWith("maximumSize=0,") ||
trimmed.contains(",maximumSize=0,") ||
trimmed.endsWith(",maximumSize=0"))) ||
(trimmed.contains("expireAfterWrite=0") &&
("expireAfterWrite=0".equals(trimmed) ||
trimmed.startsWith("expireAfterWrite=0,") ||
trimmed.contains(",expireAfterWrite=0,") ||
trimmed.endsWith(",expireAfterWrite=0")));
}
public interface CacheEvictEvent {
String getTenant();
@@ -161,6 +183,11 @@ public class TenantAwareCacheManager implements CacheManager {
spec = defaultSpec;
}
}
// it seems that setting maximumSize=0 doesn't filly disables the Caffeine cache, so we explicitly check for Nop cache
if (isNop(spec)) {
log.info("Using NOP cache for tenant '{}' and cache '{}'", tenant, name);
return new Nop(name);
}
try {
return new CaffeineCache(n, Caffeine.from(spec).build(), false) {
@@ -188,35 +215,35 @@ public class TenantAwareCacheManager implements CacheManager {
}
@Value
private static class Nop implements Cache {
@EqualsAndHashCode(callSuper = true)
private static class Nop extends AbstractValueAdaptingCache {
String name;
private Nop(final String name) {
super(false);
this.name = name;
}
@NonNull
@Override
public String getName() {
return name;
}
@NonNull
@Override
public Object getNativeCache() {
public @NonNull 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
@SuppressWarnings("unchecked")
public <T> T get(@NonNull final Object key, @NonNull final Callable<T> valueLoader) {
return null;
try {
return (T) fromStoreValue(valueLoader.call());
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
}
@Override
@@ -233,5 +260,10 @@ public class TenantAwareCacheManager implements CacheManager {
public void clear() {
// nop
}
@Override
protected Object lookup(@NonNull final Object key) {
return null; // nop cache doesn't cache anything, especially used to DO NOT cache null values
}
}
}

View File

@@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.im.authentication;
package org.eclipse.hawkbit.auth;
import static org.assertj.core.api.Assertions.assertThat;
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test;
* Feature: Unit Tests - Security<br/>
* Story: Permission Test
*/
final class SpPermissionTest {
class SpPermissionTest {
/**
* Double-checks that all permissions doesn't contain any hierarchies.

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.tenancy;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collection;
import org.junit.jupiter.api.Test;
class TenantAwareCacheManagerTest {
/**
* Double-checks that all permissions doesn't contain any hierarchies.
*/
@Test
void testNopCaches() {
assertThat(TenantAwareCacheManager.isNop(null)).isTrue();
assertThat(TenantAwareCacheManager.isNop("")).isTrue();
assertThat(TenantAwareCacheManager.isNop(" ")).isTrue();
assertThat(TenantAwareCacheManager.isNop("\t")).isTrue();
assertThat(TenantAwareCacheManager.isNop("nop")).isTrue();
assertThat(TenantAwareCacheManager.isNop("none")).isTrue();
assertThat(TenantAwareCacheManager.isNop("maximumSize=0")).isTrue();
assertThat(TenantAwareCacheManager.isNop("maximumSize=0,something")).isTrue();
assertThat(TenantAwareCacheManager.isNop("something, maximumSize=0")).isTrue();
assertThat(TenantAwareCacheManager.isNop("something, maximumSize=0 \t, something")).isTrue();
assertThat(TenantAwareCacheManager.isNop(" expireAfterWrite=0")).isTrue();
assertThat(TenantAwareCacheManager.isNop("expireAfterWrite=0,something")).isTrue();
assertThat(TenantAwareCacheManager.isNop("something, expireAfterWrite =0")).isTrue();
assertThat(TenantAwareCacheManager.isNop("something, expireAfterWrite= 0 \t, something")).isTrue();
assertThat(TenantAwareCacheManager.isNop("maximumSize=01")).isFalse();
assertThat(TenantAwareCacheManager.isNop("maximumSize=100")).isFalse();
assertThat(TenantAwareCacheManager.isNop("expireAfterWrite=01")).isFalse();
assertThat(TenantAwareCacheManager.isNop("expireAfterWrite=100")).isFalse();
}
}

View File

@@ -23,9 +23,9 @@ hawkbit.controller.maintenanceWindowPollCount=3
# Cache config
hawkbit.cache.ttl=10s
hawkbit.cache.spec=expireAfterAccess=${hawkbit.cache.ttl}
hawkbit.cache.RolloutStatus.spec=maximumSize=50000,expireAfterAccess=${hawkbit.cache.ttl}
hawkbit.cache.RolloutGroupStatus.spec=maximumSize=50000,expireAfterAccess=${hawkbit.cache.ttl}
hawkbit.cache.spec=expireAfterWrite=${hawkbit.cache.ttl}
hawkbit.cache.RolloutStatus.spec=maximumSize=50000,expireAfterWrite=${hawkbit.cache.ttl}
hawkbit.cache.RolloutGroupStatus.spec=maximumSize=50000,expireAfterWrite=${hawkbit.cache.ttl}
# Cache config - END
# Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window

View File

@@ -89,9 +89,6 @@ class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest imple
tenantConfigurationManagement.addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, newConfigurationValue2);
// sometimes it reads old value, maybe if read too early. wait to settle up?
waitMillis(100);
// verify that new configuration value is used
final TenantConfigurationValue<String> updatedConfigurationValue2 = tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class);