mirror of https://github.com/apache/cassandra
Make AuthCache easier to subclass
Patch by Kurt Greaves; reviewed by Sam Tunnicliffe for CASSANDRA-14662
This commit is contained in:
parent
cc12665bb7
commit
960174da67
|
|
@ -1,4 +1,5 @@
|
|||
4.0
|
||||
* Make AuthCache more easily extendable (CASSANDRA-14662)
|
||||
* Extend RolesCache to include detailed role info (CASSANDRA-14497)
|
||||
* Add fqltool compare (CASSANDRA-14619)
|
||||
* Add fqltool replay (CASSANDRA-14618)
|
||||
|
|
|
|||
|
|
@ -35,24 +35,40 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AuthCache<K, V> implements AuthCacheMBean
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class AuthCache<K, V> implements AuthCacheMBean<K>
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthCache.class);
|
||||
|
||||
private static final String MBEAN_NAME_BASE = "org.apache.cassandra.auth:type=";
|
||||
|
||||
private volatile LoadingCache<K, V> cache;
|
||||
/**
|
||||
* Underlying cache. LoadingCache will call underlying load function on {@link #get} if key is not present
|
||||
*/
|
||||
protected volatile LoadingCache<K, V> cache;
|
||||
|
||||
private final String name;
|
||||
private final IntConsumer setValidityDelegate;
|
||||
private final IntSupplier getValidityDelegate;
|
||||
private final IntConsumer setUpdateIntervalDelegate;
|
||||
private final IntSupplier getUpdateIntervalDelegate;
|
||||
private final IntConsumer setMaxEntriesDelegate;
|
||||
private final IntSupplier getMaxEntriesDelegate;
|
||||
private final Function<K, V> loadFunction;
|
||||
private final BooleanSupplier enableCache;
|
||||
private String name;
|
||||
private IntConsumer setValidityDelegate;
|
||||
private IntSupplier getValidityDelegate;
|
||||
private IntConsumer setUpdateIntervalDelegate;
|
||||
private IntSupplier getUpdateIntervalDelegate;
|
||||
private IntConsumer setMaxEntriesDelegate;
|
||||
private IntSupplier getMaxEntriesDelegate;
|
||||
private Function<K, V> loadFunction;
|
||||
private BooleanSupplier enableCache;
|
||||
|
||||
/**
|
||||
* @param name Used for MBean
|
||||
* @param setValidityDelegate Used to set cache validity period. See {@link Policy#expireAfterWrite()}
|
||||
* @param getValidityDelegate Getter for validity period
|
||||
* @param setUpdateIntervalDelegate Used to set cache update interval. See {@link Policy#refreshAfterWrite()}
|
||||
* @param getUpdateIntervalDelegate Getter for update interval
|
||||
* @param setMaxEntriesDelegate Used to set max # entries in cache. See {@link com.github.benmanes.caffeine.cache.Policy.Eviction#setMaximum(long)}
|
||||
* @param getMaxEntriesDelegate Getter for max entries.
|
||||
* @param loadFunction Function to load the cache. Called on {@link #get(Object)}
|
||||
* @param cacheEnabledDelegate Used to determine if cache is enabled.
|
||||
*/
|
||||
protected AuthCache(String name,
|
||||
IntConsumer setValidityDelegate,
|
||||
IntSupplier getValidityDelegate,
|
||||
|
|
@ -61,23 +77,26 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
IntConsumer setMaxEntriesDelegate,
|
||||
IntSupplier getMaxEntriesDelegate,
|
||||
Function<K, V> loadFunction,
|
||||
BooleanSupplier enableCache)
|
||||
BooleanSupplier cacheEnabledDelegate)
|
||||
{
|
||||
this.name = name;
|
||||
this.setValidityDelegate = setValidityDelegate;
|
||||
this.getValidityDelegate = getValidityDelegate;
|
||||
this.setUpdateIntervalDelegate = setUpdateIntervalDelegate;
|
||||
this.getUpdateIntervalDelegate = getUpdateIntervalDelegate;
|
||||
this.setMaxEntriesDelegate = setMaxEntriesDelegate;
|
||||
this.getMaxEntriesDelegate = getMaxEntriesDelegate;
|
||||
this.loadFunction = loadFunction;
|
||||
this.enableCache = enableCache;
|
||||
this.name = checkNotNull(name);
|
||||
this.setValidityDelegate = checkNotNull(setValidityDelegate);
|
||||
this.getValidityDelegate = checkNotNull(getValidityDelegate);
|
||||
this.setUpdateIntervalDelegate = checkNotNull(setUpdateIntervalDelegate);
|
||||
this.getUpdateIntervalDelegate = checkNotNull(getUpdateIntervalDelegate);
|
||||
this.setMaxEntriesDelegate = checkNotNull(setMaxEntriesDelegate);
|
||||
this.getMaxEntriesDelegate = checkNotNull(getMaxEntriesDelegate);
|
||||
this.loadFunction = checkNotNull(loadFunction);
|
||||
this.enableCache = checkNotNull(cacheEnabledDelegate);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do setup for the cache and MBean.
|
||||
*/
|
||||
protected void init()
|
||||
{
|
||||
this.cache = initCache(null);
|
||||
cache = initCache(null);
|
||||
try
|
||||
{
|
||||
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
|
||||
|
|
@ -107,6 +126,14 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
return new ObjectName(MBEAN_NAME_BASE + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from the cache. Will call {@link LoadingCache#get(Object)} which will
|
||||
* "load" the value if it's not present, thus populating the key.
|
||||
* @param k
|
||||
* @return The current value of {@code K} if cached or loaded.
|
||||
*
|
||||
* See {@link LoadingCache#get(Object)} for possible exceptions.
|
||||
*/
|
||||
public V get(K k)
|
||||
{
|
||||
if (cache == null)
|
||||
|
|
@ -115,17 +142,28 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
return cache.get(k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the entire cache.
|
||||
*/
|
||||
public void invalidate()
|
||||
{
|
||||
cache = initCache(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate a key
|
||||
* @param k key to invalidate
|
||||
*/
|
||||
public void invalidate(K k)
|
||||
{
|
||||
if (cache != null)
|
||||
cache.invalidate(k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time in milliseconds that a value in the cache will expire after.
|
||||
* @param validityPeriod in milliseconds
|
||||
*/
|
||||
public void setValidity(int validityPeriod)
|
||||
{
|
||||
if (Boolean.getBoolean("cassandra.disable_auth_caches_remote_configuration"))
|
||||
|
|
@ -140,6 +178,10 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
return getValidityDelegate.getAsInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Time in milliseconds after which an entry in the cache should be refreshed (it's load function called again)
|
||||
* @param updateInterval in milliseconds
|
||||
*/
|
||||
public void setUpdateInterval(int updateInterval)
|
||||
{
|
||||
if (Boolean.getBoolean("cassandra.disable_auth_caches_remote_configuration"))
|
||||
|
|
@ -154,6 +196,10 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
return getUpdateIntervalDelegate.getAsInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum number of entries in the cache.
|
||||
* @param maxEntries
|
||||
*/
|
||||
public void setMaxEntries(int maxEntries)
|
||||
{
|
||||
if (Boolean.getBoolean("cassandra.disable_auth_caches_remote_configuration"))
|
||||
|
|
@ -168,7 +214,14 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
return getMaxEntriesDelegate.getAsInt();
|
||||
}
|
||||
|
||||
private LoadingCache<K, V> initCache(LoadingCache<K, V> existing)
|
||||
/**
|
||||
* (Re-)initialise the underlying cache. Will update validity, max entries, and update interval if
|
||||
* any have changed. The underlying {@link LoadingCache} will be initiated based on the provided {@code loadFunction}.
|
||||
* Note: If you need some unhandled cache setting to be set you should extend {@link AuthCache} and override this method.
|
||||
* @param existing If not null will only update cache update validity, max entries, and update interval.
|
||||
* @return New {@link LoadingCache} if existing was null, otherwise the existing {@code cache}
|
||||
*/
|
||||
protected LoadingCache<K, V> initCache(LoadingCache<K, V> existing)
|
||||
{
|
||||
if (!enableCache.getAsBoolean())
|
||||
return null;
|
||||
|
|
@ -181,14 +234,14 @@ public class AuthCache<K, V> implements AuthCacheMBean
|
|||
|
||||
if (existing == null) {
|
||||
return Caffeine.newBuilder()
|
||||
.refreshAfterWrite(getUpdateInterval(), TimeUnit.MILLISECONDS)
|
||||
.expireAfterWrite(getValidity(), TimeUnit.MILLISECONDS)
|
||||
.maximumSize(getMaxEntries())
|
||||
.executor(MoreExecutors.directExecutor())
|
||||
.build(loadFunction::apply);
|
||||
.refreshAfterWrite(getUpdateInterval(), TimeUnit.MILLISECONDS)
|
||||
.expireAfterWrite(getValidity(), TimeUnit.MILLISECONDS)
|
||||
.maximumSize(getMaxEntries())
|
||||
.executor(MoreExecutors.directExecutor())
|
||||
.build(loadFunction::apply);
|
||||
}
|
||||
|
||||
// Always set as manditory
|
||||
// Always set as mandatory
|
||||
cache.policy().refreshAfterWrite().ifPresent(policy ->
|
||||
policy.setExpiresAfter(getUpdateInterval(), TimeUnit.MILLISECONDS));
|
||||
cache.policy().expireAfterWrite().ifPresent(policy ->
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@
|
|||
|
||||
package org.apache.cassandra.auth;
|
||||
|
||||
public interface AuthCacheMBean
|
||||
public interface AuthCacheMBean<T>
|
||||
{
|
||||
public void invalidate();
|
||||
|
||||
public void invalidate(T t);
|
||||
|
||||
public void setValidity(int validityPeriod);
|
||||
|
||||
public int getValidity();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ package org.apache.cassandra.auth;
|
|||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
||||
public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions> implements AuthCacheMBean
|
||||
public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions>
|
||||
{
|
||||
public NetworkAuthCache(INetworkAuthorizer authorizer)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ public class PasswordAuthenticator implements IAuthenticator
|
|||
}
|
||||
}
|
||||
|
||||
private static class CredentialsCache extends AuthCache<String, String> implements CredentialsCacheMBean
|
||||
private static class CredentialsCache extends AuthCache<String, String>
|
||||
{
|
||||
private CredentialsCache(PasswordAuthenticator authenticator)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.Set;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class PermissionsCache extends AuthCache<Pair<AuthenticatedUser, IResource>, Set<Permission>> implements PermissionsCacheMBean
|
||||
public class PermissionsCache extends AuthCache<Pair<AuthenticatedUser, IResource>, Set<Permission>>
|
||||
{
|
||||
public PermissionsCache(IAuthorizer authorizer)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* 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.auth;
|
||||
|
||||
/**
|
||||
* Retained since CASSANDRA-7715 for backwards compatibility of MBean interface
|
||||
* classes. This should be removed in the next major version (4.0)
|
||||
*/
|
||||
public interface PermissionsCacheMBean extends AuthCacheMBean
|
||||
{
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
||||
public class RolesCache extends AuthCache<RoleResource, Set<Role>> implements RolesCacheMBean
|
||||
public class RolesCache extends AuthCache<RoleResource, Set<Role>>
|
||||
{
|
||||
public RolesCache(IRoleManager roleManager, BooleanSupplier enableCache)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* 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.auth;
|
||||
|
||||
/**
|
||||
* Retained since CASSANDRA-7715 for backwards compatibility of MBean interface
|
||||
* classes. This should be removed in the next major version (4.0)
|
||||
*/
|
||||
public interface RolesCacheMBean extends AuthCacheMBean
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
/*
|
||||
* 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.auth;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class AuthCacheTest
|
||||
{
|
||||
private boolean loadFuncCalled = false;
|
||||
private boolean isCacheEnabled = false;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup()
|
||||
{
|
||||
DatabaseDescriptor.daemonInitialization();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching()
|
||||
{
|
||||
AuthCache<String, String> authCache = new AuthCache<>("TestCache",
|
||||
DatabaseDescriptor::setCredentialsValidity,
|
||||
DatabaseDescriptor::getCredentialsValidity,
|
||||
DatabaseDescriptor::setCredentialsUpdateInterval,
|
||||
DatabaseDescriptor::getCredentialsUpdateInterval,
|
||||
DatabaseDescriptor::setCredentialsCacheMaxEntries,
|
||||
DatabaseDescriptor::getCredentialsCacheMaxEntries,
|
||||
this::load,
|
||||
() -> true
|
||||
);
|
||||
|
||||
// Test cacheloader is called if set
|
||||
loadFuncCalled = false;
|
||||
String result = authCache.get("test");
|
||||
assertTrue(loadFuncCalled);
|
||||
Assert.assertEquals("load", result);
|
||||
|
||||
// value should be fetched from cache
|
||||
loadFuncCalled = false;
|
||||
String result2 = authCache.get("test");
|
||||
assertFalse(loadFuncCalled);
|
||||
Assert.assertEquals("load", result2);
|
||||
|
||||
// value should be fetched from cache after complete invalidate
|
||||
authCache.invalidate();
|
||||
loadFuncCalled = false;
|
||||
String result3 = authCache.get("test");
|
||||
assertTrue(loadFuncCalled);
|
||||
Assert.assertEquals("load", result3);
|
||||
|
||||
// value should be fetched from cache after invalidating key
|
||||
authCache.invalidate("test");
|
||||
loadFuncCalled = false;
|
||||
String result4 = authCache.get("test");
|
||||
assertTrue(loadFuncCalled);
|
||||
Assert.assertEquals("load", result4);
|
||||
|
||||
// set cache to null and load function should be called
|
||||
loadFuncCalled = false;
|
||||
authCache.cache = null;
|
||||
String result5 = authCache.get("test");
|
||||
assertTrue(loadFuncCalled);
|
||||
Assert.assertEquals("load", result5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitCache()
|
||||
{
|
||||
// Test that a validity of <= 0 will turn off caching
|
||||
DatabaseDescriptor.setCredentialsValidity(0);
|
||||
AuthCache<String, String> authCache = new AuthCache<>("TestCache2",
|
||||
DatabaseDescriptor::setCredentialsValidity,
|
||||
DatabaseDescriptor::getCredentialsValidity,
|
||||
DatabaseDescriptor::setCredentialsUpdateInterval,
|
||||
DatabaseDescriptor::getCredentialsUpdateInterval,
|
||||
DatabaseDescriptor::setCredentialsCacheMaxEntries,
|
||||
DatabaseDescriptor::getCredentialsCacheMaxEntries,
|
||||
this::load,
|
||||
() -> true);
|
||||
assertNull(authCache.cache);
|
||||
authCache.setValidity(2000);
|
||||
authCache.cache = authCache.initCache(null);
|
||||
assertNotNull(authCache.cache);
|
||||
|
||||
// Test enableCache works as intended
|
||||
authCache = new AuthCache<>("TestCache3",
|
||||
DatabaseDescriptor::setCredentialsValidity,
|
||||
DatabaseDescriptor::getCredentialsValidity,
|
||||
DatabaseDescriptor::setCredentialsUpdateInterval,
|
||||
DatabaseDescriptor::getCredentialsUpdateInterval,
|
||||
DatabaseDescriptor::setCredentialsCacheMaxEntries,
|
||||
DatabaseDescriptor::getCredentialsCacheMaxEntries,
|
||||
this::load,
|
||||
() -> isCacheEnabled);
|
||||
assertNull(authCache.cache);
|
||||
isCacheEnabled = true;
|
||||
authCache.cache = authCache.initCache(null);
|
||||
assertNotNull(authCache.cache);
|
||||
|
||||
// Ensure at a minimum these policies have been initialised by default
|
||||
assertTrue(authCache.cache.policy().expireAfterWrite().isPresent());
|
||||
assertTrue(authCache.cache.policy().refreshAfterWrite().isPresent());
|
||||
assertTrue(authCache.cache.policy().eviction().isPresent());
|
||||
}
|
||||
|
||||
private String load(String test)
|
||||
{
|
||||
loadFuncCalled = true;
|
||||
return "load";
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue