This is an automated email from the ASF dual-hosted git repository.
ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git
The following commit(s) were added to refs/heads/trunk by this push:
new 23f3158c CASSSIDECAR-348: Add observability around authN and authZ
caches (#262)
23f3158c is described below
commit 23f3158c867f350f930257c198c586d5e25a0883
Author: Saranya Krishnakumar <[email protected]>
AuthorDate: Tue Sep 30 13:35:41 2025 -0700
CASSSIDECAR-348: Add observability around authN and authZ caches (#262)
Patch by Saranya Krishnakumar; Reviewed by Francisco Guerrero, Yifan Cai
for CASSSIDECAR-348
---
CHANGES.txt | 1 +
conf/sidecar.yaml | 5 +-
.../apache/cassandra/sidecar/acl/AuthCache.java | 47 +++-
.../cassandra/sidecar/acl/IdentityToRoleCache.java | 7 +-
.../acl/authorization/RoleAuthorizationsCache.java | 7 +-
.../sidecar/acl/authorization/SuperUserCache.java | 7 +-
.../sidecar/config/CacheConfiguration.java | 5 +
.../config/yaml/CacheConfigurationImpl.java | 21 +-
.../sidecar/metrics/server/CacheMetrics.java | 6 +
.../sidecar/testing/IntegrationTestModule.java | 1 +
.../acl/CassandraIdentityExtractorTest.java | 11 +-
.../sidecar/acl/IdentityToRoleCacheTest.java | 26 ++-
.../sidecar/acl/RoleAuthorizationsCacheTest.java | 258 ++++++++++++++++++---
.../MutualTLSAuthenticationHandlerTest.java | 3 +-
.../MutualTlsAuthenticationHandlerFactoryTest.java | 13 +-
.../acl/authorization/SuperUserCacheTest.java | 15 +-
.../sidecar/config/SidecarConfigurationTest.java | 1 +
.../config/yaml/CacheConfigurationImplTest.java | 100 ++++++++
18 files changed, 474 insertions(+), 60 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 07c4e007..9f1262a7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
0.3.0
-----
+ * Add observability around authN and authZ caches in Sidecar (CASSSIDECAR-348)
* Thread race issue when adding excluded metric in FilteringMetricRegistry
(CASSSIDECAR-349)
* Add support for stateless JWT authentication using public keys
(CASSSIDECAR-334)
* Improve FilteringMetricRegistry implementation (CASSSIDECAR-347)
diff --git a/conf/sidecar.yaml b/conf/sidecar.yaml
index e6e6936a..809181ab 100644
--- a/conf/sidecar.yaml
+++ b/conf/sidecar.yaml
@@ -292,7 +292,10 @@ access_control:
# - spiffe://authorized/admin/identities
permission_cache:
enabled: true
- expire_after_access: 5m
+ # refresh_after_write does async cache refreshes. expire_after_access
removes the cache entry on expiry. Prefer
+ # setting refresh_after_write over expire_after_access for AuthCaches
+ # expire_after_access: 5m
+ refresh_after_write: 5m
maximum_size: 1000
warmup_retries: 5
warmup_retry_interval: 2s
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/acl/AuthCache.java
b/server/src/main/java/org/apache/cassandra/sidecar/acl/AuthCache.java
index f60ab103..28e12857 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/acl/AuthCache.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/acl/AuthCache.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.sidecar.acl;
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -33,7 +34,9 @@ import io.vertx.core.eventbus.EventBus;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.concurrent.TaskExecutorPool;
import org.apache.cassandra.sidecar.config.CacheConfiguration;
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
import org.apache.cassandra.sidecar.exceptions.SchemaUnavailableException;
+import org.apache.cassandra.sidecar.metrics.CacheStatsCounter;
import org.jetbrains.annotations.VisibleForTesting;
import static
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SIDECAR_SCHEMA_INITIALIZED;
@@ -51,6 +54,7 @@ public abstract class AuthCache<K, V>
private final Function<K, V> loadFunction;
private final Supplier<Map<K, V>> bulkLoadFunction;
private final CacheConfiguration config;
+ private final CacheStatsCounter cacheMetrics;
private final TaskExecutorPool internalPool;
// cache is null when AuthCache is disabled
private volatile LoadingCache<K, V> cache;
@@ -61,7 +65,8 @@ public abstract class AuthCache<K, V>
ExecutorPools executorPools,
Function<K, V> loadFunction,
Supplier<Map<K, V>> bulkLoadFunction,
- CacheConfiguration cacheConfiguration)
+ CacheConfiguration cacheConfiguration,
+ CacheStatsCounter cacheMetrics)
{
this.name = name;
this.vertx = vertx;
@@ -69,6 +74,7 @@ public abstract class AuthCache<K, V>
this.loadFunction = loadFunction;
this.bulkLoadFunction = bulkLoadFunction;
this.config = cacheConfiguration;
+ this.cacheMetrics = Objects.requireNonNull(cacheMetrics, "cacheMetrics
is required");
if (this.config.enabled())
{
@@ -119,15 +125,40 @@ public abstract class AuthCache<K, V>
return Collections.unmodifiableMap(cache.asMap());
}
+ /**
+ * Invalidate a key.
+ * @param k key to invalidate
+ */
+ public void invalidate(K k)
+ {
+ if (cache != null)
+ {
+ cache.invalidate(k);
+ logger.info("Cache entry with key={} has been invalidated", k);
+ }
+ }
+
private LoadingCache<K, V> initCache()
{
- return Caffeine.newBuilder()
- // setting refreshAfterWrite and expireAfterWrite to
same value makes sure no stale
- // data is fetched after expire time
-
.refreshAfterWrite(config.expireAfterAccess().quantity(),
config.expireAfterAccess().unit())
-
.expireAfterWrite(config.expireAfterAccess().quantity(),
config.expireAfterAccess().unit())
- .maximumSize(config.maximumSize())
- .build(loadFunction::apply);
+ if (config.refreshAfterWrite() == null && config.expireAfterAccess()
== null)
+ {
+ throw new ConfigurationException(name +
+ " must be configured with either
refreshAfterWrite or expireAfterAccess");
+ }
+
+ Caffeine<Object, Object> cacheBuilder
+ = Caffeine.newBuilder()
+ .recordStats(() -> cacheMetrics)
+ .maximumSize(config.maximumSize());
+ if (config.refreshAfterWrite() != null)
+ {
+
cacheBuilder.refreshAfterWrite(config.refreshAfterWrite().quantity(),
config.refreshAfterWrite().unit());
+ }
+ if (config.expireAfterAccess() != null)
+ {
+
cacheBuilder.expireAfterAccess(config.expireAfterAccess().quantity(),
config.expireAfterAccess().unit());
+ }
+ return cacheBuilder.build(loadFunction::apply);
}
private void configureSidecarServerEventListener()
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCache.java
b/server/src/main/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCache.java
index 1ede4188..752d2273 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCache.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCache.java
@@ -25,6 +25,7 @@ import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
import org.apache.cassandra.sidecar.exceptions.SchemaUnavailableException;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
/**
* Caches entries from system_auth.identity_to_role table. The table maps
valid certificate identities to Cassandra
@@ -39,14 +40,16 @@ public class IdentityToRoleCache extends AuthCache<String,
String>
public IdentityToRoleCache(Vertx vertx,
ExecutorPools executorPools,
SidecarConfiguration sidecarConfiguration,
- SystemAuthDatabaseAccessor
systemAuthDatabaseAccessor)
+ SystemAuthDatabaseAccessor
systemAuthDatabaseAccessor,
+ SidecarMetrics sidecarMetrics)
{
super(NAME,
vertx,
executorPools,
systemAuthDatabaseAccessor::findRoleFromIdentity,
systemAuthDatabaseAccessor::findAllIdentityToRoles,
-
sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration());
+
sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration(),
+ sidecarMetrics.server().cache().identityToRoleCacheMetrics);
}
public boolean containsKey(String identity)
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleAuthorizationsCache.java
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleAuthorizationsCache.java
index 7b04d2d3..d56a9023 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleAuthorizationsCache.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleAuthorizationsCache.java
@@ -32,6 +32,7 @@ import
org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SidecarPermissionsDatabaseAccessor;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
import org.apache.cassandra.sidecar.db.schema.SidecarSchema;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
/**
* Caches role and authorizations held by it. Entries from
system_auth.role_permissions table in Cassandra and
@@ -51,7 +52,8 @@ public class RoleAuthorizationsCache extends
AuthCache<String, Map<String, Set<A
SidecarConfiguration sidecarConfiguration,
SidecarSchema sidecarSchema,
SystemAuthDatabaseAccessor
systemAuthDatabaseAccessor,
- SidecarPermissionsDatabaseAccessor
sidecarPermissionsDatabaseAccessor)
+ SidecarPermissionsDatabaseAccessor
sidecarPermissionsDatabaseAccessor,
+ SidecarMetrics sidecarMetrics)
{
super(NAME,
vertx,
@@ -63,7 +65,8 @@ public class RoleAuthorizationsCache extends
AuthCache<String, Map<String, Set<A
loadAuthorizations(systemAuthDatabaseAccessor,
sidecarSchema,
sidecarPermissionsDatabaseAccessor)),
-
sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration());
+
sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration(),
+ sidecarMetrics.server().cache().rolePermissionsCacheMetrics);
}
/**
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCache.java
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCache.java
index a011a6c2..511af804 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCache.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCache.java
@@ -24,6 +24,7 @@ import org.apache.cassandra.sidecar.acl.AuthCache;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
/**
* Caches superuser status of cassandra roles. Returns true if the supplied
role or any other role granted to it
@@ -38,14 +39,16 @@ public class SuperUserCache extends AuthCache<String,
Boolean>
public SuperUserCache(Vertx vertx,
ExecutorPools executorPools,
SidecarConfiguration sidecarConfiguration,
- SystemAuthDatabaseAccessor
systemAuthDatabaseAccessor)
+ SystemAuthDatabaseAccessor
systemAuthDatabaseAccessor,
+ SidecarMetrics sidecarMetrics)
{
super(NAME,
vertx,
executorPools,
systemAuthDatabaseAccessor::isSuperUser,
systemAuthDatabaseAccessor::findAllRolesToSuperuserStatus,
-
sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration());
+
sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration(),
+ sidecarMetrics.server().cache().superUserCacheMetrics);
}
public boolean isSuperUser(String role)
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
b/server/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
index f1f6b4cd..61be7322 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
@@ -31,6 +31,11 @@ public interface CacheConfiguration
*/
MillisecondBoundConfiguration expireAfterAccess();
+ /**
+ * @return the configured amount of time after which cache entries are
refreshed.
+ */
+ MillisecondBoundConfiguration refreshAfterWrite();
+
/**
* @return the maximum number of entries the cache may contain
*/
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java
b/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java
index 2b5e0e9b..150864a4 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java
@@ -37,6 +37,8 @@ public class CacheConfigurationImpl implements
CacheConfiguration
protected MillisecondBoundConfiguration expireAfterAccess;
+ protected MillisecondBoundConfiguration refreshAfterWrite;
+
@JsonProperty("maximum_size")
protected final long maximumSize;
@@ -50,22 +52,24 @@ public class CacheConfigurationImpl implements
CacheConfiguration
public CacheConfigurationImpl()
{
- this(MillisecondBoundConfiguration.parse("1h"), 100, true, 5,
MillisecondBoundConfiguration.parse("1s"));
+ this(null, null, 100, true, 5,
MillisecondBoundConfiguration.parse("1s"));
}
@VisibleForTesting
public CacheConfigurationImpl(MillisecondBoundConfiguration
expireAfterAccess, long maximumSize)
{
- this(expireAfterAccess, maximumSize, true, 5,
MillisecondBoundConfiguration.parse("1s"));
+ this(expireAfterAccess, MillisecondBoundConfiguration.parse("1h"),
maximumSize, true, 5, MillisecondBoundConfiguration.parse("1s"));
}
public CacheConfigurationImpl(MillisecondBoundConfiguration
expireAfterAccess,
+ MillisecondBoundConfiguration
refreshAfterWrite,
long maximumSize,
boolean enabled,
int warmupRetries,
MillisecondBoundConfiguration
warmupRetryInterval)
{
this.expireAfterAccess = expireAfterAccess;
+ this.refreshAfterWrite = refreshAfterWrite;
this.maximumSize = maximumSize;
this.enabled = enabled;
this.warmupRetries = warmupRetries;
@@ -85,6 +89,19 @@ public class CacheConfigurationImpl implements
CacheConfiguration
this.expireAfterAccess = expireAfterAccess;
}
+ @Override
+ @JsonProperty("refresh_after_write")
+ public MillisecondBoundConfiguration refreshAfterWrite()
+ {
+ return refreshAfterWrite;
+ }
+
+ @JsonProperty("refresh_after_write")
+ public void setRefreshAfterWrite(MillisecondBoundConfiguration
refreshAfterWrite)
+ {
+ this.refreshAfterWrite = refreshAfterWrite;
+ }
+
/**
* Legacy property {@code expire_after_access_millis}
*
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/metrics/server/CacheMetrics.java
b/server/src/main/java/org/apache/cassandra/sidecar/metrics/server/CacheMetrics.java
index 4b612ade..3c478884 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/metrics/server/CacheMetrics.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/metrics/server/CacheMetrics.java
@@ -29,9 +29,15 @@ import static
org.apache.cassandra.sidecar.handlers.snapshots.ListSnapshotHandle
public class CacheMetrics
{
public final CacheStatsCounter snapshotCacheMetrics;
+ public final CacheStatsCounter identityToRoleCacheMetrics;
+ public final CacheStatsCounter superUserCacheMetrics;
+ public final CacheStatsCounter rolePermissionsCacheMetrics;
public CacheMetrics(MetricRegistry globalMetricRegistry)
{
snapshotCacheMetrics = new CacheStatsCounter(globalMetricRegistry,
SNAPSHOT_CACHE_NAME);
+ identityToRoleCacheMetrics = new
CacheStatsCounter(globalMetricRegistry, "identity_to_role_cache");
+ superUserCacheMetrics = new CacheStatsCounter(globalMetricRegistry,
"super_user_cache");
+ rolePermissionsCacheMetrics = new
CacheStatsCounter(globalMetricRegistry, "role_permissions_cache");
}
}
diff --git
a/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
b/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
index 16a0438e..8bc70252 100644
---
a/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
+++
b/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
@@ -263,6 +263,7 @@ public class IntegrationTestModule extends AbstractModule
rbacConfig,
Collections.singleton(ADMIN_IDENTITY),
new
CacheConfigurationImpl(MillisecondBoundConfiguration.parse("1s"),
+
MillisecondBoundConfiguration.parse("1s"),
100,
true,
5,
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/acl/CassandraIdentityExtractorTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/acl/CassandraIdentityExtractorTest.java
index 8c0f78c5..ae7e8df4 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/acl/CassandraIdentityExtractorTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/acl/CassandraIdentityExtractorTest.java
@@ -36,6 +36,9 @@ import
org.apache.cassandra.sidecar.config.AccessControlConfiguration;
import org.apache.cassandra.sidecar.config.CacheConfiguration;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
+import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
+import org.apache.cassandra.sidecar.metrics.SidecarMetricsImpl;
import org.apache.cassandra.testing.utils.tls.CertificateBuilder;
import static
org.apache.cassandra.sidecar.ExecutorPoolsHelper.createdSharedTestPool;
@@ -49,14 +52,20 @@ import static org.mockito.Mockito.when;
*/
class CassandraIdentityExtractorTest
{
+ private static final MetricRegistryFactory FACTORY
+ = new MetricRegistryFactory(CassandraIdentityExtractorTest.class.getName(),
+ Collections.emptyList(),
+ Collections.emptyList());
Vertx vertx;
ExecutorPools executorPools;
+ SidecarMetrics sidecarMetrics;
@BeforeEach
void setup()
{
vertx = Vertx.vertx();
executorPools = createdSharedTestPool(vertx);
+ sidecarMetrics = new SidecarMetricsImpl(FACTORY, null);
}
@AfterEach
@@ -137,7 +146,7 @@ class CassandraIdentityExtractorTest
when(mockCacheConfig.maximumSize()).thenReturn(10L);
when(mockAccessControlConfig.permissionCacheConfiguration()).thenReturn(mockCacheConfig);
- return new IdentityToRoleCache(vertx, executorPools,
mockSidecarConfig, mockDbAccessor);
+ return new IdentityToRoleCache(vertx, executorPools,
mockSidecarConfig, mockDbAccessor, sidecarMetrics);
}
private X509Certificate certificate(String identity) throws Exception
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCacheTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCacheTest.java
index dc5ddbbf..76200b84 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCacheTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/acl/IdentityToRoleCacheTest.java
@@ -40,6 +40,9 @@ import org.apache.cassandra.sidecar.config.CacheConfiguration;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
import org.apache.cassandra.sidecar.db.schema.SystemAuthSchema;
+import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
+import org.apache.cassandra.sidecar.metrics.SidecarMetricsImpl;
import static
org.apache.cassandra.sidecar.ExecutorPoolsHelper.createdSharedTestPool;
import static
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SIDECAR_SCHEMA_INITIALIZED;
@@ -52,20 +55,27 @@ import static org.mockito.Mockito.when;
*/
class IdentityToRoleCacheTest
{
+ private static final MetricRegistryFactory FACTORY
+ = new MetricRegistryFactory(IdentityToRoleCacheTest.class.getName(),
+ Collections.emptyList(),
+ Collections.emptyList());
Vertx vertx;
ExecutorPools executorPools;
+ SidecarMetrics sidecarMetrics;
@BeforeEach
void setup()
{
vertx = Vertx.vertx();
executorPools = createdSharedTestPool(vertx);
+ sidecarMetrics = new SidecarMetricsImpl(FACTORY, null);
}
@AfterEach
void cleanup()
{
TestResourceReaper.create().with(vertx).with(executorPools).close();
+ FACTORY.getOrCreate().removeMatching((name, metric) -> true);
}
@Test
@@ -75,7 +85,7 @@ class IdentityToRoleCacheTest
when(mockDbAccessor.findRoleFromIdentity("spiffe://cassandra/sidecar/test")).thenReturn("cassandra-role");
when(mockDbAccessor.findAllIdentityToRoles()).thenReturn(Collections.singletonMap("spiffe://cassandra/sidecar/test",
"cassandra-role"));
SidecarConfiguration mockConfig = mockConfig();
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor,
sidecarMetrics);
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test")).isTrue();
assertThat(identityToRoleCache.get("spiffe://cassandra/sidecar/test")).isEqualTo("cassandra-role");
assertThat(identityToRoleCache.getAll().size()).isOne();
@@ -89,7 +99,7 @@ class IdentityToRoleCacheTest
when(mockDbAccessor.findAllIdentityToRoles()).thenReturn(Collections.singletonMap("spiffe://cassandra/sidecar/test",
"cassandra-role"));
SidecarConfiguration mockConfig = mockConfig();
when(mockConfig.accessControlConfiguration().permissionCacheConfiguration().enabled()).thenReturn(false);
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor,
sidecarMetrics);
assertThat(identityToRoleCache.cache()).isNull();
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test")).isFalse();
// loaded with load function
@@ -110,12 +120,13 @@ class IdentityToRoleCacheTest
} };
when(mockDbAccessor.findAllIdentityToRoles()).thenReturn(identityRoles);
SidecarConfiguration mockConfig = mockConfig();
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor,
sidecarMetrics);
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test")).isTrue();
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test2")).isTrue();
assertThat(identityToRoleCache.get("spiffe://cassandra/sidecar/test")).isEqualTo("cassandra-role");
assertThat(identityToRoleCache.get("spiffe://cassandra/sidecar/test2")).isEqualTo("cassandra-role2");
assertThat(identityToRoleCache.getAll().size()).isEqualTo(2);
+
assertThat(sidecarMetrics.server().cache().identityToRoleCacheMetrics.snapshot().hitCount()).isEqualTo(2);
}
@Test
@@ -125,7 +136,7 @@ class IdentityToRoleCacheTest
when(mockDbAccessor.findRoleFromIdentity("spiffe://cassandra/sidecar/test")).thenReturn("cassandra-role");
when(mockDbAccessor.findAllIdentityToRoles()).thenReturn(Collections.singletonMap("spiffe://cassandra/sidecar/test",
"cassandra-role"));
SidecarConfiguration mockConfig = mockConfig();
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor,
sidecarMetrics);
assertThat(identityToRoleCache.cache().asMap().size()).isZero();
// warming cache
identityToRoleCache.warmUp(5);
@@ -143,7 +154,7 @@ class IdentityToRoleCacheTest
when(mockDbAccessor.findAllIdentityToRoles()).thenReturn(Collections.singletonMap("spiffe://cassandra/sidecar/test",
"cassandra-role"));
SidecarConfiguration mockConfig = mockConfig();
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor,
sidecarMetrics);
assertThat(identityToRoleCache.cache().asMap().size()).isZero();
// warming cache
@@ -155,6 +166,7 @@ class IdentityToRoleCacheTest
assertThat(identityToRoleCache.cache().asMap().size()).isOne();
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test")).isTrue();
assertThat(identityToRoleCache.get("spiffe://cassandra/sidecar/test")).isEqualTo("cassandra-role");
+
assertThat(sidecarMetrics.server().cache().identityToRoleCacheMetrics.snapshot().hitCount()).isOne();
}
@Test
@@ -165,7 +177,7 @@ class IdentityToRoleCacheTest
when(mockDbAccessor.findAllIdentityToRoles()).thenReturn(Collections.emptyMap());
SidecarConfiguration mockConfig = mockConfig();
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig, mockDbAccessor,
sidecarMetrics);
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test")).isFalse();
assertThat(identityToRoleCache.get("spiffe://cassandra/sidecar/test")).isNull();
@@ -183,7 +195,7 @@ class IdentityToRoleCacheTest
SidecarConfiguration mockConfig = mockConfig();
- IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig,
systemAuthDatabaseAccessor);
+ IdentityToRoleCache identityToRoleCache = new
IdentityToRoleCache(vertx, executorPools, mockConfig,
systemAuthDatabaseAccessor, sidecarMetrics);
assertThat(identityToRoleCache.containsKey("spiffe://cassandra/sidecar/test")).isFalse();
}
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/acl/RoleAuthorizationsCacheTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/acl/RoleAuthorizationsCacheTest.java
index 1cef08b5..f0477a5a 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/acl/RoleAuthorizationsCacheTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/acl/RoleAuthorizationsCacheTest.java
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import com.github.benmanes.caffeine.cache.stats.CacheStats;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.authorization.Authorization;
@@ -45,10 +46,15 @@ import
org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SidecarPermissionsDatabaseAccessor;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
import org.apache.cassandra.sidecar.db.schema.SidecarSchema;
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
+import org.apache.cassandra.sidecar.metrics.SidecarMetricsImpl;
import static
org.apache.cassandra.sidecar.ExecutorPoolsHelper.createdSharedTestPool;
import static
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SIDECAR_SCHEMA_INITIALIZED;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -57,9 +63,14 @@ import static org.mockito.Mockito.when;
*/
class RoleAuthorizationsCacheTest
{
+ private static final MetricRegistryFactory FACTORY
+ = new MetricRegistryFactory(RoleAuthorizationsCacheTest.class.getName(),
+ Collections.emptyList(),
+ Collections.emptyList());
Vertx vertx;
SidecarSchema mockSidecarSchema;
ExecutorPools executorPools;
+ SidecarMetrics sidecarMetrics;
@BeforeEach
void setup()
@@ -68,12 +79,14 @@ class RoleAuthorizationsCacheTest
mockSidecarSchema = mock(SidecarSchema.class);
when(mockSidecarSchema.isInitialized()).thenReturn(true);
executorPools = createdSharedTestPool(vertx);
+ sidecarMetrics = new SidecarMetricsImpl(FACTORY, null);
}
@AfterEach
void cleanup()
{
TestResourceReaper.create().with(vertx).with(executorPools).close();
+ FACTORY.getOrCreate().removeMatching((name, metric) -> true);
}
@Test
@@ -93,9 +106,13 @@ class RoleAuthorizationsCacheTest
mockConfig,
mockSidecarSchema,
mockDbAccessor,
-
mockSidecarPermissionsAccessor);
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
assertThat(cache.getAuthorizations("test_role1").size()).isEqualTo(2);
+ CacheStats initialCallStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(initialCallStats.hitCount()).isZero();
+ assertThat(initialCallStats.missCount()).isOne();
assertThat(cache.getAll().size()).isOne();
sidecarAuthorizations.put("test_role2", new
HashSet<>(Collections.singletonList(BasicPermissions.STREAM_SNAPSHOT.toAuthorization())));
@@ -106,42 +123,88 @@ class RoleAuthorizationsCacheTest
// New entries fetched during refreshes
assertThat(cache.getAuthorizations("test_role2").size()).isOne();
+ CacheStats afterRefreshStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(afterRefreshStats.hitCount()).isZero();
+ assertThat(afterRefreshStats.missCount()).isOne();
assertThat(cache.getAll().size()).isOne();
+ assertThat(afterRefreshStats.evictionCount()).isOne();
+
+ assertThat(cache.getAuthorizations("test_role2").size()).isOne();
+ CacheStats validEntryStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(validEntryStats.hitCount()).isOne();
+ assertThat(validEntryStats.missCount()).isZero();
+
+ // check for not existing role
+ cache.getAuthorizations("non_existing_role");
+ CacheStats afterMissStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(afterMissStats.missCount()).isEqualTo(0);
+ // It is a hit, since we load entire role_permissions table during
each refresh
+ assertThat(afterMissStats.hitCount()).isOne();
}
@Test
- void testNotFoundUser()
+ void testMultipleLoadCacheStats()
{
SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
- Map<String, Set<Authorization>> cassandraAuthorizations = new
HashMap<>();
- cassandraAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(CassandraPermissions.SELECT.toAuthorization())));
-
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(cassandraAuthorizations);
+ when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(new
HashMap<>());
Map<String, Set<Authorization>> sidecarAuthorizations = new
HashMap<>();
sidecarAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(BasicPermissions.CREATE_SNAPSHOT.toAuthorization())));
SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
when(mockSidecarPermissionsAccessor.rolesToAuthorizations()).thenReturn(sidecarAuthorizations);
+ // high cache expire time to test load stats
+ SidecarConfiguration mockConfig = mockConfig(mockCacheConfig(false,
true, "5m"));
+ RoleAuthorizationsCache cache = new RoleAuthorizationsCache(vertx,
+
executorPools,
+ mockConfig,
+
mockSidecarSchema,
+
mockDbAccessor,
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
+
+ assertThat(cache.getAuthorizations("test_role1").size()).isEqualTo(1);
+ assertThat(cache.getAuthorizations("test_role1").size()).isEqualTo(1);
+ assertThat(cache.getAuthorizations("test_role1").size()).isEqualTo(1);
+ assertThat(cache.getAuthorizations("test_role1").size()).isEqualTo(1);
+ assertThat(cache.getAuthorizations("test_role1").size()).isEqualTo(1);
+
+ CacheStats multipleRetrievalStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(multipleRetrievalStats.hitCount()).isEqualTo(4);
+ assertThat(multipleRetrievalStats.loadSuccessCount()).isEqualTo(1);
+ assertThat(multipleRetrievalStats.loadFailureCount()).isEqualTo(0);
+ assertThat(multipleRetrievalStats.missCount()).isEqualTo(1);
+ assertThat(multipleRetrievalStats.loadCount()).isEqualTo(1);
+ }
+
+ @Test
+ void testNotFoundUser()
+ {
+ SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
+ Map<String, Set<Authorization>> cassandraAuthorizations =
cassandraAuthorizations();
+
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(cassandraAuthorizations);
+ Map<String, Set<Authorization>> sidecarAuthorizations =
sidecarAuthorizations();
+ SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
+
when(mockSidecarPermissionsAccessor.rolesToAuthorizations()).thenReturn(sidecarAuthorizations);
SidecarConfiguration mockConfig = mockConfig();
RoleAuthorizationsCache cache = new RoleAuthorizationsCache(vertx,
executorPools,
mockConfig,
mockSidecarSchema,
mockDbAccessor,
-
mockSidecarPermissionsAccessor);
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
cache.warmUp(5);
// New entries fetched during refreshes
assertThat(cache.getAll().size()).isOne();
- assertThat(cache.getAuthorizations("test_role2")).isNull();
+ assertThat(cache.getAuthorizations("not_found_user")).isNull();
}
@Test
void testBulkload() throws InterruptedException
{
- Map<String, Set<Authorization>> sidecarAuthorizations = new
HashMap<>();
- sidecarAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(BasicPermissions.CREATE_SNAPSHOT.toAuthorization())));
- sidecarAuthorizations.put("test_role2", new
HashSet<>(Collections.singletonList(BasicPermissions.STREAM_SNAPSHOT.toAuthorization())));
+ Map<String, Set<Authorization>> sidecarAuthorizations =
sidecarAuthorizations();
SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(sidecarAuthorizations);
SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
@@ -151,7 +214,8 @@ class RoleAuthorizationsCacheTest
mockConfig,
mockSidecarSchema,
mockDbAccessor,
-
mockSidecarPermissionsAccessor);
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
// warming cache
@@ -167,9 +231,7 @@ class RoleAuthorizationsCacheTest
@Test
void testCacheDisabled()
{
- Map<String, Set<Authorization>> sidecarAuthorizations = new
HashMap<>();
- sidecarAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(BasicPermissions.CREATE_SNAPSHOT.toAuthorization())));
- sidecarAuthorizations.put("test_role2", new
HashSet<>(Collections.singletonList(BasicPermissions.STREAM_SNAPSHOT.toAuthorization())));
+ Map<String, Set<Authorization>> sidecarAuthorizations =
sidecarAuthorizations();
SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(sidecarAuthorizations);
SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
@@ -184,7 +246,8 @@ class RoleAuthorizationsCacheTest
mockConfig,
mockSidecarSchema,
mockDbAccessor,
-
mockSidecarPermissionsAccessor);
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
assertThat(cache.getAuthorizations("test_role1").size()).isOne();
assertThat(cache.getAuthorizations("test_role2").size()).isOne();
}
@@ -201,7 +264,8 @@ class RoleAuthorizationsCacheTest
mockConfig,
mockSidecarSchema,
mockDbAccessor,
-
mockSidecarPermissionsAccessor);
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
// warming cache
@@ -216,13 +280,10 @@ class RoleAuthorizationsCacheTest
@Test
void testSidecarPermissionsNotAddedWhenSchemaDisabled()
{
- Map<String, Set<Authorization>> cassandraAuthorizations = new
HashMap<>();
- cassandraAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(CassandraPermissions.SELECT.toAuthorization())));
- cassandraAuthorizations.put("test_role2", new
HashSet<>(Collections.singletonList(CassandraPermissions.CREATE.toAuthorization())));
+ Map<String, Set<Authorization>> cassandraAuthorizations =
cassandraAuthorizations();
SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(cassandraAuthorizations);
- Map<String, Set<Authorization>> sidecarAuthorizations = new
HashMap<>();
- sidecarAuthorizations.put("test_role3", new
HashSet<>(Collections.singletonList(BasicPermissions.CREATE_SNAPSHOT.toAuthorization())));
+ Map<String, Set<Authorization>> sidecarAuthorizations =
sidecarAuthorizations();
SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
when(mockSidecarPermissionsAccessor.rolesToAuthorizations()).thenReturn(sidecarAuthorizations);
SidecarConfiguration mockConfig = mockConfig();
@@ -233,7 +294,8 @@ class RoleAuthorizationsCacheTest
mockConfig,
mockSidecarSchema,
mockDbAccessor,
-
mockSidecarPermissionsAccessor);
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
// force warmup of cache
@@ -245,7 +307,135 @@ class RoleAuthorizationsCacheTest
assertThat(cache.get("unique_cache_entry_key").get("test_role3")).isNull();
}
+ @Test
+ void testCacheLoadTime()
+ {
+ Map<String, Set<Authorization>> cassandraAuthorizations =
cassandraAuthorizations();
+ SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
+
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(cassandraAuthorizations);
+ SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
+
+ SidecarConfiguration mockConfig = mockConfig();
+ RoleAuthorizationsCache cache = new RoleAuthorizationsCache(vertx,
+
executorPools,
+ mockConfig,
+
mockSidecarSchema,
+
mockDbAccessor,
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
+
+ CacheStats initialStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(initialStats.loadCount()).isZero();
+ assertThat(initialStats.totalLoadTime()).isZero();
+
+ cache.getAuthorizations("test_role1");
+
+ CacheStats afterLoadStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(afterLoadStats.loadCount()).isOne();
+ assertThat(afterLoadStats.totalLoadTime()).isGreaterThan(0);
+
+ double averageLoadTime = afterLoadStats.averageLoadPenalty();
+ assertThat(averageLoadTime).isGreaterThan(0);
+ }
+
+ @Test
+ void testCacheLoadFailureStats()
+ {
+ SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
+ when(mockDbAccessor.findAllRolesAndPermissions()).thenThrow(new
RuntimeException("Database connection failed"));
+ SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
+
+ SidecarConfiguration mockConfig = mockConfig();
+ RoleAuthorizationsCache cache = new RoleAuthorizationsCache(vertx,
+
executorPools,
+ mockConfig,
+
mockSidecarSchema,
+
mockDbAccessor,
+
mockSidecarPermissionsAccessor,
+
sidecarMetrics);
+
+ CacheStats initialStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(initialStats.loadFailureCount()).isZero();
+
+ try
+ {
+ cache.getAuthorizations("test_role1");
+ }
+ catch (Exception e)
+ {
+ // ignore exception
+ }
+
+ CacheStats afterFailureStats =
sidecarMetrics.server().cache().rolePermissionsCacheMetrics.snapshot();
+ assertThat(afterFailureStats.loadFailureCount()).isEqualTo(1);
+ assertThat(afterFailureStats.loadCount()).isEqualTo(1);
+ assertThat(afterFailureStats.loadSuccessCount()).isEqualTo(0);
+ }
+
+ @Test
+ void testCacheWithInvalidCacheConfig()
+ {
+ Map<String, Set<Authorization>> cassandraAuthorizations =
cassandraAuthorizations();
+ SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
+
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(cassandraAuthorizations);
+ SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
+
+ // Configure cache with both expireAfterAccess and refreshAfterWrite
as null
+ SidecarConfiguration mockConfig = mockConfig(mockCacheConfig(false,
false, "1s"));
+
+ assertThatThrownBy(
+ () -> new RoleAuthorizationsCache(vertx,
+ executorPools,
+ mockConfig,
+ mockSidecarSchema,
+ mockDbAccessor,
+ mockSidecarPermissionsAccessor,
+ sidecarMetrics))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("role_permissions_cache must be configured with
either refreshAfterWrite or expireAfterAccess");
+ }
+
+ @Test
+ void testCacheWithOnlyExpireAfterAccess()
+ {
+ Map<String, Set<Authorization>> cassandraAuthorizations =
cassandraAuthorizations();
+ SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
+
when(mockDbAccessor.findAllRolesAndPermissions()).thenReturn(cassandraAuthorizations);
+ SidecarPermissionsDatabaseAccessor mockSidecarPermissionsAccessor =
mock(SidecarPermissionsDatabaseAccessor.class);
+
+ // Configure cache with only expireAfterAccess
+ CacheConfiguration mockCacheConfig = mockCacheConfig(true, false,
"1s");
+ SidecarConfiguration mockConfig = mockConfig(mockCacheConfig);
+
+ RoleAuthorizationsCache authorizationsCache
+ = new RoleAuthorizationsCache(vertx, executorPools, mockConfig,
mockSidecarSchema, mockDbAccessor,
+ mockSidecarPermissionsAccessor,
sidecarMetrics);
+ assertThat(authorizationsCache).isNotNull();
+ }
+
+ private CacheConfiguration mockCacheConfig()
+ {
+ return mockCacheConfig(true, true, "1s");
+ }
+
+ private CacheConfiguration mockCacheConfig(boolean setExpire, boolean
setRefresh, String time)
+ {
+ CacheConfiguration mockCacheConfig = mock(CacheConfiguration.class);
+ when(mockCacheConfig.enabled()).thenReturn(true);
+ when(mockCacheConfig.expireAfterAccess()).thenReturn(setExpire ?
MillisecondBoundConfiguration.parse("1s") : null);
+ when(mockCacheConfig.refreshAfterWrite()).thenReturn(setRefresh ?
MillisecondBoundConfiguration.parse("1s") : null);
+ when(mockCacheConfig.maximumSize()).thenReturn(10L);
+ when(mockCacheConfig.warmupRetries()).thenReturn(5);
+
when(mockCacheConfig.warmupRetryInterval()).thenReturn(MillisecondBoundConfiguration.parse("1s"));
+ return mockCacheConfig;
+ }
+
private SidecarConfiguration mockConfig()
+ {
+ return mockConfig(mockCacheConfig());
+ }
+
+ private SidecarConfiguration mockConfig(CacheConfiguration
cacheConfiguration)
{
SidecarConfiguration mockConfig = mock(SidecarConfiguration.class);
ServiceConfiguration mockServiceConfig =
mock(ServiceConfiguration.class);
@@ -255,13 +445,23 @@ class RoleAuthorizationsCacheTest
when(mockConfig.serviceConfiguration()).thenReturn(mockServiceConfig);
AccessControlConfiguration mockAccessControlConfig =
mock(AccessControlConfiguration.class);
when(mockConfig.accessControlConfiguration()).thenReturn(mockAccessControlConfig);
- CacheConfiguration mockCacheConfig = mock(CacheConfiguration.class);
- when(mockCacheConfig.enabled()).thenReturn(true);
-
when(mockCacheConfig.expireAfterAccess()).thenReturn(MillisecondBoundConfiguration.parse("1s"));
- when(mockCacheConfig.maximumSize()).thenReturn(10L);
- when(mockCacheConfig.warmupRetries()).thenReturn(5);
-
when(mockCacheConfig.expireAfterAccess()).thenReturn(MillisecondBoundConfiguration.parse("1s"));
-
when(mockAccessControlConfig.permissionCacheConfiguration()).thenReturn(mockCacheConfig);
+
when(mockAccessControlConfig.permissionCacheConfiguration()).thenReturn(cacheConfiguration);
return mockConfig;
}
+
+ private Map<String, Set<Authorization>> sidecarAuthorizations()
+ {
+ Map<String, Set<Authorization>> sidecarAuthorizations = new
HashMap<>();
+ sidecarAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(BasicPermissions.CREATE_SNAPSHOT.toAuthorization())));
+ sidecarAuthorizations.put("test_role2", new
HashSet<>(Collections.singletonList(BasicPermissions.STREAM_SNAPSHOT.toAuthorization())));
+ return sidecarAuthorizations;
+ }
+
+ private Map<String, Set<Authorization>> cassandraAuthorizations()
+ {
+ Map<String, Set<Authorization>> cassandraAuthorizations = new
HashMap<>();
+ cassandraAuthorizations.put("test_role1", new
HashSet<>(Collections.singletonList(CassandraPermissions.SELECT.toAuthorization())));
+ cassandraAuthorizations.put("test_role2", new
HashSet<>(Collections.singletonList(CassandraPermissions.CREATE.toAuthorization())));
+ return cassandraAuthorizations;
+ }
}
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTLSAuthenticationHandlerTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTLSAuthenticationHandlerTest.java
index 360d6046..79d2cd2b 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTLSAuthenticationHandlerTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTLSAuthenticationHandlerTest.java
@@ -66,6 +66,7 @@ import io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import org.apache.cassandra.sidecar.TestModule;
+import
org.apache.cassandra.sidecar.common.server.utils.MillisecondBoundConfiguration;
import
org.apache.cassandra.sidecar.common.server.utils.SecondBoundConfiguration;
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
import org.apache.cassandra.sidecar.config.ParameterizedClassConfiguration;
@@ -386,7 +387,7 @@ class MutualTLSAuthenticationHandlerTest
authenticatorsConfiguration(),
new
ParameterizedClassConfigurationImpl(className, Collections.emptyMap()),
Collections.singleton(ADMIN_IDENTITY),
- new CacheConfigurationImpl());
+ new
CacheConfigurationImpl(MillisecondBoundConfiguration.parse("30s"), 100));
return super.abstractConfig(sslConfiguration, builder ->
builder.accessControlConfiguration(accessControlConfiguration));
}
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTlsAuthenticationHandlerFactoryTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTlsAuthenticationHandlerFactoryTest.java
index 66e567da..e88078b1 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTlsAuthenticationHandlerFactoryTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTlsAuthenticationHandlerFactoryTest.java
@@ -38,6 +38,9 @@ import org.apache.cassandra.sidecar.config.CacheConfiguration;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
+import org.apache.cassandra.sidecar.metrics.SidecarMetricsImpl;
import org.apache.cassandra.sidecar.metrics.server.AuthMetrics;
import static
org.apache.cassandra.sidecar.ExecutorPoolsHelper.createdSharedTestPool;
@@ -52,14 +55,20 @@ import static org.mockito.Mockito.when;
*/
class MutualTlsAuthenticationHandlerFactoryTest
{
+ private static final MetricRegistryFactory FACTORY
+ = new
MetricRegistryFactory(MutualTlsAuthenticationHandlerFactoryTest.class.getName(),
+ Collections.emptyList(),
+ Collections.emptyList());
Vertx vertx;
ExecutorPools executorPools;
+ SidecarMetrics sidecarMetrics;
@BeforeEach
void setup()
{
vertx = Vertx.vertx();
executorPools = createdSharedTestPool(vertx);
+ sidecarMetrics = new SidecarMetricsImpl(FACTORY, null);
}
@AfterEach
@@ -123,9 +132,9 @@ class MutualTlsAuthenticationHandlerFactoryTest
SystemAuthDatabaseAccessor mockAccessor)
{
IdentityToRoleCache identityToRoleCache
- = new IdentityToRoleCache(vertx, executorPools, mockSidecarConfig,
mockAccessor);
+ = new IdentityToRoleCache(vertx, executorPools, mockSidecarConfig,
mockAccessor, sidecarMetrics);
SuperUserCache superUserCache
- = new SuperUserCache(vertx, executorPools, mockSidecarConfig,
mockAccessor);
+ = new SuperUserCache(vertx, executorPools, mockSidecarConfig,
mockAccessor, sidecarMetrics);
AdminIdentityResolver adminIdentityResolver
= new AdminIdentityResolver(identityToRoleCache, superUserCache,
mockSidecarConfig);
return new MutualTlsAuthenticationHandlerFactory(identityToRoleCache,
adminIdentityResolver);
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCacheTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCacheTest.java
index 9d4730c0..9410299c 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCacheTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/SuperUserCacheTest.java
@@ -36,6 +36,9 @@ import
org.apache.cassandra.sidecar.config.AccessControlConfiguration;
import org.apache.cassandra.sidecar.config.CacheConfiguration;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
+import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory;
+import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
+import org.apache.cassandra.sidecar.metrics.SidecarMetricsImpl;
import static
org.apache.cassandra.sidecar.ExecutorPoolsHelper.createdSharedTestPool;
import static
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SIDECAR_SCHEMA_INITIALIZED;
@@ -49,14 +52,20 @@ import static org.mockito.Mockito.when;
*/
class SuperUserCacheTest
{
+ private static final MetricRegistryFactory FACTORY
+ = new MetricRegistryFactory(SuperUserCacheTest.class.getName(),
+ Collections.emptyList(),
+ Collections.emptyList());
Vertx vertx;
ExecutorPools executorPools;
+ SidecarMetrics sidecarMetrics;
@BeforeEach
void setup()
{
vertx = Vertx.vertx();
executorPools = createdSharedTestPool(vertx);
+ sidecarMetrics = new SidecarMetricsImpl(FACTORY, null);
}
@AfterEach
@@ -72,7 +81,7 @@ class SuperUserCacheTest
when(mockDbAccessor.findAllRolesToSuperuserStatus()).thenReturn(ImmutableMap.of("test_role1",
true,
"test_role2", false));
SidecarConfiguration mockConfig = mockConfig();
- SuperUserCache cache = new SuperUserCache(vertx, executorPools,
mockConfig, mockDbAccessor);
+ SuperUserCache cache = new SuperUserCache(vertx, executorPools,
mockConfig, mockDbAccessor, sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
// warming cache
@@ -97,7 +106,7 @@ class SuperUserCacheTest
when(mockDbAccessor.findAllRolesToSuperuserStatus()).thenReturn(superUserMap);
SidecarConfiguration mockConfig = mockConfig();
when(mockConfig.accessControlConfiguration().permissionCacheConfiguration().enabled()).thenReturn(false);
- SuperUserCache superUserCache = new SuperUserCache(vertx,
executorPools, mockConfig, mockDbAccessor);
+ SuperUserCache superUserCache = new SuperUserCache(vertx,
executorPools, mockConfig, mockDbAccessor, sidecarMetrics);
assertThat(superUserCache.get("test_role")).isTrue();
assertThat(superUserCache.isSuperUser("test_role")).isTrue();
assertThat(superUserCache.getAll().size()).isEqualTo(2);
@@ -109,7 +118,7 @@ class SuperUserCacheTest
SystemAuthDatabaseAccessor mockDbAccessor =
mock(SystemAuthDatabaseAccessor.class);
when(mockDbAccessor.findAllRolesToSuperuserStatus()).thenReturn(Collections.emptyMap());
SidecarConfiguration mockConfig = mockConfig();
- SuperUserCache cache = new SuperUserCache(vertx, executorPools,
mockConfig, mockDbAccessor);
+ SuperUserCache cache = new SuperUserCache(vertx, executorPools,
mockConfig, mockDbAccessor, sidecarMetrics);
assertThat(cache.getAll().size()).isZero();
// warming cache
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
index a16da2b3..45e763b9 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
@@ -418,6 +418,7 @@ class SidecarConfigurationTest
assertThat(permissionCacheConfiguration.enabled()).isTrue();
assertThat(permissionCacheConfiguration.expireAfterAccess().quantity()).isEqualTo(5);
assertThat(permissionCacheConfiguration.expireAfterAccess().unit()).isEqualTo(TimeUnit.MINUTES);
+ assertThat(permissionCacheConfiguration.refreshAfterWrite()).isNull();
assertThat(permissionCacheConfiguration.maximumSize()).isEqualTo(1000);
assertThat(permissionCacheConfiguration.warmupRetries()).isEqualTo(5);
assertThat(permissionCacheConfiguration.warmupRetryInterval().quantity()).isEqualTo(2);
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImplTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImplTest.java
new file mode 100644
index 00000000..ff16f9cd
--- /dev/null
+++
b/server/src/test/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImplTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import
org.apache.cassandra.sidecar.common.server.utils.MillisecondBoundConfiguration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for {@link CacheConfigurationImpl}
+ */
+class CacheConfigurationImplTest
+{
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @Test
+ void testDefaultConstructor()
+ {
+ CacheConfigurationImpl config = new CacheConfigurationImpl();
+
+ assertThat(config.expireAfterAccess()).isNull();
+ assertThat(config.refreshAfterWrite()).isNull();
+ assertThat(config.maximumSize()).isEqualTo(100);
+ assertThat(config.enabled()).isTrue();
+ assertThat(config.warmupRetries()).isEqualTo(5);
+
assertThat(config.warmupRetryInterval()).isEqualTo(MillisecondBoundConfiguration.parse("1s"));
+ }
+
+ @Test
+ void testConstructorWithParameters()
+ {
+ MillisecondBoundConfiguration expireAfterAccess =
MillisecondBoundConfiguration.parse("30m");
+ MillisecondBoundConfiguration refreshAfterWrite =
MillisecondBoundConfiguration.parse("5m");
+ MillisecondBoundConfiguration warmupRetryInterval =
MillisecondBoundConfiguration.parse("2s");
+
+ CacheConfigurationImpl config
+ = new CacheConfigurationImpl(expireAfterAccess, refreshAfterWrite,
1000, false,
+ 10, warmupRetryInterval);
+
+ assertThat(config.expireAfterAccess()).isEqualTo(expireAfterAccess);
+ assertThat(config.refreshAfterWrite()).isEqualTo(refreshAfterWrite);
+ assertThat(config.maximumSize()).isEqualTo(1000);
+ assertThat(config.enabled()).isFalse();
+ assertThat(config.warmupRetries()).isEqualTo(10);
+
assertThat(config.warmupRetryInterval()).isEqualTo(warmupRetryInterval);
+ }
+
+ @Test
+ void testBuilderWithDefaults()
+ {
+ CacheConfigurationImpl config = new
CacheConfigurationImpl(MillisecondBoundConfiguration.parse("30m"), 100);
+
+
assertThat(config.expireAfterAccess()).isEqualTo(MillisecondBoundConfiguration.parse("30m"));
+
assertThat(config.refreshAfterWrite()).isEqualTo(MillisecondBoundConfiguration.parse("1h"));
+ assertThat(config.maximumSize()).isEqualTo(100);
+ assertThat(config.enabled()).isTrue();
+ assertThat(config.warmupRetries()).isEqualTo(5);
+
assertThat(config.warmupRetryInterval()).isEqualTo(MillisecondBoundConfiguration.parse("1s"));
+ }
+
+ @Test
+ void testSerializationWithOnlyRefreshAfterWrite() throws Exception
+ {
+ String jsonString = "{" +
+ "\"enabled\": \"true\"," +
+ "\"refresh_after_write\": \"5m\"," +
+ "\"maximum_size\": 1000," +
+ "\"warmup_retries\": 10," +
+ "\"warmup_retry_interval\": \"2s\"" +
+ "}";
+
+ CacheConfigurationImpl config = MAPPER.readValue(jsonString,
CacheConfigurationImpl.class);
+
+ assertThat(config.enabled()).isTrue();
+ assertThat(config.expireAfterAccess()).isNull();
+
assertThat(config.refreshAfterWrite()).isEqualTo(MillisecondBoundConfiguration.parse("5m"));
+ assertThat(config.maximumSize()).isEqualTo(1000);
+ assertThat(config.warmupRetries()).isEqualTo(10);
+
assertThat(config.warmupRetryInterval()).isEqualTo(MillisecondBoundConfiguration.parse("2s"));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]