This is an automated email from the ASF dual-hosted git repository.

samt pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new f9aa19e   Add nodetool commands to invalidate auth caches
f9aa19e is described below

commit f9aa19e3b116c0078019e9382d1a6c4bb050f113
Author: Aleksei Zotov <[email protected]>
AuthorDate: Thu Aug 12 14:52:31 2021 +0100

     Add nodetool commands to invalidate auth caches
    
     Patch by Aleksei Zotov; reviewed by Benjamin Lerer, Sumanth Pasupuleti
     and Sam Tunnicliffe for CASSANDRA-16404
---
 CHANGES.txt                                        |   1 +
 src/java/org/apache/cassandra/auth/AuthCache.java  |   2 +-
 .../apache/cassandra/auth/AuthenticatedUser.java   |   4 +-
 .../apache/cassandra/auth/INetworkAuthorizer.java  |   2 +-
 ...AuthCache.java => NetworkPermissionsCache.java} |  21 +-
 ...ache.java => NetworkPermissionsCacheMBean.java} |  21 +-
 .../cassandra/auth/PasswordAuthenticator.java      |  20 +-
 .../apache/cassandra/auth/PermissionsCache.java    |   8 +-
 ...rkAuthCache.java => PermissionsCacheMBean.java} |  19 +-
 src/java/org/apache/cassandra/auth/RolesCache.java |   9 +-
 ...{NetworkAuthCache.java => RolesCacheMBean.java} |  19 +-
 .../cassandra/auth/jmx/AuthorizationProxy.java     |  35 ++-
 src/java/org/apache/cassandra/tools/NodeProbe.java |  75 ++++-
 src/java/org/apache/cassandra/tools/NodeTool.java  |  31 +-
 .../tools/nodetool/InvalidateCredentialsCache.java |  49 ++++
 .../nodetool/InvalidateJmxPermissionsCache.java    |  48 ++++
 .../InvalidateNetworkPermissionsCache.java         |  49 ++++
 .../tools/nodetool/InvalidatePermissionsCache.java | 174 +++++++++++
 .../tools/nodetool/InvalidateRolesCache.java       |  50 ++++
 .../{RoleTestUtils.java => AuthTestUtils.java}     |  69 ++++-
 .../auth/CassandraNetworkAuthorizerTest.java       |  50 +---
 .../cassandra/auth/CassandraRoleManagerTest.java   |   6 +-
 test/unit/org/apache/cassandra/auth/RolesTest.java |  18 +-
 .../nodetool/InvalidateCredentialsCacheTest.java   | 171 +++++++++++
 .../InvalidateJmxPermissionsCacheTest.java         | 192 +++++++++++++
 .../InvalidateNetworkPermissionsCacheTest.java     | 160 +++++++++++
 .../nodetool/InvalidatePermissionsCacheTest.java   | 317 +++++++++++++++++++++
 .../tools/nodetool/InvalidateRolesCacheTest.java   | 159 +++++++++++
 28 files changed, 1630 insertions(+), 149 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 8555e51..a2a4043 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.1
+ * Provide a nodetool command to invalidate auth caches (CASSANDRA-16404)
  * Catch read repair timeout exceptions and add metric (CASSANDRA-16880)
  * Exclude Jackson 1.x transitive dependency of hadoop* provided dependencies 
(CASSANDRA-16854)
  * Add client warnings and abort to tombstone and coordinator reads which go 
past a low/high watermark (CASSANDRA-16850)
diff --git a/src/java/org/apache/cassandra/auth/AuthCache.java 
b/src/java/org/apache/cassandra/auth/AuthCache.java
index 6393da7..32e9f0f 100644
--- a/src/java/org/apache/cassandra/auth/AuthCache.java
+++ b/src/java/org/apache/cassandra/auth/AuthCache.java
@@ -38,7 +38,7 @@ public class AuthCache<K, V> implements AuthCacheMBean
 {
     private static final Logger logger = 
LoggerFactory.getLogger(AuthCache.class);
 
-    private static final String MBEAN_NAME_BASE = 
"org.apache.cassandra.auth:type=";
+    public static final String MBEAN_NAME_BASE = 
"org.apache.cassandra.auth:type=";
 
     /**
      * Underlying cache. LoadingCache will call underlying load function on 
{@link #get} if key is not present
diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java 
b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
index 9f22bea..c2d93ca 100644
--- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
+++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
@@ -40,7 +40,7 @@ public class AuthenticatedUser
 
     // User-level permissions cache.
     private static final PermissionsCache permissionsCache = new 
PermissionsCache(DatabaseDescriptor.getAuthorizer());
-    private static final NetworkAuthCache networkAuthCache = new 
NetworkAuthCache(DatabaseDescriptor.getNetworkAuthorizer());
+    private static final NetworkPermissionsCache networkPermissionsCache = new 
NetworkPermissionsCache(DatabaseDescriptor.getNetworkAuthorizer());
 
     private final String name;
     // primary Role of the logged in user
@@ -136,7 +136,7 @@ public class AuthenticatedUser
      */
     public boolean hasLocalAccess()
     {
-        return 
networkAuthCache.get(this.getPrimaryRole()).canAccess(Datacenters.thisDatacenter());
+        return 
networkPermissionsCache.get(this.getPrimaryRole()).canAccess(Datacenters.thisDatacenter());
     }
 
     @Override
diff --git a/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java 
b/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java
index 4582b5e..9a5a5d6 100644
--- a/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java
+++ b/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java
@@ -46,7 +46,7 @@ public interface INetworkAuthorizer
     void setRoleDatacenters(RoleResource role, DCPermissions permissions);
 
     /**
-     * Called when a role is deleted, so any corresponding network auth
+     * Called when a role is deleted, so any corresponding network permissions
      * data can also be cleaned up
      */
     void drop(RoleResource role);
diff --git a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java 
b/src/java/org/apache/cassandra/auth/NetworkPermissionsCache.java
similarity index 66%
copy from src/java/org/apache/cassandra/auth/NetworkAuthCache.java
copy to src/java/org/apache/cassandra/auth/NetworkPermissionsCache.java
index 6b3c74e..72817a9 100644
--- a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java
+++ b/src/java/org/apache/cassandra/auth/NetworkPermissionsCache.java
@@ -19,12 +19,13 @@
 package org.apache.cassandra.auth;
 
 import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.utils.MBeanWrapper;
 
-public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions>
+public class NetworkPermissionsCache extends AuthCache<RoleResource, 
DCPermissions> implements NetworkPermissionsCacheMBean
 {
-    public NetworkAuthCache(INetworkAuthorizer authorizer)
+    public NetworkPermissionsCache(INetworkAuthorizer authorizer)
     {
-        super("NetworkAuthCache",
+        super(CACHE_NAME,
               DatabaseDescriptor::setRolesValidity,
               DatabaseDescriptor::getRolesValidity,
               DatabaseDescriptor::setRolesUpdateInterval,
@@ -33,5 +34,19 @@ public class NetworkAuthCache extends 
AuthCache<RoleResource, DCPermissions>
               DatabaseDescriptor::getRolesCacheMaxEntries,
               authorizer::authorize,
               () -> 
DatabaseDescriptor.getAuthenticator().requireAuthentication());
+
+        MBeanWrapper.instance.registerMBean(this, MBEAN_NAME_BASE + 
DEPRECATED_CACHE_NAME);
+    }
+
+    public void invalidateNetworkPermissions(String roleName)
+    {
+        invalidate(RoleResource.role(roleName));
+    }
+
+    @Override
+    protected void unregisterMBean()
+    {
+        super.unregisterMBean();
+        MBeanWrapper.instance.unregisterMBean(MBEAN_NAME_BASE + 
DEPRECATED_CACHE_NAME, MBeanWrapper.OnException.LOG);
     }
 }
diff --git a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java 
b/src/java/org/apache/cassandra/auth/NetworkPermissionsCacheMBean.java
similarity index 54%
copy from src/java/org/apache/cassandra/auth/NetworkAuthCache.java
copy to src/java/org/apache/cassandra/auth/NetworkPermissionsCacheMBean.java
index 6b3c74e..b0e72b0 100644
--- a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java
+++ b/src/java/org/apache/cassandra/auth/NetworkPermissionsCacheMBean.java
@@ -18,20 +18,11 @@
 
 package org.apache.cassandra.auth;
 
-import org.apache.cassandra.config.DatabaseDescriptor;
-
-public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions>
+public interface NetworkPermissionsCacheMBean extends AuthCacheMBean
 {
-    public NetworkAuthCache(INetworkAuthorizer authorizer)
-    {
-        super("NetworkAuthCache",
-              DatabaseDescriptor::setRolesValidity,
-              DatabaseDescriptor::getRolesValidity,
-              DatabaseDescriptor::setRolesUpdateInterval,
-              DatabaseDescriptor::getRolesUpdateInterval,
-              DatabaseDescriptor::setRolesCacheMaxEntries,
-              DatabaseDescriptor::getRolesCacheMaxEntries,
-              authorizer::authorize,
-              () -> 
DatabaseDescriptor.getAuthenticator().requireAuthentication());
-    }
+    public static final String CACHE_NAME = "NetworkPermissionsCache";
+    @Deprecated
+    public static final String DEPRECATED_CACHE_NAME = "NetworkAuthCache";
+
+    public void invalidateNetworkPermissions(String roleName);
 }
diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java 
b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
index 9da99a9..00ccab7 100644
--- a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
+++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import java.util.Map;
 import java.util.Set;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.slf4j.Logger;
@@ -103,11 +104,10 @@ public class PasswordAuthenticator implements 
IAuthenticator
     {
         try
         {
-            ResultMessage.Rows rows =
-            authenticateStatement.execute(QueryState.forInternalCalls(),
-                                            
QueryOptions.forInternalCalls(consistencyForRole(username),
-                                                                          
Lists.newArrayList(ByteBufferUtil.bytes(username))),
-                                            System.nanoTime());
+            QueryOptions options = 
QueryOptions.forInternalCalls(consistencyForRole(username),
+                    Lists.newArrayList(ByteBufferUtil.bytes(username)));
+
+            ResultMessage.Rows rows = select(authenticateStatement, options);
 
             // If either a non-existent role name was supplied, or no 
credentials
             // were found for that role we don't want to cache the result so 
we throw
@@ -127,6 +127,12 @@ public class PasswordAuthenticator implements 
IAuthenticator
         }
     }
 
+    @VisibleForTesting
+    ResultMessage.Rows select(SelectStatement statement, QueryOptions options)
+    {
+        return statement.execute(QueryState.forInternalCalls(), options, 
System.nanoTime());
+    }
+
     public Set<DataResource> protectedResources()
     {
         // Also protected by CassandraRoleManager, but the duplication doesn't 
hurt and is more explicit
@@ -243,7 +249,7 @@ public class PasswordAuthenticator implements IAuthenticator
     {
         private CredentialsCache(PasswordAuthenticator authenticator)
         {
-            super("CredentialsCache",
+            super(CACHE_NAME,
                   DatabaseDescriptor::setCredentialsValidity,
                   DatabaseDescriptor::getCredentialsValidity,
                   DatabaseDescriptor::setCredentialsUpdateInterval,
@@ -262,6 +268,8 @@ public class PasswordAuthenticator implements IAuthenticator
 
     public static interface CredentialsCacheMBean extends AuthCacheMBean
     {
+        public static final String CACHE_NAME = "CredentialsCache";
+
         public void invalidateCredentials(String roleName);
     }
 }
diff --git a/src/java/org/apache/cassandra/auth/PermissionsCache.java 
b/src/java/org/apache/cassandra/auth/PermissionsCache.java
index a33f5d1..a649c35 100644
--- a/src/java/org/apache/cassandra/auth/PermissionsCache.java
+++ b/src/java/org/apache/cassandra/auth/PermissionsCache.java
@@ -23,10 +23,11 @@ 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 PermissionsCache(IAuthorizer authorizer)
     {
-        super("PermissionsCache",
+        super(CACHE_NAME,
               DatabaseDescriptor::setPermissionsValidity,
               DatabaseDescriptor::getPermissionsValidity,
               DatabaseDescriptor::setPermissionsUpdateInterval,
@@ -41,4 +42,9 @@ public class PermissionsCache extends 
AuthCache<Pair<AuthenticatedUser, IResourc
     {
         return get(Pair.create(user, resource));
     }
+
+    public void invalidatePermissions(String userName, String resourceName)
+    {
+        invalidate(Pair.create(new AuthenticatedUser(userName), 
Resources.fromName(resourceName)));
+    }
 }
diff --git a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java 
b/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java
similarity index 54%
copy from src/java/org/apache/cassandra/auth/NetworkAuthCache.java
copy to src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java
index 6b3c74e..f2116d1 100644
--- a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java
+++ b/src/java/org/apache/cassandra/auth/PermissionsCacheMBean.java
@@ -18,20 +18,9 @@
 
 package org.apache.cassandra.auth;
 
-import org.apache.cassandra.config.DatabaseDescriptor;
-
-public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions>
+public interface PermissionsCacheMBean extends AuthCacheMBean
 {
-    public NetworkAuthCache(INetworkAuthorizer authorizer)
-    {
-        super("NetworkAuthCache",
-              DatabaseDescriptor::setRolesValidity,
-              DatabaseDescriptor::getRolesValidity,
-              DatabaseDescriptor::setRolesUpdateInterval,
-              DatabaseDescriptor::getRolesUpdateInterval,
-              DatabaseDescriptor::setRolesCacheMaxEntries,
-              DatabaseDescriptor::getRolesCacheMaxEntries,
-              authorizer::authorize,
-              () -> 
DatabaseDescriptor.getAuthenticator().requireAuthentication());
-    }
+    public static final String CACHE_NAME = "PermissionsCache";
+
+    public void invalidatePermissions(String userName, String resourceName);
 }
diff --git a/src/java/org/apache/cassandra/auth/RolesCache.java 
b/src/java/org/apache/cassandra/auth/RolesCache.java
index d01de63..62fecfb 100644
--- a/src/java/org/apache/cassandra/auth/RolesCache.java
+++ b/src/java/org/apache/cassandra/auth/RolesCache.java
@@ -23,11 +23,11 @@ import java.util.stream.Collectors;
 
 import org.apache.cassandra.config.DatabaseDescriptor;
 
-public class RolesCache extends AuthCache<RoleResource, Set<Role>>
+public class RolesCache extends AuthCache<RoleResource, Set<Role>> implements 
RolesCacheMBean
 {
     public RolesCache(IRoleManager roleManager, BooleanSupplier enableCache)
     {
-        super("RolesCache",
+        super(CACHE_NAME,
               DatabaseDescriptor::setRolesValidity,
               DatabaseDescriptor::getRolesValidity,
               DatabaseDescriptor::setRolesUpdateInterval,
@@ -62,4 +62,9 @@ public class RolesCache extends AuthCache<RoleResource, 
Set<Role>>
     {
         return get(primaryRole);
     }
+
+    public void invalidateRoles(String roleName)
+    {
+        invalidate(RoleResource.role(roleName));
+    }
 }
diff --git a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java 
b/src/java/org/apache/cassandra/auth/RolesCacheMBean.java
similarity index 54%
rename from src/java/org/apache/cassandra/auth/NetworkAuthCache.java
rename to src/java/org/apache/cassandra/auth/RolesCacheMBean.java
index 6b3c74e..18b3c40 100644
--- a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java
+++ b/src/java/org/apache/cassandra/auth/RolesCacheMBean.java
@@ -18,20 +18,9 @@
 
 package org.apache.cassandra.auth;
 
-import org.apache.cassandra.config.DatabaseDescriptor;
-
-public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions>
+public interface RolesCacheMBean extends AuthCacheMBean
 {
-    public NetworkAuthCache(INetworkAuthorizer authorizer)
-    {
-        super("NetworkAuthCache",
-              DatabaseDescriptor::setRolesValidity,
-              DatabaseDescriptor::getRolesValidity,
-              DatabaseDescriptor::setRolesUpdateInterval,
-              DatabaseDescriptor::getRolesUpdateInterval,
-              DatabaseDescriptor::setRolesCacheMaxEntries,
-              DatabaseDescriptor::getRolesCacheMaxEntries,
-              authorizer::authorize,
-              () -> 
DatabaseDescriptor.getAuthenticator().requireAuthentication());
-    }
+    public static final String CACHE_NAME = "RolesCache";
+
+    void invalidateRoles(String roleName);
 }
diff --git a/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java 
b/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java
index 68cff0c..9179062 100644
--- a/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java
+++ b/src/java/org/apache/cassandra/auth/jmx/AuthorizationProxy.java
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
 import org.apache.cassandra.auth.*;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.utils.MBeanWrapper;
 
 /**
  * Provides a proxy interface to the platform's MBeanServer instance to perform
@@ -103,7 +104,7 @@ public class AuthorizationProxy implements InvocationHandler
                                                                       
"registerMBean",
                                                                       
"unregisterMBean");
 
-    private static final JMXPermissionsCache permissionsCache = new 
JMXPermissionsCache();
+    private static final JmxPermissionsCache permissionsCache = new 
JmxPermissionsCache();
     private MBeanServer mbs;
 
     /*
@@ -182,7 +183,7 @@ public class AuthorizationProxy implements InvocationHandler
      *             as an invocation of a method on the MBeanServer.
      */
     @VisibleForTesting
-    boolean authorize(Subject subject, String methodName, Object[] args)
+    public boolean authorize(Subject subject, String methodName, Object[] args)
     {
         logger.trace("Authorizing JMX method invocation {} for {}",
                      methodName,
@@ -476,11 +477,12 @@ public class AuthorizationProxy implements 
InvocationHandler
                                                  .collect(Collectors.toSet());
     }
 
-    private static final class JMXPermissionsCache extends 
AuthCache<RoleResource, Set<PermissionDetails>>
+    private static final class JmxPermissionsCache extends 
AuthCache<RoleResource, Set<PermissionDetails>>
+        implements JmxPermissionsCacheMBean
     {
-        protected JMXPermissionsCache()
+        protected JmxPermissionsCache()
         {
-            super("JMXPermissionsCache",
+            super(CACHE_NAME,
                   DatabaseDescriptor::setPermissionsValidity,
                   DatabaseDescriptor::getPermissionsValidity,
                   DatabaseDescriptor::setPermissionsUpdateInterval,
@@ -489,6 +491,29 @@ public class AuthorizationProxy implements 
InvocationHandler
                   DatabaseDescriptor::getPermissionsCacheMaxEntries,
                   AuthorizationProxy::loadPermissions,
                   () -> true);
+
+            MBeanWrapper.instance.registerMBean(this, MBEAN_NAME_BASE + 
DEPRECATED_CACHE_NAME);
+        }
+
+        public void invalidatePermissions(String roleName)
+        {
+            invalidate(RoleResource.role(roleName));
         }
+
+        @Override
+        protected void unregisterMBean()
+        {
+            super.unregisterMBean();
+            MBeanWrapper.instance.unregisterMBean(MBEAN_NAME_BASE + 
DEPRECATED_CACHE_NAME, MBeanWrapper.OnException.LOG);
+        }
+    }
+
+    public static interface JmxPermissionsCacheMBean extends AuthCacheMBean
+    {
+        public static final String CACHE_NAME = "JmxPermissionsCache";
+        @Deprecated
+        public static final String DEPRECATED_CACHE_NAME = 
"JMXPermissionsCache";
+
+        public void invalidatePermissions(String roleName);
     }
 }
diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java 
b/src/java/org/apache/cassandra/tools/NodeProbe.java
index e7c595e..d1b7528 100644
--- a/src/java/org/apache/cassandra/tools/NodeProbe.java
+++ b/src/java/org/apache/cassandra/tools/NodeProbe.java
@@ -55,12 +55,20 @@ import javax.management.remote.JMXConnectorFactory;
 import javax.management.remote.JMXServiceURL;
 import javax.rmi.ssl.SslRMIClientSocketFactory;
 
-
 import org.apache.cassandra.audit.AuditLogManager;
 import org.apache.cassandra.audit.AuditLogManagerMBean;
 import org.apache.cassandra.audit.AuditLogOptions;
 import org.apache.cassandra.audit.AuditLogOptionsCompositeData;
 import com.google.common.collect.ImmutableMap;
+import org.apache.cassandra.auth.AuthCache;
+import org.apache.cassandra.auth.NetworkPermissionsCache;
+import org.apache.cassandra.auth.NetworkPermissionsCacheMBean;
+import org.apache.cassandra.auth.PasswordAuthenticator;
+import org.apache.cassandra.auth.PermissionsCache;
+import org.apache.cassandra.auth.PermissionsCacheMBean;
+import org.apache.cassandra.auth.RolesCache;
+import org.apache.cassandra.auth.RolesCacheMBean;
+import org.apache.cassandra.auth.jmx.AuthorizationProxy;
 import org.apache.cassandra.batchlog.BatchlogManager;
 import org.apache.cassandra.batchlog.BatchlogManagerMBean;
 import org.apache.cassandra.db.ColumnFamilyStoreMBean;
@@ -140,6 +148,11 @@ public class NodeProbe implements AutoCloseable
     protected BatchlogManagerMBean bmProxy;
     protected ActiveRepairServiceMBean arsProxy;
     protected AuditLogManagerMBean almProxy;
+    protected PasswordAuthenticator.CredentialsCacheMBean ccProxy;
+    protected AuthorizationProxy.JmxPermissionsCacheMBean jpcProxy;
+    protected NetworkPermissionsCacheMBean npcProxy;
+    protected PermissionsCacheMBean pcProxy;
+    protected RolesCacheMBean rcProxy;
     protected Output output;
     private boolean failed;
 
@@ -248,6 +261,16 @@ public class NodeProbe implements AutoCloseable
             arsProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
ActiveRepairServiceMBean.class);
             name = new ObjectName(AuditLogManager.MBEAN_NAME);
             almProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
AuditLogManagerMBean.class);
+            name = new ObjectName(AuthCache.MBEAN_NAME_BASE + 
PasswordAuthenticator.CredentialsCacheMBean.CACHE_NAME);
+            ccProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
PasswordAuthenticator.CredentialsCacheMBean.class);
+            name = new ObjectName(AuthCache.MBEAN_NAME_BASE + 
AuthorizationProxy.JmxPermissionsCacheMBean.CACHE_NAME);
+            jpcProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
AuthorizationProxy.JmxPermissionsCacheMBean.class);
+            name = new ObjectName(AuthCache.MBEAN_NAME_BASE + 
NetworkPermissionsCache.CACHE_NAME);
+            npcProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
NetworkPermissionsCacheMBean.class);
+            name = new ObjectName(AuthCache.MBEAN_NAME_BASE + 
PermissionsCache.CACHE_NAME);
+            pcProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
PermissionsCacheMBean.class);
+            name = new ObjectName(AuthCache.MBEAN_NAME_BASE + 
RolesCache.CACHE_NAME);
+            rcProxy = JMX.newMBeanProxy(mbeanServerConn, name, 
RolesCacheMBean.class);
         }
         catch (MalformedObjectNameException e)
         {
@@ -481,11 +504,61 @@ public class NodeProbe implements AutoCloseable
         cacheService.invalidateCounterCache();
     }
 
+    public void invalidateCredentialsCache()
+    {
+        ccProxy.invalidate();
+    }
+
+    public void invalidateCredentialsCache(String roleName)
+    {
+        ccProxy.invalidateCredentials(roleName);
+    }
+
+    public void invalidateJmxPermissionsCache()
+    {
+        jpcProxy.invalidate();
+    }
+
+    public void invalidateJmxPermissionsCache(String roleName)
+    {
+        jpcProxy.invalidatePermissions(roleName);
+    }
+
     public void invalidateKeyCache()
     {
         cacheService.invalidateKeyCache();
     }
 
+    public void invalidateNetworkPermissionsCache()
+    {
+        npcProxy.invalidate();
+    }
+
+    public void invalidateNetworkPermissionsCache(String roleName)
+    {
+        npcProxy.invalidateNetworkPermissions(roleName);
+    }
+
+    public void invalidatePermissionsCache()
+    {
+        pcProxy.invalidate();
+    }
+
+    public void invalidatePermissionsCache(String userName, String 
resourceName)
+    {
+        pcProxy.invalidatePermissions(userName, resourceName);
+    }
+
+    public void invalidateRolesCache()
+    {
+        rcProxy.invalidate();
+    }
+
+    public void invalidateRolesCache(String roleName)
+    {
+        rcProxy.invalidateRoles(roleName);
+    }
+
     public void invalidateRowCache()
     {
         cacheService.invalidateRowCache();
diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java 
b/src/java/org/apache/cassandra/tools/NodeTool.java
index 544bab0..1f5295f 100644
--- a/src/java/org/apache/cassandra/tools/NodeTool.java
+++ b/src/java/org/apache/cassandra/tools/NodeTool.java
@@ -33,7 +33,6 @@ import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOError;
 import java.io.IOException;
-import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -140,9 +139,14 @@ public class NodeTool
                 GetMaxHintWindow.class,
                 GossipInfo.class,
                 Import.class,
+                InvalidateCounterCache.class,
+                InvalidateCredentialsCache.class,
+                InvalidateJmxPermissionsCache.class,
                 InvalidateKeyCache.class,
+                InvalidateNetworkPermissionsCache.class,
+                InvalidatePermissionsCache.class,
+                InvalidateRolesCache.class,
                 InvalidateRowCache.class,
-                InvalidateCounterCache.class,
                 Join.class,
                 Move.class,
                 PauseHandoff.class,
@@ -473,29 +477,6 @@ public class NodeTool
         }
     }
 
-    public static SortedMap<String, SetHostStat> getOwnershipByDc(NodeProbe 
probe, boolean resolveIp,
-                                                                  Map<String, 
String> tokenToEndpoint,
-                                                                  
Map<InetAddress, Float> ownerships)
-    {
-        SortedMap<String, SetHostStat> ownershipByDc = Maps.newTreeMap();
-        EndpointSnitchInfoMBean epSnitchInfo = 
probe.getEndpointSnitchInfoProxy();
-        try
-        {
-            for (Entry<String, String> tokenAndEndPoint : 
tokenToEndpoint.entrySet())
-            {
-                String dc = 
epSnitchInfo.getDatacenter(tokenAndEndPoint.getValue());
-                if (!ownershipByDc.containsKey(dc))
-                    ownershipByDc.put(dc, new SetHostStat(resolveIp));
-                ownershipByDc.get(dc).add(tokenAndEndPoint.getKey(), 
tokenAndEndPoint.getValue(), ownerships);
-            }
-        }
-        catch (UnknownHostException e)
-        {
-            throw new RuntimeException(e);
-        }
-        return ownershipByDc;
-    }
-
     public static SortedMap<String, SetHostStatWithPort> 
getOwnershipByDcWithPort(NodeProbe probe, boolean resolveIp,
                                                                   Map<String, 
String> tokenToEndpoint,
                                                                   Map<String, 
Float> ownerships)
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCache.java 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCache.java
new file mode 100644
index 0000000..0f9079f
--- /dev/null
+++ 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCache.java
@@ -0,0 +1,49 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatecredentialscache", description = "Invalidate the 
credentials cache")
+public class InvalidateCredentialsCache extends NodeToolCmd
+{
+    @Arguments(usage = "[<role>...]", description = "List of roles to 
invalidate. By default, all roles")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        if (args.isEmpty())
+        {
+            probe.invalidateCredentialsCache();
+        }
+        else
+        {
+            for (String roleName : args)
+            {
+                probe.invalidateCredentialsCache(roleName);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCache.java
 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCache.java
new file mode 100644
index 0000000..c242b03
--- /dev/null
+++ 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCache.java
@@ -0,0 +1,48 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatejmxpermissionscache", description = "Invalidate the 
JMX permissions cache")
+public class InvalidateJmxPermissionsCache extends NodeToolCmd
+{
+    @Arguments(usage = "[<role>...]", description = "List of roles to 
invalidate. By default, all roles")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        if (args.isEmpty())
+        {
+            probe.invalidateJmxPermissionsCache();
+        } else
+        {
+            for (String roleName : args)
+            {
+                probe.invalidateJmxPermissionsCache(roleName);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCache.java
 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCache.java
new file mode 100644
index 0000000..8b58060
--- /dev/null
+++ 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCache.java
@@ -0,0 +1,49 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatenetworkpermissionscache", description = "Invalidate 
the network permissions cache")
+public class InvalidateNetworkPermissionsCache extends NodeToolCmd
+{
+    @Arguments(usage = "[<role>...]", description = "List of roles to 
invalidate. By default, all roles")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        if (args.isEmpty())
+        {
+            probe.invalidateNetworkPermissionsCache();
+        }
+        else
+        {
+            for (String roleName : args)
+            {
+                probe.invalidateNetworkPermissionsCache(roleName);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java
new file mode 100644
index 0000000..049e91b
--- /dev/null
+++ 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java
@@ -0,0 +1,174 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.auth.DataResource;
+import org.apache.cassandra.auth.FunctionResource;
+import org.apache.cassandra.auth.JMXResource;
+import org.apache.cassandra.auth.RoleResource;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "invalidatepermissionscache", description = "Invalidate the 
permissions cache")
+public class InvalidatePermissionsCache extends NodeToolCmd
+{
+    @Arguments(usage = "[<user>]", description = "A specific user for whom 
permissions need to be invalidated")
+    private List<String> args = new ArrayList<>();
+
+    // Data Resources
+    @Option(title = "all-keyspaces",
+            name = {"--all-keyspaces"},
+            description = "Invalidate permissions for 'ALL KEYSPACES'")
+    private boolean allKeyspaces;
+
+    @Option(title = "keyspace",
+            name = {"--keyspace"},
+            description = "Keyspace to invalidate permissions for")
+    private String keyspace;
+
+    @Option(title = "table",
+            name = {"--table"},
+            description = "Table to invalidate permissions for (you must 
specify --keyspace for using this option)")
+    private String table;
+
+    // Roles Resources
+    @Option(title = "all-roles",
+            name = {"--all-roles"},
+            description = "Invalidate permissions for 'ALL ROLES'")
+    private boolean allRoles;
+
+    @Option(title = "role",
+            name = {"--role"},
+            description = "Role to invalidate permissions for")
+    private String role;
+
+    // Functions Resources
+    @Option(title = "all-functions",
+            name = {"--all-functions"},
+            description = "Invalidate permissions for 'ALL FUNCTIONS'")
+    private boolean allFunctions;
+
+    @Option(title = "functions-in-keyspace",
+            name = {"--functions-in-keyspace"},
+            description = "Keyspace to invalidate permissions for")
+    private String functionsInKeyspace;
+
+    @Option(title = "function",
+            name = {"--function"},
+            description = "Function to invalidate permissions for (you must 
specify --functions-in-keyspace for using " +
+                    "this option; function format: name[arg1^..^agrN], for 
example: foo[Int32Type^DoubleType])")
+    private String function;
+
+    // MBeans Resources
+    @Option(title = "all-mbeans",
+            name = {"--all-mbeans"},
+            description = "Invalidate permissions for 'ALL MBEANS'")
+    private boolean allMBeans;
+
+    @Option(title = "mbean",
+            name = {"--mbean"},
+            description = "MBean to invalidate permissions for")
+    private String mBean;
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        if (args.isEmpty())
+        {
+            checkArgument(!allKeyspaces && StringUtils.isEmpty(keyspace) && 
StringUtils.isEmpty(table)
+                    && !allRoles && StringUtils.isEmpty(role)
+                    && !allFunctions && 
StringUtils.isEmpty(functionsInKeyspace) && StringUtils.isEmpty(function)
+                    && !allMBeans && StringUtils.isEmpty(mBean),
+                    "No options allowed without a <user> being specified");
+
+            probe.invalidatePermissionsCache();
+        }
+        else
+        {
+            checkArgument(args.size() == 1,
+                    "A single <user> is only supported / you have a typo in 
the options spelling");
+            List<String> resourceNames = new ArrayList<>();
+
+            // Data Resources
+            if (allKeyspaces)
+                resourceNames.add(DataResource.root().getName());
+
+            if (StringUtils.isNotEmpty(table))
+                if (StringUtils.isNotEmpty(keyspace))
+                    resourceNames.add(DataResource.table(keyspace, 
table).getName());
+                else
+                    throw new IllegalArgumentException("--table option should 
be passed along with --keyspace option");
+            else
+                if (StringUtils.isNotEmpty(keyspace))
+                    
resourceNames.add(DataResource.keyspace(keyspace).getName());
+
+            // Roles Resources
+            if (allRoles)
+                resourceNames.add(RoleResource.root().getName());
+
+            if (StringUtils.isNotEmpty(role))
+                resourceNames.add(RoleResource.role(role).getName());
+
+            // Function Resources
+            if (allFunctions)
+                resourceNames.add(FunctionResource.root().getName());
+
+            if (StringUtils.isNotEmpty(function))
+                if (StringUtils.isNotEmpty(functionsInKeyspace))
+                    
resourceNames.add(constructFunctionResource(functionsInKeyspace, function));
+                else
+                    throw new IllegalArgumentException("--function option 
should be passed along with --functions-in-keyspace option");
+            else
+                if (StringUtils.isNotEmpty(functionsInKeyspace))
+                    
resourceNames.add(FunctionResource.keyspace(functionsInKeyspace).getName());
+
+            // MBeans Resources
+            if (allMBeans)
+                resourceNames.add(JMXResource.root().getName());
+
+            if (StringUtils.isNotEmpty(mBean))
+                resourceNames.add(JMXResource.mbean(mBean).getName());
+
+            String userName = args.get(0);
+
+            for (String resourceName : resourceNames)
+                probe.invalidatePermissionsCache(userName, resourceName);
+        }
+    }
+
+    private String constructFunctionResource(String functionsInKeyspace, 
String function) {
+        try
+        {
+            return FunctionResource.fromName("functions/" + 
functionsInKeyspace + '/' + function).getName();
+        } catch (ConfigurationException e)
+        {
+            throw new IllegalArgumentException("An error was encountered when 
looking up function definition; " + e.getMessage());
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/InvalidateRolesCache.java 
b/src/java/org/apache/cassandra/tools/nodetool/InvalidateRolesCache.java
new file mode 100644
index 0000000..4fca5c3
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/InvalidateRolesCache.java
@@ -0,0 +1,50 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidaterolescache", description = "Invalidate the roles 
cache")
+public class InvalidateRolesCache extends NodeToolCmd
+{
+
+    @Arguments(usage = "[<role>...]", description = "List of roles to 
invalidate. By default, all roles")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        if (args.isEmpty())
+        {
+            probe.invalidateRolesCache();
+        }
+        else
+        {
+            for (String roleName : args)
+            {
+                probe.invalidateRolesCache(roleName);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/auth/RoleTestUtils.java 
b/test/unit/org/apache/cassandra/auth/AuthTestUtils.java
similarity index 60%
rename from test/unit/org/apache/cassandra/auth/RoleTestUtils.java
rename to test/unit/org/apache/cassandra/auth/AuthTestUtils.java
index e2d1006..a012b62 100644
--- a/test/unit/org/apache/cassandra/auth/RoleTestUtils.java
+++ b/test/unit/org/apache/cassandra/auth/AuthTestUtils.java
@@ -23,16 +23,18 @@ import java.util.concurrent.Callable;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.cql3.QueryProcessor;
 import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.cql3.statements.BatchStatement;
 import org.apache.cassandra.cql3.statements.SelectStatement;
 import org.apache.cassandra.db.ColumnFamilyStore;
 import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.exceptions.RequestExecutionException;
 import org.apache.cassandra.schema.SchemaConstants;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.transport.messages.ResultMessage;
 
 
-public class RoleTestUtils
+public class AuthTestUtils
 {
 
     public static final RoleResource ROLE_A = RoleResource.role("role_a");
@@ -71,15 +73,78 @@ public class RoleTestUtils
         }
     }
 
+    public static class LocalCassandraAuthorizer extends CassandraAuthorizer
+    {
+        ResultMessage.Rows select(SelectStatement statement, QueryOptions 
options)
+        {
+            return statement.executeLocally(QueryState.forInternalCalls(), 
options);
+        }
+
+        UntypedResultSet process(String query) throws RequestExecutionException
+        {
+            return QueryProcessor.executeInternal(query);
+        }
+
+        @Override
+        void processBatch(BatchStatement statement)
+        {
+            statement.executeLocally(QueryState.forInternalCalls(), 
QueryOptions.DEFAULT);
+        }
+    }
+
+    public static class LocalCassandraNetworkAuthorizer extends 
CassandraNetworkAuthorizer
+    {
+        ResultMessage.Rows select(SelectStatement statement, QueryOptions 
options)
+        {
+            return statement.executeLocally(QueryState.forInternalCalls(), 
options);
+        }
+
+        void process(String query)
+        {
+            QueryProcessor.executeInternal(query);
+        }
+    }
+
+    public static class LocalPasswordAuthenticator extends 
PasswordAuthenticator
+    {
+        ResultMessage.Rows select(SelectStatement statement, QueryOptions 
options)
+        {
+            return statement.executeLocally(QueryState.forInternalCalls(), 
options);
+        }
+    }
+
     public static void grantRolesTo(IRoleManager roleManager, RoleResource 
grantee, RoleResource...granted)
     {
         for(RoleResource toGrant : granted)
             roleManager.grantRole(AuthenticatedUser.ANONYMOUS_USER, toGrant, 
grantee);
     }
 
-    public static long getReadCount()
+    public static long getNetworkPermissionsReadCount()
+    {
+        ColumnFamilyStore networkPemissionsTable =
+                
Keyspace.open(SchemaConstants.AUTH_KEYSPACE_NAME).getColumnFamilyStore(AuthKeyspace.NETWORK_PERMISSIONS);
+        return networkPemissionsTable.metric.readLatency.latency.getCount();
+    }
+
+    public static long getRolePermissionsReadCount()
+    {
+        ColumnFamilyStore rolesPemissionsTable =
+                
Keyspace.open(SchemaConstants.AUTH_KEYSPACE_NAME).getColumnFamilyStore(AuthKeyspace.ROLE_PERMISSIONS);
+        return rolesPemissionsTable.metric.readLatency.latency.getCount();
+    }
+
+    public static long getRolesReadCount()
     {
         ColumnFamilyStore rolesTable = 
Keyspace.open(SchemaConstants.AUTH_KEYSPACE_NAME).getColumnFamilyStore(AuthKeyspace.ROLES);
         return rolesTable.metric.readLatency.latency.getCount();
     }
+
+    public static RoleOptions getLoginRoleOprions()
+    {
+        RoleOptions roleOptions = new RoleOptions();
+        roleOptions.setOption(IRoleManager.Option.SUPERUSER, false);
+        roleOptions.setOption(IRoleManager.Option.LOGIN, true);
+        roleOptions.setOption(IRoleManager.Option.PASSWORD, "ignored");
+        return roleOptions;
+    }
 }
\ No newline at end of file
diff --git 
a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java 
b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java
index 31af270..3dc9d91 100644
--- a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java
+++ b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java
@@ -31,62 +31,26 @@ import org.junit.Test;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.cql3.CQLStatement;
-import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.cql3.QueryProcessor;
 import org.apache.cassandra.cql3.UntypedResultSet;
 import org.apache.cassandra.cql3.statements.AlterRoleStatement;
 import org.apache.cassandra.cql3.statements.AuthenticationStatement;
-import org.apache.cassandra.cql3.statements.BatchStatement;
 import org.apache.cassandra.cql3.statements.CreateRoleStatement;
 import org.apache.cassandra.cql3.statements.DropRoleStatement;
-import org.apache.cassandra.cql3.statements.SelectStatement;
 import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.exceptions.RequestExecutionException;
 import org.apache.cassandra.service.ClientState;
-import org.apache.cassandra.service.QueryState;
-import org.apache.cassandra.transport.messages.ResultMessage;
 
 import static org.apache.cassandra.auth.AuthKeyspace.NETWORK_PERMISSIONS;
-import static 
org.apache.cassandra.auth.RoleTestUtils.LocalCassandraRoleManager;
+import static org.apache.cassandra.auth.AuthTestUtils.LocalCassandraAuthorizer;
+import static 
org.apache.cassandra.auth.AuthTestUtils.LocalCassandraNetworkAuthorizer;
+import static 
org.apache.cassandra.auth.AuthTestUtils.LocalCassandraRoleManager;
+import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount;
 import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
-import static org.apache.cassandra.auth.RoleTestUtils.getReadCount;
 
 public class CassandraNetworkAuthorizerTest
 {
-    private static class LocalCassandraAuthorizer extends CassandraAuthorizer
-    {
-        ResultMessage.Rows select(SelectStatement statement, QueryOptions 
options)
-        {
-            return statement.executeLocally(QueryState.forInternalCalls(), 
options);
-        }
-
-        UntypedResultSet process(String query) throws RequestExecutionException
-        {
-            return QueryProcessor.executeInternal(query);
-        }
-
-        @Override
-        void processBatch(BatchStatement statement)
-        {
-            statement.executeLocally(QueryState.forInternalCalls(), 
QueryOptions.DEFAULT);
-        }
-    }
-
-    private static class LocalCassandraNetworkAuthorizer extends 
CassandraNetworkAuthorizer
-    {
-        ResultMessage.Rows select(SelectStatement statement, QueryOptions 
options)
-        {
-            return statement.executeLocally(QueryState.forInternalCalls(), 
options);
-        }
-
-        void process(String query)
-        {
-            QueryProcessor.executeInternal(query);
-        }
-    }
-
     private static void setupSuperUser()
     {
         QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, 
is_superuser, can_login, salted_hash) "
@@ -249,10 +213,10 @@ public class CassandraNetworkAuthorizerTest
     {
         String username = createName();
         auth("CREATE ROLE %s", username);
-        long readCount = getReadCount();
+        long readCount = getRolesReadCount();
         dcPerms(username);
-        Assert.assertEquals(++readCount, getReadCount());
+        Assert.assertEquals(++readCount, getRolesReadCount());
         dcPerms(username);
-        Assert.assertEquals(readCount, getReadCount());
+        Assert.assertEquals(readCount, getRolesReadCount());
     }
 }
diff --git a/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java 
b/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java
index 6583c49..ea1ff65 100644
--- a/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java
+++ b/test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java
@@ -29,7 +29,7 @@ import org.apache.cassandra.schema.KeyspaceParams;
 import org.apache.cassandra.schema.SchemaConstants;
 import org.apache.cassandra.schema.TableMetadata;
 
-import static org.apache.cassandra.auth.RoleTestUtils.*;
+import static org.apache.cassandra.auth.AuthTestUtils.*;
 import static org.junit.Assert.assertEquals;
 
 public class CassandraRoleManagerTest
@@ -80,9 +80,9 @@ public class CassandraRoleManagerTest
 
     private void fetchRolesAndCheckReadCount(IRoleManager roleManager, 
RoleResource primaryRole)
     {
-        long before = getReadCount();
+        long before = getRolesReadCount();
         Set<Role> granted = roleManager.getRoleDetails(primaryRole);
-        long after = getReadCount();
+        long after = getRolesReadCount();
         assertEquals(granted.size(), after - before);
     }
 }
diff --git a/test/unit/org/apache/cassandra/auth/RolesTest.java 
b/test/unit/org/apache/cassandra/auth/RolesTest.java
index 94322a7..0211d46 100644
--- a/test/unit/org/apache/cassandra/auth/RolesTest.java
+++ b/test/unit/org/apache/cassandra/auth/RolesTest.java
@@ -29,7 +29,7 @@ import org.apache.cassandra.schema.KeyspaceParams;
 import org.apache.cassandra.schema.SchemaConstants;
 import org.apache.cassandra.schema.TableMetadata;
 
-import static org.apache.cassandra.auth.RoleTestUtils.*;
+import static org.apache.cassandra.auth.AuthTestUtils.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -57,39 +57,39 @@ public class RolesTest
     public void superuserStatusIsCached()
     {
         boolean hasSuper = Roles.hasSuperuserStatus(ROLE_A);
-        long count = getReadCount();
+        long count = getRolesReadCount();
 
         assertEquals(hasSuper, Roles.hasSuperuserStatus(ROLE_A));
-        assertEquals(count, getReadCount());
+        assertEquals(count, getRolesReadCount());
     }
 
     @Test
     public void loginPrivilegeIsCached()
     {
         boolean canLogin = Roles.canLogin(ROLE_A);
-        long count = getReadCount();
+        long count = getRolesReadCount();
 
         assertEquals(canLogin, Roles.canLogin(ROLE_A));
-        assertEquals(count, getReadCount());
+        assertEquals(count, getRolesReadCount());
     }
 
     @Test
     public void grantedRoleDetailsAreCached()
     {
         Iterable<Role> granted = Roles.getRoleDetails(ROLE_A);
-        long count = getReadCount();
+        long count = getRolesReadCount();
 
         assertTrue(Iterables.elementsEqual(granted, 
Roles.getRoleDetails(ROLE_A)));
-        assertEquals(count, getReadCount());
+        assertEquals(count, getRolesReadCount());
     }
 
     @Test
     public void grantedRoleResourcesAreCached()
     {
         Set<RoleResource> granted = Roles.getRoles(ROLE_A);
-        long count = getReadCount();
+        long count = getRolesReadCount();
 
         assertEquals(granted, Roles.getRoles(ROLE_A));
-        assertEquals(count, getReadCount());
+        assertEquals(count, getRolesReadCount());
     }
 }
diff --git 
a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java
 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java
new file mode 100644
index 0000000..7ed5530
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCredentialsCacheTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.tools.nodetool;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.datastax.driver.core.EndPoint;
+import com.datastax.driver.core.PlainTextAuthProvider;
+import org.apache.cassandra.OrderedJUnit4ClassRunner;
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.auth.IAuthenticator;
+import org.apache.cassandra.auth.PasswordAuthenticator;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(OrderedJUnit4ClassRunner.class)
+public class InvalidateCredentialsCacheTest extends CQLTester
+{
+    private static IAuthenticator.SaslNegotiator roleANegotiator;
+    private static IAuthenticator.SaslNegotiator roleBNegotiator;
+
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        SchemaLoader.prepareServer();
+        AuthTestUtils.LocalCassandraRoleManager roleManager = new 
AuthTestUtils.LocalCassandraRoleManager();
+        PasswordAuthenticator authenticator = new 
AuthTestUtils.LocalPasswordAuthenticator();
+        SchemaLoader.setupAuth(roleManager,
+                authenticator,
+                new AuthTestUtils.LocalCassandraAuthorizer(),
+                new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, 
AuthTestUtils.getLoginRoleOprions());
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, 
AuthTestUtils.getLoginRoleOprions());
+
+        roleANegotiator = authenticator.newSaslNegotiator(null);
+        roleANegotiator.evaluateResponse(new 
PlainTextAuthProvider(ROLE_A.getRoleName(), "ignored")
+                .newAuthenticator((EndPoint) null, null)
+                .initialResponse());
+        roleBNegotiator = authenticator.newSaslNegotiator(null);
+        roleBNegotiator.evaluateResponse(new 
PlainTextAuthProvider(ROLE_B.getRoleName(), "ignored")
+                .newAuthenticator((EndPoint) null, null)
+                .initialResponse());
+
+        startJMXServer();
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if 
necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", 
"invalidatecredentialscache");
+        tool.assertOnCleanExit();
+
+        String help =   "NAME\n" +
+                        "        nodetool invalidatecredentialscache - 
Invalidate the credentials cache\n" +
+                        "\n" +
+                        "SYNOPSIS\n" +
+                        "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
+                        "                [(-pp | --print-port)] [(-pw 
<password> | --password <password>)]\n" +
+                        "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
+                        "                [(-u <username> | --username 
<username>)] invalidatecredentialscache\n" +
+                        "                [--] [<role>...]\n" +
+                        "\n" +
+                        "OPTIONS\n" +
+                        "        -h <host>, --host <host>\n" +
+                        "            Node hostname or ip address\n" +
+                        "\n" +
+                        "        -p <port>, --port <port>\n" +
+                        "            Remote jmx agent port number\n" +
+                        "\n" +
+                        "        -pp, --print-port\n" +
+                        "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
+                        "\n" +
+                        "        -pw <password>, --password <password>\n" +
+                        "            Remote jmx agent password\n" +
+                        "\n" +
+                        "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
+                        "            Path to the JMX password file\n" +
+                        "\n" +
+                        "        -u <username>, --username <username>\n" +
+                        "            Remote jmx agent username\n" +
+                        "\n" +
+                        "        --\n" +
+                        "            This option can be used to separate 
command-line options from the\n" +
+                        "            list of argument, (useful when arguments 
might be mistaken for\n" +
+                        "            command-line options\n" +
+                        "\n" +
+                        "        [<role>...]\n" +
+                        "            List of roles to invalidate. By default, 
all roles\n" +
+                        "\n" +
+                        "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testInvalidateSingleCredential()
+    {
+        // cache credential
+        roleANegotiator.getAuthenticatedUser();
+        long originalReadsCount = getRolesReadCount();
+
+        // enure credential is cached
+        assertThat(roleANegotiator.getAuthenticatedUser()).isEqualTo(new 
AuthenticatedUser(ROLE_A.getRoleName()));
+        assertThat(originalReadsCount).isEqualTo(getRolesReadCount());
+
+        // invalidate credential
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatecredentialscache", ROLE_A.getRoleName());
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure credential is reloaded
+        assertThat(roleANegotiator.getAuthenticatedUser()).isEqualTo(new 
AuthenticatedUser(ROLE_A.getRoleName()));
+        assertThat(originalReadsCount).isLessThan(getRolesReadCount());
+    }
+
+    @Test
+    public void testInvalidateAllCredentials()
+    {
+        // cache credentials
+        roleANegotiator.getAuthenticatedUser();
+        roleBNegotiator.getAuthenticatedUser();
+        long originalReadsCount = getRolesReadCount();
+
+        // enure credentials are cached
+        assertThat(roleANegotiator.getAuthenticatedUser()).isEqualTo(new 
AuthenticatedUser(ROLE_A.getRoleName()));
+        assertThat(roleBNegotiator.getAuthenticatedUser()).isEqualTo(new 
AuthenticatedUser(ROLE_B.getRoleName()));
+        assertThat(originalReadsCount).isEqualTo(getRolesReadCount());
+
+        // invalidate both credentials
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatecredentialscache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure credential for roleA is reloaded
+        assertThat(roleANegotiator.getAuthenticatedUser()).isEqualTo(new 
AuthenticatedUser(ROLE_A.getRoleName()));
+        long readsCountAfterFirstReLoad = getRolesReadCount();
+        assertThat(originalReadsCount).isLessThan(readsCountAfterFirstReLoad);
+
+        // ensure credential for roleB is reloaded
+        assertThat(roleBNegotiator.getAuthenticatedUser()).isEqualTo(new 
AuthenticatedUser(ROLE_B.getRoleName()));
+        long readsCountAfterSecondReLoad = getRolesReadCount();
+        
assertThat(readsCountAfterFirstReLoad).isLessThan(readsCountAfterSecondReLoad);
+    }
+}
diff --git 
a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java
 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java
new file mode 100644
index 0000000..81fa2c5
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateJmxPermissionsCacheTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.Set;
+import javax.security.auth.Subject;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.apache.cassandra.OrderedJUnit4ClassRunner;
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.auth.CassandraPrincipal;
+import org.apache.cassandra.auth.JMXResource;
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.auth.jmx.AuthorizationProxy;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static 
org.apache.cassandra.auth.AuthTestUtils.getRolePermissionsReadCount;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(OrderedJUnit4ClassRunner.class)
+public class InvalidateJmxPermissionsCacheTest extends CQLTester
+{
+    private static final AuthorizationProxy authorizationProxy = new 
NoAuthSetupAuthorizationProxy();
+
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        SchemaLoader.prepareServer();
+        AuthTestUtils.LocalCassandraRoleManager roleManager = new 
AuthTestUtils.LocalCassandraRoleManager();
+        AuthTestUtils.LocalCassandraAuthorizer authorizer = new 
AuthTestUtils.LocalCassandraAuthorizer();
+        SchemaLoader.setupAuth(roleManager,
+                new AuthTestUtils.LocalPasswordAuthenticator(),
+                authorizer,
+                new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+
+        JMXResource rootJmxResource = JMXResource.root();
+        Set<Permission> jmxPermissions = 
rootJmxResource.applicablePermissions();
+
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, 
AuthTestUtils.getLoginRoleOprions());
+        authorizer.grant(AuthenticatedUser.SYSTEM_USER, jmxPermissions, 
rootJmxResource, ROLE_A);
+
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, 
AuthTestUtils.getLoginRoleOprions());
+        authorizer.grant(AuthenticatedUser.SYSTEM_USER, jmxPermissions, 
rootJmxResource, ROLE_B);
+
+        startJMXServer();
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if 
necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", 
"invalidatejmxpermissionscache");
+        tool.assertOnCleanExit();
+
+        String help =   "NAME\n" +
+                        "        nodetool invalidatejmxpermissionscache - 
Invalidate the JMX permissions\n" +
+                        "        cache\n" +
+                        "\n" +
+                        "SYNOPSIS\n" +
+                        "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
+                        "                [(-pp | --print-port)] [(-pw 
<password> | --password <password>)]\n" +
+                        "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
+                        "                [(-u <username> | --username 
<username>)] invalidatejmxpermissionscache\n" +
+                        "                [--] [<role>...]\n" +
+                        "\n" +
+                        "OPTIONS\n" +
+                        "        -h <host>, --host <host>\n" +
+                        "            Node hostname or ip address\n" +
+                        "\n" +
+                        "        -p <port>, --port <port>\n" +
+                        "            Remote jmx agent port number\n" +
+                        "\n" +
+                        "        -pp, --print-port\n" +
+                        "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
+                        "\n" +
+                        "        -pw <password>, --password <password>\n" +
+                        "            Remote jmx agent password\n" +
+                        "\n" +
+                        "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
+                        "            Path to the JMX password file\n" +
+                        "\n" +
+                        "        -u <username>, --username <username>\n" +
+                        "            Remote jmx agent username\n" +
+                        "\n" +
+                        "        --\n" +
+                        "            This option can be used to separate 
command-line options from the\n" +
+                        "            list of argument, (useful when arguments 
might be mistaken for\n" +
+                        "            command-line options\n" +
+                        "\n" +
+                        "        [<role>...]\n" +
+                        "            List of roles to invalidate. By default, 
all roles\n" +
+                        "\n" +
+                        "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testInvalidateSingleJMXPermission()
+    {
+        Subject userSubject = subject(ROLE_A.getRoleName());
+
+        // cache role permission
+        authorizationProxy.authorize(userSubject, "queryNames", null);
+        long originalReadsCount = getRolePermissionsReadCount();
+
+        // enure role permission is cached
+        assertThat(authorizationProxy.authorize(userSubject, "queryNames", 
null)).isTrue();
+        
assertThat(originalReadsCount).isEqualTo(getRolePermissionsReadCount());
+
+        // invalidate role permission
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatejmxpermissionscache", 
ROLE_A.getRoleName());
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure role permission is reloaded
+        assertThat(authorizationProxy.authorize(userSubject, "queryNames", 
null)).isTrue();
+        
assertThat(originalReadsCount).isLessThan(getRolePermissionsReadCount());
+    }
+
+    @Test
+    public void testInvalidateAllJMXPermissions()
+    {
+        Subject roleASubject = subject(ROLE_A.getRoleName());
+        Subject roleBSubject = subject(ROLE_B.getRoleName());
+
+        // cache role permissions
+        authorizationProxy.authorize(roleASubject, "queryNames", null);
+        authorizationProxy.authorize(roleBSubject, "queryNames", null);
+        long originalReadsCount = getRolePermissionsReadCount();
+
+        // enure role permissions are cached
+        assertThat(authorizationProxy.authorize(roleASubject, "queryNames", 
null)).isTrue();
+        assertThat(authorizationProxy.authorize(roleBSubject, "queryNames", 
null)).isTrue();
+        
assertThat(originalReadsCount).isEqualTo(getRolePermissionsReadCount());
+
+        // invalidate both role permissions
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatejmxpermissionscache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure role permission for roleA is reloaded
+        assertThat(authorizationProxy.authorize(roleASubject, "queryNames", 
null)).isTrue();
+        long readsCountAfterFirstReLoad = getRolePermissionsReadCount();
+        assertThat(originalReadsCount).isLessThan(readsCountAfterFirstReLoad);
+
+        // ensure role permission for roleB is reloaded
+        assertThat(authorizationProxy.authorize(roleBSubject, "queryNames", 
null)).isTrue();
+        long readsCountAfterSecondReLoad = getRolePermissionsReadCount();
+        
assertThat(readsCountAfterFirstReLoad).isLessThan(readsCountAfterSecondReLoad);
+    }
+
+    private static Subject subject(String roleName)
+    {
+        Subject subject = new Subject();
+        subject.getPrincipals().add(new CassandraPrincipal(roleName));
+        return subject;
+    }
+
+    private static class NoAuthSetupAuthorizationProxy extends 
AuthorizationProxy
+    {
+        public NoAuthSetupAuthorizationProxy()
+        {
+            super();
+            this.isAuthSetupComplete = () -> true;
+        }
+    }
+}
diff --git 
a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java
 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java
new file mode 100644
index 0000000..cef29b3
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateNetworkPermissionsCacheTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.tools.nodetool;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.apache.cassandra.OrderedJUnit4ClassRunner;
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static 
org.apache.cassandra.auth.AuthTestUtils.getNetworkPermissionsReadCount;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(OrderedJUnit4ClassRunner.class)
+public class InvalidateNetworkPermissionsCacheTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        SchemaLoader.prepareServer();
+        AuthTestUtils.LocalCassandraRoleManager roleManager = new 
AuthTestUtils.LocalCassandraRoleManager();
+        SchemaLoader.setupAuth(roleManager,
+                new AuthTestUtils.LocalPasswordAuthenticator(),
+                new AuthTestUtils.LocalCassandraAuthorizer(),
+                new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, 
AuthTestUtils.getLoginRoleOprions());
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, 
AuthTestUtils.getLoginRoleOprions());
+
+        startJMXServer();
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if 
necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", 
"invalidatenetworkpermissionscache");
+        tool.assertOnCleanExit();
+
+        String help =   "NAME\n" +
+                        "        nodetool invalidatenetworkpermissionscache - 
Invalidate the network\n" +
+                        "        permissions cache\n" +
+                        "\n" +
+                        "SYNOPSIS\n" +
+                        "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
+                        "                [(-pp | --print-port)] [(-pw 
<password> | --password <password>)]\n" +
+                        "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
+                        "                [(-u <username> | --username 
<username>)]\n" +
+                        "                invalidatenetworkpermissionscache 
[--] [<role>...]\n" +
+                        "\n" +
+                        "OPTIONS\n" +
+                        "        -h <host>, --host <host>\n" +
+                        "            Node hostname or ip address\n" +
+                        "\n" +
+                        "        -p <port>, --port <port>\n" +
+                        "            Remote jmx agent port number\n" +
+                        "\n" +
+                        "        -pp, --print-port\n" +
+                        "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
+                        "\n" +
+                        "        -pw <password>, --password <password>\n" +
+                        "            Remote jmx agent password\n" +
+                        "\n" +
+                        "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
+                        "            Path to the JMX password file\n" +
+                        "\n" +
+                        "        -u <username>, --username <username>\n" +
+                        "            Remote jmx agent username\n" +
+                        "\n" +
+                        "        --\n" +
+                        "            This option can be used to separate 
command-line options from the\n" +
+                        "            list of argument, (useful when arguments 
might be mistaken for\n" +
+                        "            command-line options\n" +
+                        "\n" +
+                        "        [<role>...]\n" +
+                        "            List of roles to invalidate. By default, 
all roles\n" +
+                        "\n" +
+                        "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testInvalidateSingleNetworkPermission()
+    {
+        AuthenticatedUser role = new AuthenticatedUser(ROLE_A.getRoleName());
+
+        // cache network permission
+        role.hasLocalAccess();
+        long originalReadsCount = getNetworkPermissionsReadCount();
+
+        // enure network permission is cached
+        assertThat(role.hasLocalAccess()).isTrue();
+        
assertThat(originalReadsCount).isEqualTo(getNetworkPermissionsReadCount());
+
+        // invalidate network permission
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatenetworkpermissionscache", 
ROLE_A.getRoleName());
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure network permission is reloaded
+        assertThat(role.hasLocalAccess()).isTrue();
+        
assertThat(originalReadsCount).isLessThan(getNetworkPermissionsReadCount());
+    }
+
+    @Test
+    public void testInvalidateAllNetworkPermissions()
+    {
+        AuthenticatedUser roleA = new AuthenticatedUser(ROLE_A.getRoleName());
+        AuthenticatedUser roleB = new AuthenticatedUser(ROLE_B.getRoleName());
+
+        // cache network permissions
+        roleA.hasLocalAccess();
+        roleB.hasLocalAccess();
+        long originalReadsCount = getNetworkPermissionsReadCount();
+
+        // enure network permissions are cached
+        assertThat(roleA.hasLocalAccess()).isTrue();
+        assertThat(roleB.hasLocalAccess()).isTrue();
+        
assertThat(originalReadsCount).isEqualTo(getNetworkPermissionsReadCount());
+
+        // invalidate both network permissions
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatenetworkpermissionscache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure network permission for roleA is reloaded
+        assertThat(roleA.hasLocalAccess()).isTrue();
+        long readsCountAfterFirstReLoad = getNetworkPermissionsReadCount();
+        assertThat(originalReadsCount).isLessThan(readsCountAfterFirstReLoad);
+
+        // ensure network permission for roleB is reloaded
+        assertThat(roleB.hasLocalAccess()).isTrue();
+        long readsCountAfterSecondReLoad = getNetworkPermissionsReadCount();
+        
assertThat(readsCountAfterFirstReLoad).isLessThan(readsCountAfterSecondReLoad);
+    }
+}
diff --git 
a/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java
 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java
new file mode 100644
index 0000000..caaabf2
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCacheTest.java
@@ -0,0 +1,317 @@
+/*
+ * 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.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.apache.cassandra.OrderedJUnit4ClassRunner;
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.auth.DataResource;
+import org.apache.cassandra.auth.FunctionResource;
+import org.apache.cassandra.auth.IResource;
+import org.apache.cassandra.auth.JMXResource;
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.auth.RoleResource;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.db.marshal.Int32Type;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static 
org.apache.cassandra.auth.AuthTestUtils.getRolePermissionsReadCount;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(OrderedJUnit4ClassRunner.class)
+public class InvalidatePermissionsCacheTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        SchemaLoader.prepareServer();
+        AuthTestUtils.LocalCassandraRoleManager roleManager = new 
AuthTestUtils.LocalCassandraRoleManager();
+        AuthTestUtils.LocalCassandraAuthorizer authorizer = new 
AuthTestUtils.LocalCassandraAuthorizer();
+        SchemaLoader.setupAuth(roleManager,
+                new AuthTestUtils.LocalPasswordAuthenticator(),
+                authorizer,
+                new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, 
AuthTestUtils.getLoginRoleOprions());
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, 
AuthTestUtils.getLoginRoleOprions());
+
+        List<IResource> resources = Arrays.asList(
+                DataResource.root(),
+                DataResource.keyspace(KEYSPACE),
+                DataResource.table(KEYSPACE, "t1"),
+                RoleResource.root(),
+                RoleResource.role("role_x"),
+                FunctionResource.root(),
+                FunctionResource.keyspace(KEYSPACE),
+                // Particular function is excluded from here and covered by a 
separate test because in order to grant
+                // permissions we need to have a function registered. However, 
the function cannot be registered via
+                // CQLTester.createFunction from static contex. That's why we 
initialize it in a separate test case.
+                JMXResource.root(),
+                JMXResource.mbean("org.apache.cassandra.auth:type=*"));
+
+        for (IResource resource : resources)
+        {
+            Set<Permission> permissions = resource.applicablePermissions();
+            authorizer.grant(AuthenticatedUser.SYSTEM_USER, permissions, 
resource, ROLE_A);
+            authorizer.grant(AuthenticatedUser.SYSTEM_USER, permissions, 
resource, ROLE_B);
+        }
+
+        startJMXServer();
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if 
necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", 
"invalidatepermissionscache");
+        tool.assertOnCleanExit();
+
+        String help =   "NAME\n" +
+                        "        nodetool invalidatepermissionscache - 
Invalidate the permissions cache\n" +
+                        "\n" +
+                        "SYNOPSIS\n" +
+                        "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
+                        "                [(-pp | --print-port)] [(-pw 
<password> | --password <password>)]\n" +
+                        "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
+                        "                [(-u <username> | --username 
<username>)] invalidatepermissionscache\n" +
+                        "                [--all-functions] [--all-keyspaces] 
[--all-mbeans] [--all-roles]\n" +
+                        "                [--function <function>]\n" +
+                        "                [--functions-in-keyspace 
<functions-in-keyspace>]\n" +
+                        "                [--keyspace <keyspace>] [--mbean 
<mbean>] [--role <role>]\n" +
+                        "                [--table <table>] [--] [<user>]\n" +
+                        "\n" +
+                        "OPTIONS\n" +
+                        "        --all-functions\n" +
+                        "            Invalidate permissions for 'ALL 
FUNCTIONS'\n" +
+                        "\n" +
+                        "        --all-keyspaces\n" +
+                        "            Invalidate permissions for 'ALL 
KEYSPACES'\n" +
+                        "\n" +
+                        "        --all-mbeans\n" +
+                        "            Invalidate permissions for 'ALL 
MBEANS'\n" +
+                        "\n" +
+                        "        --all-roles\n" +
+                        "            Invalidate permissions for 'ALL ROLES'\n" 
+
+                        "\n" +
+                        "        --function <function>\n" +
+                        "            Function to invalidate permissions for 
(you must specify\n" +
+                        "            --functions-in-keyspace for using this 
option; function format:\n" +
+                        "            name[arg1^..^agrN], for example: 
foo[Int32Type^DoubleType])\n" +
+                        "\n" +
+                        "        --functions-in-keyspace 
<functions-in-keyspace>\n" +
+                        "            Keyspace to invalidate permissions for\n" 
+
+                        "\n" +
+                        "        -h <host>, --host <host>\n" +
+                        "            Node hostname or ip address\n" +
+                        "\n" +
+                        "        --keyspace <keyspace>\n" +
+                        "            Keyspace to invalidate permissions for\n" 
+
+                        "\n" +
+                        "        --mbean <mbean>\n" +
+                        "            MBean to invalidate permissions for\n" +
+                        "\n" +
+                        "        -p <port>, --port <port>\n" +
+                        "            Remote jmx agent port number\n" +
+                        "\n" +
+                        "        -pp, --print-port\n" +
+                        "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
+                        "\n" +
+                        "        -pw <password>, --password <password>\n" +
+                        "            Remote jmx agent password\n" +
+                        "\n" +
+                        "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
+                        "            Path to the JMX password file\n" +
+                        "\n" +
+                        "        --role <role>\n" +
+                        "            Role to invalidate permissions for\n" +
+                        "\n" +
+                        "        --table <table>\n" +
+                        "            Table to invalidate permissions for (you 
must specify --keyspace for\n" +
+                        "            using this option)\n" +
+                        "\n" +
+                        "        -u <username>, --username <username>\n" +
+                        "            Remote jmx agent username\n" +
+                        "\n" +
+                        "        --\n" +
+                        "            This option can be used to separate 
command-line options from the\n" +
+                        "            list of argument, (useful when arguments 
might be mistaken for\n" +
+                        "            command-line options\n" +
+                        "\n" +
+                        "        [<user>]\n" +
+                        "            A specific user for whom permissions need 
to be invalidated\n" +
+                        "\n" +
+                        "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testInvalidatePermissionsWithIncorrectParameters()
+    {
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatepermissionscache", "--all-keyspaces");
+        assertThat(tool.getExitCode()).isEqualTo(1);
+        assertThat(tool.getStdout())
+                .isEqualTo(wrapByDefaultNodetoolMessage("No options allowed 
without a <user> being specified"));
+        assertThat(tool.getStderr()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("invalidatepermissionscache", 
"user1", "--invalid-option");
+        assertThat(tool.getExitCode()).isEqualTo(1);
+        assertThat(tool.getStdout())
+                .isEqualTo(wrapByDefaultNodetoolMessage("A single <user> is 
only supported / you have a typo in the options spelling"));
+        assertThat(tool.getStderr()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("invalidatepermissionscache", 
"user1", "--table", "t1");
+        assertThat(tool.getExitCode()).isEqualTo(1);
+        assertThat(tool.getStdout())
+                .isEqualTo(wrapByDefaultNodetoolMessage("--table option should 
be passed along with --keyspace option"));
+        assertThat(tool.getStderr()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("invalidatepermissionscache", 
"user1", "--function", "f[Int32Type]");
+        assertThat(tool.getExitCode()).isEqualTo(1);
+        assertThat(tool.getStdout())
+                .isEqualTo(wrapByDefaultNodetoolMessage("--function option 
should be passed along with --functions-in-keyspace option"));
+        assertThat(tool.getStderr()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("invalidatepermissionscache", 
"user1", "--functions-in-keyspace",
+                KEYSPACE, "--function", "f[x]");
+        assertThat(tool.getExitCode()).isEqualTo(1);
+        assertThat(tool.getStdout())
+                .isEqualTo(wrapByDefaultNodetoolMessage("An error was 
encountered when looking up function definition; Unable to find abstract-type 
class 'org.apache.cassandra.db.marshal.x'"));
+        assertThat(tool.getStderr()).isEmpty();
+    }
+
+    @Test
+    public void testInvalidatePermissionsForEveryResourceExceptFunction()
+    {
+        assertInvalidation(DataResource.root(), 
Collections.singletonList("--all-keyspaces"));
+        assertInvalidation(DataResource.keyspace(KEYSPACE), 
Arrays.asList("--keyspace", KEYSPACE));
+        assertInvalidation(DataResource.table(KEYSPACE, "t1"),
+                Arrays.asList("--keyspace", KEYSPACE, "--table", "t1"));
+        assertInvalidation(RoleResource.root(), 
Collections.singletonList("--all-roles"));
+        assertInvalidation(RoleResource.role("role_x"), 
Arrays.asList("--role", "role_x"));
+        assertInvalidation(FunctionResource.root(), 
Collections.singletonList("--all-functions"));
+        assertInvalidation(FunctionResource.keyspace(KEYSPACE), 
Arrays.asList("--functions-in-keyspace", KEYSPACE));
+        assertInvalidation(JMXResource.root(), 
Collections.singletonList("--all-mbeans"));
+        
assertInvalidation(JMXResource.mbean("org.apache.cassandra.auth:type=*"),
+                Arrays.asList("--mbean", "org.apache.cassandra.auth:type=*"));
+    }
+
+    @Test
+    public void testInvalidatePermissionsForFunction() throws Throwable
+    {
+        String keyspaceAndFunctionName = createFunction(KEYSPACE, "int",
+                " CREATE FUNCTION %s (val int)" +
+                        " CALLED ON NULL INPUT" +
+                        " RETURNS int" +
+                        " LANGUAGE java" +
+                        " AS 'return val;'");
+        String functionName = StringUtils.split(keyspaceAndFunctionName, 
".")[1];
+
+        FunctionResource resource = FunctionResource.function(KEYSPACE, 
functionName, Collections.singletonList(Int32Type.instance));
+        Set<Permission> permissions = resource.applicablePermissions();
+        
DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, 
permissions, resource, ROLE_A);
+        
DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, 
permissions, resource, ROLE_B);
+
+        assertInvalidation(resource,
+                Arrays.asList("--functions-in-keyspace", KEYSPACE, 
"--function", functionName + "[Int32Type]"));
+    }
+
+    private void assertInvalidation(IResource resource, List<String> options)
+    {
+        Set<Permission> dataPermissions = resource.applicablePermissions();
+
+        AuthenticatedUser role = new AuthenticatedUser(ROLE_A.getRoleName());
+
+        // cache permission
+        role.getPermissions(resource);
+        long originalReadsCount = getRolePermissionsReadCount();
+
+        // enure permission is cached
+        assertThat(role.getPermissions(resource)).isEqualTo(dataPermissions);
+        
assertThat(originalReadsCount).isEqualTo(getRolePermissionsReadCount());
+
+        // invalidate permission
+        List<String> args = new ArrayList<>();
+        args.add("invalidatepermissionscache");
+        args.add(ROLE_A.getRoleName());
+        args.addAll(options);
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool(args);
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure permission is reloaded
+        assertThat(role.getPermissions(resource)).isEqualTo(dataPermissions);
+        
assertThat(originalReadsCount).isLessThan(getRolePermissionsReadCount());
+    }
+
+    @Test
+    public void testInvalidatePermissionsForAllUsers()
+    {
+        DataResource rootDataResource = DataResource.root();
+        Set<Permission> dataPermissions = 
rootDataResource.applicablePermissions();
+
+        AuthenticatedUser roleA = new AuthenticatedUser(ROLE_A.getRoleName());
+        AuthenticatedUser roleB = new AuthenticatedUser(ROLE_B.getRoleName());
+
+        // cache permissions
+        roleA.getPermissions(rootDataResource);
+        roleB.getPermissions(rootDataResource);
+        long originalReadsCount = getRolePermissionsReadCount();
+
+        // enure permissions are cached
+        
assertThat(roleA.getPermissions(rootDataResource)).isEqualTo(dataPermissions);
+        
assertThat(roleB.getPermissions(rootDataResource)).isEqualTo(dataPermissions);
+        
assertThat(originalReadsCount).isEqualTo(getRolePermissionsReadCount());
+
+        // invalidate both permissions
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidatepermissionscache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure permission for roleA is reloaded
+        
assertThat(roleA.getPermissions(rootDataResource)).isEqualTo(dataPermissions);
+        long readsCountAfterFirstReLoad = getRolePermissionsReadCount();
+        assertThat(originalReadsCount).isLessThan(readsCountAfterFirstReLoad);
+
+        // ensure permission for roleB is reloaded
+        
assertThat(roleB.getPermissions(rootDataResource)).isEqualTo(dataPermissions);
+        long readsCountAfterSecondReLoad = getRolePermissionsReadCount();
+        
assertThat(readsCountAfterFirstReLoad).isLessThan(readsCountAfterSecondReLoad);
+    }
+
+    private String wrapByDefaultNodetoolMessage(String s)
+    {
+        return "nodetool: " + s + "\nSee 'nodetool help' or 'nodetool help 
<command>'.\n";
+    }
+}
diff --git 
a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java
new file mode 100644
index 0000000..80596b3
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateRolesCacheTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.tools.nodetool;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.apache.cassandra.OrderedJUnit4ClassRunner;
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(OrderedJUnit4ClassRunner.class)
+public class InvalidateRolesCacheTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        SchemaLoader.prepareServer();
+        AuthTestUtils.LocalCassandraRoleManager roleManager = new 
AuthTestUtils.LocalCassandraRoleManager();
+        SchemaLoader.setupAuth(roleManager,
+                new AuthTestUtils.LocalPasswordAuthenticator(),
+                new AuthTestUtils.LocalCassandraAuthorizer(),
+                new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, 
AuthTestUtils.getLoginRoleOprions());
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, 
AuthTestUtils.getLoginRoleOprions());
+
+        startJMXServer();
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if 
necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", 
"invalidaterolescache");
+        tool.assertOnCleanExit();
+
+        String help =   "NAME\n" +
+                        "        nodetool invalidaterolescache - Invalidate 
the roles cache\n" +
+                        "\n" +
+                        "SYNOPSIS\n" +
+                        "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
+                        "                [(-pp | --print-port)] [(-pw 
<password> | --password <password>)]\n" +
+                        "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
+                        "                [(-u <username> | --username 
<username>)] invalidaterolescache [--]\n" +
+                        "                [<role>...]\n" +
+                        "\n" +
+                        "OPTIONS\n" +
+                        "        -h <host>, --host <host>\n" +
+                        "            Node hostname or ip address\n" +
+                        "\n" +
+                        "        -p <port>, --port <port>\n" +
+                        "            Remote jmx agent port number\n" +
+                        "\n" +
+                        "        -pp, --print-port\n" +
+                        "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
+                        "\n" +
+                        "        -pw <password>, --password <password>\n" +
+                        "            Remote jmx agent password\n" +
+                        "\n" +
+                        "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
+                        "            Path to the JMX password file\n" +
+                        "\n" +
+                        "        -u <username>, --username <username>\n" +
+                        "            Remote jmx agent username\n" +
+                        "\n" +
+                        "        --\n" +
+                        "            This option can be used to separate 
command-line options from the\n" +
+                        "            list of argument, (useful when arguments 
might be mistaken for\n" +
+                        "            command-line options\n" +
+                        "\n" +
+                        "        [<role>...]\n" +
+                        "            List of roles to invalidate. By default, 
all roles\n" +
+                        "\n" +
+                        "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testInvalidateSingleRole()
+    {
+        AuthenticatedUser role = new AuthenticatedUser(ROLE_A.getRoleName());
+
+        // cache role
+        role.canLogin();
+        long originalReadsCount = getRolesReadCount();
+
+        // enure role is cached
+        assertThat(role.canLogin()).isTrue();
+        assertThat(originalReadsCount).isEqualTo(getRolesReadCount());
+
+        // invalidate role
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidaterolescache", ROLE_A.getRoleName());
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure role is reloaded
+        assertThat(role.canLogin()).isTrue();
+        assertThat(originalReadsCount).isLessThan(getRolesReadCount());
+    }
+
+    @Test
+    public void testInvalidateAllRoles()
+    {
+        AuthenticatedUser roleA = new AuthenticatedUser(ROLE_A.getRoleName());
+        AuthenticatedUser roleB = new AuthenticatedUser(ROLE_B.getRoleName());
+
+        // cache roles
+        roleA.canLogin();
+        roleB.canLogin();
+        long originalReadsCount = getRolesReadCount();
+
+        // enure roles are cached
+        assertThat(roleA.canLogin()).isTrue();
+        assertThat(roleB.canLogin()).isTrue();
+        assertThat(originalReadsCount).isEqualTo(getRolesReadCount());
+
+        // invalidate both roles
+        ToolRunner.ToolResult tool = 
ToolRunner.invokeNodetool("invalidaterolescache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        // ensure role for roleA is reloaded
+        assertThat(roleA.canLogin()).isTrue();
+        long readsCountAfterFirstReLoad = getRolesReadCount();
+        assertThat(originalReadsCount).isLessThan(readsCountAfterFirstReLoad);
+
+        // ensure role for roleB is reloaded
+        assertThat(roleB.canLogin()).isTrue();
+        long readsCountAfterSecondReLoad = getRolesReadCount();
+        
assertThat(readsCountAfterFirstReLoad).isLessThan(readsCountAfterSecondReLoad);
+    }
+}

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to