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

JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 5d163835737 Support high availability for the authority module (#18425)
5d163835737 is described below

commit 5d163835737190c94aaa76c51f4d5b04c42aa6d7
Author: CYB <[email protected]>
AuthorDate: Tue Aug 11 09:39:57 2026 +0800

    Support high availability for the authority module (#18425)
---
 .../relational/it/schema/IoTDBTableAuthHAIT.java   | 670 +++++++++++++++++++++
 .../iotdb/confignode/i18n/ProcedureMessages.java   |   9 +-
 .../iotdb/confignode/i18n/ProcedureMessages.java   |   7 +-
 .../client/async/CnToDnAsyncRequestType.java       |   3 +
 .../CnToDnInternalServiceAsyncRequestManager.java  |   6 +
 .../impl/sync/AuthOperationProcedure.java          |  86 ++-
 .../apache/iotdb/db/i18n/DataNodeMiscMessages.java |   6 -
 .../apache/iotdb/db/i18n/DataNodeMiscMessages.java |   5 -
 .../org/apache/iotdb/db/auth/AuthorityChecker.java |   1 +
 .../apache/iotdb/db/auth/BasicAuthorityCache.java  |   9 -
 .../iotdb/db/auth/ClusterAuthorityFetcher.java     |  68 +--
 .../apache/iotdb/db/auth/IAuthorityFetcher.java    |   2 -
 .../impl/DataNodeInternalRPCServiceImpl.java       |   1 -
 .../schemaengine/lease/MetadataLeaseManager.java   |   4 +-
 .../db/auth/ClusterAuthorityFetcherLeaseTest.java  |  24 +-
 15 files changed, 766 insertions(+), 135 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableAuthHAIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableAuthHAIT.java
new file mode 100644
index 00000000000..6422ca01826
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableAuthHAIT.java
@@ -0,0 +1,670 @@
+/*
+ * 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.iotdb.relational.it.schema;
+
+import org.apache.iotdb.consensus.ConsensusFactory;
+import org.apache.iotdb.isession.SessionConfig;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.env.BaseEnv;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.concurrent.Callable;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableClusterIT.class})
+public class IoTDBTableAuthHAIT {
+
+  private final Logger LOGGER = 
LoggerFactory.getLogger(IoTDBTableAuthHAIT.class);
+
+  private static final String DATABASE_NAME = "test_auth_db";
+  private static final String TABLE_NAME = "test_tb";
+  private static final String TREE_DB_NAME = "root.test_auth_tree";
+
+  private static final String TEST_USER = "test_user";
+  private static final String TEST_USER_INITIAL_PWD = "Test_user@336699!";
+  private static final String TEST_USER_NEW_PWD = "New_pass@2024Pwd!";
+
+  private static final String HA_USER = "ha_user";
+  private static final String HA_USER_PWD = "Ha_user@123456!";
+
+  private static final String RENAMED_USER = "renamed_user";
+  private static final String TEST_ROLE = "test_role";
+  private static final String HA_ROLE = "ha_role";
+
+  private static void initCluster() {
+    EnvFactory.getEnv()
+        .getConfig()
+        .getCommonConfig()
+        .setConfigNodeConsensusProtocolClass(ConsensusFactory.RATIS_CONSENSUS)
+        
.setSchemaRegionConsensusProtocolClass(ConsensusFactory.RATIS_CONSENSUS)
+        .setDataRegionConsensusProtocolClass(ConsensusFactory.IOT_CONSENSUS)
+        .setSchemaReplicationFactor(3)
+        .setDataReplicationFactor(2);
+
+    
EnvFactory.getEnv().getConfig().getConfigNodeConfig().setMetadataLeaseFenceMs(20000);
+    EnvFactory.getEnv().initClusterEnvironment(1, 3);
+  }
+
+  private static void cleanCluster() {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  private void preTestData(
+      final Statement rootStmt, final String databaseName, final String 
tableName)
+      throws SQLException {
+    // Table model setup
+    rootStmt.execute("CREATE DATABASE " + databaseName);
+    rootStmt.execute("USE " + databaseName);
+    rootStmt.execute("CREATE TABLE " + tableName + " (dev STRING TAG, s1 INT32 
FIELD)");
+    rootStmt.execute("INSERT INTO " + tableName + "(time, dev, s1) VALUES(1, 
'dev1', 100)");
+
+    // Tree model setup
+    rootStmt.execute("SET SQL_DIALECT=tree");
+    rootStmt.execute("CREATE TIMESERIES " + TREE_DB_NAME + ".dev1.s1 WITH 
DATATYPE=INT32");
+    rootStmt.execute("INSERT INTO " + TREE_DB_NAME + ".dev1(time, s1) 
VALUES(1, 100)");
+    rootStmt.execute("SET SQL_DIALECT=table");
+
+    // Create user and role
+    rootStmt.execute("CREATE USER " + TEST_USER + " '" + TEST_USER_INITIAL_PWD 
+ "'");
+    rootStmt.execute("CREATE ROLE " + TEST_ROLE);
+  }
+
+  @Test
+  public void testAuthHAWithOneDataNodeDown() throws Exception {
+    initCluster();
+    try {
+      final DataNodeWrapper liveDN0 = 
EnvFactory.getEnv().getDataNodeWrapper(0);
+      final DataNodeWrapper liveDN1 = 
EnvFactory.getEnv().getDataNodeWrapper(1);
+      final DataNodeWrapper victimDN2 = 
EnvFactory.getEnv().getDataNodeWrapper(2);
+
+      // Prepare data (all 3 DNs alive)
+      try (final Connection rootConn =
+              EnvFactory.getEnv()
+                  .getConnection(
+                      liveDN0,
+                      SessionConfig.DEFAULT_USER,
+                      SessionConfig.DEFAULT_PASSWORD,
+                      BaseEnv.TABLE_SQL_DIALECT);
+          final Statement rootStmt = rootConn.createStatement()) {
+        preTestData(rootStmt, DATABASE_NAME, TABLE_NAME);
+      }
+
+      // Take one DataNode down
+      victimDN2.stop();
+      Assert.assertFalse("victim DataNode should be stopped", 
victimDN2.isAlive());
+
+      // Execute all HA tests via live DN-0, verify effects via DN-1
+      try (final Connection rootConn =
+              EnvFactory.getEnv()
+                  .getConnection(
+                      liveDN0,
+                      SessionConfig.DEFAULT_USER,
+                      SessionConfig.DEFAULT_PASSWORD,
+                      BaseEnv.TABLE_SQL_DIALECT);
+          final Statement rootStmt = rootConn.createStatement()) {
+
+        executeUserManagementHA(rootStmt, liveDN0, liveDN1);
+        executeRoleManagementHA(rootStmt);
+        executeTablePermissionHA(rootStmt, liveDN0, liveDN1);
+        executeTableRoleBasedPermissionHA(rootStmt, liveDN1);
+        executeTreePermissionHA(rootStmt, liveDN0, liveDN1);
+        executeTreeRoleBasedPermissionHA(rootStmt, liveDN1);
+        executeCleanup(rootStmt, liveDN1);
+      }
+    } finally {
+      cleanCluster();
+    }
+  }
+
+  // ==================== User Management ====================
+
+  private void executeUserManagementHA(
+      final Statement rootStmt, final DataNodeWrapper liveDN0, final 
DataNodeWrapper liveDN1)
+      throws Exception {
+
+    // Step 1: CREATE USER
+    LOGGER.info("1. start to test high availability of CREATE USER");
+    assertStatementEffect(
+        rootStmt,
+        "CREATE USER " + HA_USER + " '" + HA_USER_PWD + "'",
+        () -> userExists(rootStmt, HA_USER),
+        "CREATE USER must succeed");
+
+    // Step 2: ALTER USER SET PASSWORD
+    LOGGER.info("2. start to test high availability of ALTER USER SET 
PASSWORD");
+    rootStmt.execute("ALTER USER " + TEST_USER + " SET PASSWORD '" + 
TEST_USER_NEW_PWD + "'");
+    // Verify: old password fails, new password succeeds on DN-1
+    assertConnectionFails(
+        liveDN1,
+        TEST_USER,
+        TEST_USER_INITIAL_PWD,
+        BaseEnv.TABLE_SQL_DIALECT,
+        "old password should fail after password change");
+    try (Connection newConn =
+            EnvFactory.getEnv()
+                .getConnection(liveDN1, TEST_USER, TEST_USER_NEW_PWD, 
BaseEnv.TABLE_SQL_DIALECT);
+        Statement s = newConn.createStatement()) {
+      s.executeQuery("LIST USER");
+    }
+
+    // Step 3: ALTER USER RENAME TO
+    LOGGER.info("3. start to test high availability of ALTER USER RENAME TO");
+    rootStmt.execute("ALTER USER " + TEST_USER + " RENAME TO " + RENAMED_USER);
+    assertTrue("old user should not exist", !userExists(rootStmt, TEST_USER));
+    assertTrue("new user should exist", userExists(rootStmt, RENAMED_USER));
+    // Verify: old name fails, new name succeeds on DN-1
+    assertConnectionFails(
+        liveDN1,
+        TEST_USER,
+        TEST_USER_NEW_PWD,
+        BaseEnv.TABLE_SQL_DIALECT,
+        "old user name should fail after rename");
+    try (Connection renamedConn =
+            EnvFactory.getEnv()
+                .getConnection(
+                    liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, 
BaseEnv.TABLE_SQL_DIALECT);
+        Statement s = renamedConn.createStatement()) {
+      s.executeQuery("LIST USER");
+    }
+
+    // Step 4: LIST USER
+    LOGGER.info("4. start to test high availability of LIST USER");
+    assertTrue("LIST USER should return results", listHasRows(rootStmt, "LIST 
USER"));
+  }
+
+  // ==================== Role Management ====================
+
+  private void executeRoleManagementHA(final Statement rootStmt) throws 
Exception {
+
+    // Step 6: CREATE ROLE
+    LOGGER.info("5. start to test high availability of CREATE ROLE");
+    assertStatementEffect(
+        rootStmt,
+        "CREATE ROLE " + HA_ROLE,
+        () -> roleExists(rootStmt, HA_ROLE),
+        "CREATE ROLE must succeed");
+
+    // Grant ha_role to renamed_user, then verify
+    rootStmt.execute("GRANT ROLE " + HA_ROLE + " TO " + RENAMED_USER);
+
+    // Step 6: LIST ROLE OF USER renamed_user
+    LOGGER.info("6. start to test high availability of LIST ROLE OF USER");
+    assertTrue(
+        "LIST ROLE OF USER renamed_user should contain ha_role",
+        userHasRole(rootStmt, RENAMED_USER, HA_ROLE));
+  }
+
+  // ==================== Table Model Permission Management 
====================
+
+  private void executeTablePermissionHA(
+      final Statement rootStmt, final DataNodeWrapper liveDN0, final 
DataNodeWrapper liveDN1)
+      throws Exception {
+
+    final String testTable = DATABASE_NAME + "." + TABLE_NAME;
+
+    // Step 8: GRANT ROLE to user
+    LOGGER.info("7. start to test high availability of GRANT ROLE (table 
model)");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT ROLE " + TEST_ROLE + " TO " + RENAMED_USER,
+        () -> userHasRole(rootStmt, RENAMED_USER, TEST_ROLE),
+        "GRANT ROLE must succeed");
+
+    // Step 9: GRANT SELECT ON TABLE
+    LOGGER.info("8. start to test high availability of GRANT SELECT ON TABLE");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT SELECT ON TABLE " + testTable + " TO USER " + RENAMED_USER,
+        () -> userHasPrivilege(rootStmt, RENAMED_USER, "SELECT"),
+        "GRANT SELECT ON TABLE must succeed");
+    // Verify: SELECT succeeds on DN-1
+    tableUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, true);
+
+    // Step 10: Verify NO INSERT permission (never granted)
+    LOGGER.info("9. start to test NO INSERT permission enforcement (table 
model)");
+    tableUserInsert(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, false);
+
+    // Step 11: GRANT INSERT ON TABLE
+    LOGGER.info("10. start to test high availability of GRANT INSERT ON 
TABLE");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT INSERT ON TABLE " + testTable + " TO USER " + RENAMED_USER,
+        () -> userHasPrivilege(rootStmt, RENAMED_USER, "INSERT"),
+        "GRANT INSERT ON TABLE must succeed");
+    tableUserInsert(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, true);
+
+    // Step 12: REVOKE INSERT ON TABLE
+    LOGGER.info("11. start to test high availability of REVOKE INSERT ON 
TABLE");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE INSERT ON TABLE " + testTable + " FROM USER " + RENAMED_USER,
+        () -> !userHasPrivilege(rootStmt, RENAMED_USER, "INSERT"),
+        "REVOKE INSERT ON TABLE must succeed");
+    tableUserInsert(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, false);
+
+    // Step 13: REVOKE SELECT ON TABLE
+    LOGGER.info("12. start to test high availability of REVOKE SELECT ON 
TABLE");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE SELECT ON TABLE " + testTable + " FROM USER " + RENAMED_USER,
+        () -> !userHasPrivilege(rootStmt, RENAMED_USER, "SELECT"),
+        "REVOKE SELECT ON TABLE must succeed");
+    tableUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, false);
+
+    // Step 14: GRANT SYSTEM
+    LOGGER.info("13. start to test high availability of GRANT SYSTEM");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT SYSTEM TO USER " + RENAMED_USER,
+        () -> userHasPrivilege(rootStmt, RENAMED_USER, "SYSTEM"),
+        "GRANT SYSTEM must succeed");
+
+    // Step 15: REVOKE SYSTEM
+    LOGGER.info("14. start to test high availability of REVOKE SYSTEM");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE SYSTEM FROM USER " + RENAMED_USER,
+        () -> !userHasPrivilege(rootStmt, RENAMED_USER, "SYSTEM"),
+        "REVOKE SYSTEM must succeed");
+  }
+
+  // ==================== Table Model Role-Based Permission 
====================
+
+  private void executeTableRoleBasedPermissionHA(
+      final Statement rootStmt, final DataNodeWrapper liveDN1) throws 
Exception {
+
+    final String testTable = DATABASE_NAME + "." + TABLE_NAME;
+
+    // Step: GRANT SELECT ON TABLE TO ROLE → user inherits via role
+    LOGGER.info("start to test high availability of GRANT SELECT ON TABLE TO 
ROLE (table model)");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT SELECT ON TABLE " + testTable + " TO ROLE " + TEST_ROLE,
+        () -> userHasPrivilege(rootStmt, RENAMED_USER, "SELECT"),
+        "GRANT SELECT ON TABLE TO ROLE must succeed");
+    // renamed_user inherits SELECT via test_role
+    tableUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, true);
+
+    // Step: REVOKE SELECT ON TABLE FROM ROLE → user loses inherited privilege
+    LOGGER.info(
+        "start to test high availability of REVOKE SELECT ON TABLE FROM ROLE 
(table model)");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE SELECT ON TABLE " + testTable + " FROM ROLE " + TEST_ROLE,
+        () -> !userHasPrivilege(rootStmt, RENAMED_USER, "SELECT"),
+        "REVOKE SELECT ON TABLE FROM ROLE must succeed");
+    tableUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, DATABASE_NAME, 
TABLE_NAME, false);
+  }
+
+  // ==================== Tree Model Permission Management ====================
+
+  private void executeTreePermissionHA(
+      final Statement rootStmt, final DataNodeWrapper liveDN0, final 
DataNodeWrapper liveDN1)
+      throws Exception {
+
+    final String treePath = "root.test_auth_tree.**";
+
+    // Step 15: GRANT READ_DATA ON tree path
+    LOGGER.info("15. start to test high availability of GRANT READ_DATA (tree 
model)");
+    rootStmt.execute("SET SQL_DIALECT=tree");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT READ_DATA ON " + treePath + " TO USER " + RENAMED_USER,
+        () -> userHasPrivilege(rootStmt, RENAMED_USER, "READ_DATA"),
+        "GRANT READ_DATA must succeed");
+    // Verify: SELECT succeeds on DN-1
+    treeUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, true);
+
+    // Step 16: Verify NO WRITE permission
+    LOGGER.info("16. start to test NO WRITE permission enforcement (tree 
model)");
+    treeUserInsert(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, false);
+
+    // Step 17: GRANT WRITE_DATA ON tree path
+    LOGGER.info("17. start to test high availability of GRANT WRITE_DATA (tree 
model)");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT WRITE_DATA ON " + treePath + " TO USER " + RENAMED_USER,
+        () -> userHasPrivilege(rootStmt, RENAMED_USER, "WRITE_DATA"),
+        "GRANT WRITE_DATA must succeed");
+    treeUserInsert(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, true);
+
+    // Step 18: REVOKE WRITE_DATA ON tree path
+    LOGGER.info("18. start to test high availability of REVOKE WRITE_DATA 
(tree model)");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE WRITE_DATA ON " + treePath + " FROM USER " + RENAMED_USER,
+        () -> !userHasPrivilege(rootStmt, RENAMED_USER, "WRITE_DATA"),
+        "REVOKE WRITE_DATA must succeed");
+    treeUserInsert(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, false);
+
+    // Step 19: REVOKE READ_DATA ON tree path
+    LOGGER.info("19. start to test high availability of REVOKE READ_DATA (tree 
model)");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE READ_DATA ON " + treePath + " FROM USER " + RENAMED_USER,
+        () -> !userHasPrivilege(rootStmt, RENAMED_USER, "READ_DATA"),
+        "REVOKE READ_DATA must succeed");
+    treeUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, false);
+
+    rootStmt.execute("SET SQL_DIALECT=table");
+  }
+
+  // ==================== Tree Model Role-Based Permission ====================
+
+  private void executeTreeRoleBasedPermissionHA(
+      final Statement rootStmt, final DataNodeWrapper liveDN1) throws 
Exception {
+
+    final String treePath = "root.test_auth_tree.**";
+
+    // Grant READ_DATA to test_role, renamed_user inherits via role
+    LOGGER.info("start to test high availability of GRANT READ_DATA TO ROLE 
(tree model)");
+    rootStmt.execute("SET SQL_DIALECT=tree");
+    assertStatementEffect(
+        rootStmt,
+        "GRANT READ_DATA ON " + treePath + " TO ROLE " + TEST_ROLE,
+        () -> roleHasPrivilege(rootStmt, TEST_ROLE, "READ_DATA"),
+        "GRANT READ_DATA ON TO ROLE must succeed");
+    // renamed_user inherits READ_DATA via test_role
+    assertTrue(
+        "renamed_user should inherit READ_DATA via test_role",
+        userHasPrivilege(rootStmt, RENAMED_USER, "READ_DATA"));
+    treeUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, true);
+
+    // Revoke READ_DATA from test_role → renamed_user loses inherited privilege
+    LOGGER.info("start to test high availability of REVOKE READ_DATA FROM ROLE 
(tree model)");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE READ_DATA ON " + treePath + " FROM ROLE " + TEST_ROLE,
+        () -> !roleHasPrivilege(rootStmt, TEST_ROLE, "READ_DATA"),
+        "REVOKE READ_DATA ON FROM ROLE must succeed");
+    assertTrue(
+        "renamed_user should lose READ_DATA after revoke from test_role",
+        !userHasPrivilege(rootStmt, RENAMED_USER, "READ_DATA"));
+    treeUserSelect(liveDN1, RENAMED_USER, TEST_USER_NEW_PWD, false);
+
+    rootStmt.execute("SET SQL_DIALECT=table");
+  }
+
+  // ==================== Cleanup ====================
+
+  private void executeCleanup(final Statement rootStmt, final DataNodeWrapper 
liveDN1)
+      throws Exception {
+
+    // Step 21: REVOKE ROLE
+    LOGGER.info("20. start to test high availability of REVOKE ROLE");
+    assertStatementEffect(
+        rootStmt,
+        "REVOKE ROLE " + TEST_ROLE + " FROM " + RENAMED_USER,
+        () -> !userHasRole(rootStmt, RENAMED_USER, TEST_ROLE),
+        "REVOKE ROLE must succeed");
+
+    // Step 22: DROP ROLE
+    LOGGER.info("21. start to test high availability of DROP ROLE");
+    assertStatementEffect(
+        rootStmt,
+        "DROP ROLE " + HA_ROLE,
+        () -> !roleExists(rootStmt, HA_ROLE),
+        "DROP ROLE must succeed");
+
+    // Step 23: DROP USER renamed_user
+    LOGGER.info("22. start to test high availability of DROP USER 
renamed_user");
+    assertStatementEffect(
+        rootStmt,
+        "DROP USER " + RENAMED_USER,
+        () -> !userExists(rootStmt, RENAMED_USER),
+        "DROP USER renamed_user must succeed");
+
+    // Step 24: DROP USER ha_user
+    LOGGER.info("23. start to test high availability of DROP USER ha_user");
+    assertStatementEffect(
+        rootStmt,
+        "DROP USER " + HA_USER,
+        () -> !userExists(rootStmt, HA_USER),
+        "DROP USER ha_user must succeed");
+  }
+
+  // ==================== Verification Helpers ====================
+
+  private void assertStatementEffect(
+      final Statement statement,
+      final String sql,
+      final Callable<Boolean> effect,
+      final String message)
+      throws Exception {
+    statement.execute(sql);
+    assertTrue(message, effect.call());
+  }
+
+  private boolean userExists(final Statement stmt, final String userName) 
throws SQLException {
+    try (final ResultSet rs = stmt.executeQuery("LIST USER")) {
+      while (rs.next()) {
+        if (userName.equalsIgnoreCase(rs.getString(2))) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean roleExists(final Statement stmt, final String roleName) 
throws SQLException {
+    try (final ResultSet rs = stmt.executeQuery("LIST ROLE")) {
+      while (rs.next()) {
+        if (roleName.equalsIgnoreCase(rs.getString(1))) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean listHasRows(final Statement stmt, final String sql) throws 
SQLException {
+    try (final ResultSet rs = stmt.executeQuery(sql)) {
+      return rs.next();
+    }
+  }
+
+  private boolean userHasRole(final Statement stmt, final String userName, 
final String roleName)
+      throws SQLException {
+    try (final ResultSet rs = stmt.executeQuery("LIST ROLE OF USER " + 
userName)) {
+      while (rs.next()) {
+        if (roleName.equalsIgnoreCase(rs.getString(1))) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean userHasPrivilege(
+      final Statement stmt, final String userName, final String privilege) 
throws SQLException {
+    try (final ResultSet rs = stmt.executeQuery("LIST PRIVILEGES OF USER " + 
userName)) {
+      while (rs.next()) {
+        // LIST PRIVILEGES columns: Role, Scope, Privileges, GrantOption
+        if (privilege.equalsIgnoreCase(rs.getString(3))) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean roleHasPrivilege(
+      final Statement stmt, final String roleName, final String privilege) 
throws SQLException {
+    try (final ResultSet rs = stmt.executeQuery("LIST PRIVILEGES OF ROLE " + 
roleName)) {
+      while (rs.next()) {
+        // LIST PRIVILEGES columns: Role, Scope, Privileges, GrantOption
+        if (privilege.equalsIgnoreCase(rs.getString(3))) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private void assertConnectionFails(
+      final DataNodeWrapper dn,
+      final String user,
+      final String password,
+      final String sqlDialect,
+      final String message) {
+    try {
+      final Connection conn = EnvFactory.getEnv().getConnection(dn, user, 
password, sqlDialect);
+      conn.close();
+      Assert.fail(message + " — expected connection failure but succeeded");
+    } catch (final SQLException e) {
+      // Expected
+    }
+  }
+
+  // ==================== Table Model User Operation Helpers 
====================
+
+  private void tableUserSelect(
+      final DataNodeWrapper dn,
+      final String user,
+      final String password,
+      final String databaseName,
+      final String tableName,
+      final boolean expectSuccess)
+      throws SQLException {
+    final String sql = "SELECT * FROM " + tableName;
+    if (expectSuccess) {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TABLE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement()) {
+        stmt.execute("USE " + databaseName);
+        try (final ResultSet rs = stmt.executeQuery(sql)) {
+          assertTrue("SELECT should succeed", rs.next());
+        }
+      }
+    } else {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TABLE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement()) {
+        stmt.execute("USE " + databaseName);
+        stmt.executeQuery(sql);
+        Assert.fail("SELECT should fail");
+      } catch (final SQLException e) {
+        assertTrue(
+            e.getMessage(),
+            e.getMessage().contains("No permissions") || 
e.getMessage().contains("Access Denied"));
+      }
+    }
+  }
+
+  private void tableUserInsert(
+      final DataNodeWrapper dn,
+      final String user,
+      final String password,
+      final String databaseName,
+      final String tableName,
+      final boolean expectSuccess)
+      throws SQLException {
+    final String sql = "INSERT INTO " + tableName + "(time, dev, s1) VALUES(2, 
'dev2', 200)";
+    if (expectSuccess) {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TABLE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement()) {
+        stmt.execute("USE " + databaseName);
+        stmt.execute(sql);
+      }
+    } else {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TABLE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement()) {
+        stmt.execute("USE " + databaseName);
+        stmt.execute(sql);
+        Assert.fail("INSERT should fail");
+      } catch (final SQLException e) {
+        assertTrue(
+            e.getMessage(),
+            e.getMessage().contains("No permissions") || 
e.getMessage().contains("Access Denied"));
+      }
+    }
+  }
+
+  // ==================== Tree Model User Operation Helpers 
====================
+
+  private void treeUserSelect(
+      final DataNodeWrapper dn,
+      final String user,
+      final String password,
+      final boolean expectSuccess)
+      throws SQLException {
+    final String sql = "SELECT * FROM " + TREE_DB_NAME + ".dev1";
+    if (expectSuccess) {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TREE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement();
+          final ResultSet rs = stmt.executeQuery(sql)) {
+        assertTrue("Tree SELECT should succeed", rs.next());
+      }
+    } else {
+      // Tree model returns empty result set on permission denial, no 
exception thrown
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TREE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement();
+          final ResultSet rs = stmt.executeQuery(sql)) {
+        Assert.assertFalse("Tree SELECT should return empty result set", 
rs.next());
+      }
+    }
+  }
+
+  private void treeUserInsert(
+      final DataNodeWrapper dn,
+      final String user,
+      final String password,
+      final boolean expectSuccess)
+      throws SQLException {
+    final String sql = "INSERT INTO " + TREE_DB_NAME + ".dev1(time, s1) 
VALUES(2, 200)";
+    if (expectSuccess) {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TREE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement()) {
+        stmt.execute(sql);
+      }
+    } else {
+      try (final Connection conn =
+              EnvFactory.getEnv().getConnection(dn, user, password, 
BaseEnv.TREE_SQL_DIALECT);
+          final Statement stmt = conn.createStatement()) {
+        stmt.execute(sql);
+        Assert.fail("Tree INSERT should fail");
+      } catch (final SQLException e) {
+        assertTrue(
+            e.getMessage(),
+            e.getMessage().contains("No permissions") || 
e.getMessage().contains("Access Denied"));
+      }
+    }
+  }
+}
diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index b29244d4117..e04460375f9 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -93,6 +93,9 @@ public final class ProcedureMessages {
   public static final String AUTHENTICATION_FAILED = "Authentication failed.";
   public static final String AUTH_PROCEDURE_CLEAN_DATANODE_CACHE_SUCCESSFULLY =
       "Auth procedure: clean datanode cache successfully";
+  public static final String AUTH_PROCEDURE_CACHE_INVALIDATION_FAILED =
+      "Auth plan has been committed, but DataNode permission cache 
invalidation failed. "
+          + "Some DataNodes may have stale permissions; please clear their 
permission cache manually.";
   public static final String BEGIN_TO_CHANGE_DATANODE_STATUS_NODESTATUSMAP =
       "{}, Begin to change DataNode status, nodeStatusMap: {}";
   public static final String 
BEGIN_TO_STOP_DATANODES_AND_KILL_THE_DATANODE_PROCESS =
@@ -356,7 +359,7 @@ public final class ProcedureMessages {
   public static final String 
ERROR_IN_DESERIALIZE_PROCID_THIS_PROCEDURE_WILL_BE_IGNORED_IT =
       "Error in deserialize {} (procID {}). This procedure will be ignored. It 
may belong to old version and cannot be used now.";
   public static final String EXECUTE_AUTH_PLAN_SUCCESS_TO_INVALIDATE_DATANODES 
=
-      "Execute auth plan {} success. To invalidate datanodes: {}";
+      "Execute auth plan {} success.";
   public static final String 
EXECUTING_ON_REGION_FOR_COLUMN_IN_WHEN_DROPPING_COLUMN =
       "Executing on region for column {} in {}.{} when dropping column";
   public static final String FAILED_TO_ACTIVE_CQ_BECAUSE_OF_NO_SUCH_CQ =
@@ -551,8 +554,6 @@ public final class ProcedureMessages {
       "Fail to drop trigger [%s] at STATE [%s]";
   public static final String FAIL_TO_DROP_TRIGGER_ON_DATA_NODES =
       "Fail to drop trigger [%s] on Data Nodes";
-  public static final String FAIL_TO_EXECUTE_PLAN_AT_STATE =
-      "Fail to execute plan [%s] at state[%s]";
   public static final String FAIL_TO_REMOVE_AINODE_AT_STATE =
       "Fail to remove AINode [%s] at STATE [%s], %s";
   public static final String FAIL_TO_REMOVE_AINODE_ON_CONFIG_NODES =
@@ -860,8 +861,6 @@ public final class ProcedureMessages {
       "Retrievable error trying to create pipe plugin [{}], state: {}";
   public static final String 
RETRIEVABLE_ERROR_TRYING_TO_DROP_PIPE_PLUGIN_STATE =
       "Retrievable error trying to drop pipe plugin [{}], state: {}";
-  public static final String RETRIEVABLE_ERROR_TRYING_TO_EXECUTE_PLAN_STATE =
-      "Retrievable error trying to execute plan {}, state: {}";
   public static final String RETRIEVABLE_ERROR_TRYING_TO_REMOVE_AINODE_STATE =
       "Retrievable error trying to remove AINode [{}], state [{}]";
   public static final String ROLLBACK_CREATETABLE_COSTS_MS = "Rollback 
CreateTable-{} costs {}ms.";
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 0569e1b6261..3014e1c0076 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -91,6 +91,8 @@ public final class ProcedureMessages {
   public static final String AUTHENTICATION_FAILED = "认证失败。";
   public static final String AUTH_PROCEDURE_CLEAN_DATANODE_CACHE_SUCCESSFULLY =
       "Auth procedure:成功清理 datanode 缓存";
+  public static final String AUTH_PROCEDURE_CACHE_INVALIDATION_FAILED =
+      "权限计划已提交,但 DataNode 权限缓存失效失败。部分 DataNode 可能仍持有过期权限,请手动清理权限缓存。";
   public static final String BEGIN_TO_CHANGE_DATANODE_STATUS_NODESTATUSMAP =
       "{}, 开始修改 DataNode 状态,nodeStatusMap:{}";
   public static final String 
BEGIN_TO_STOP_DATANODES_AND_KILL_THE_DATANODE_PROCESS =
@@ -344,7 +346,7 @@ public final class ProcedureMessages {
   public static final String 
ERROR_IN_DESERIALIZE_PROCID_THIS_PROCEDURE_WILL_BE_IGNORED_IT =
       "反序列化 {}(procID {})出错。该 procedure 将被忽略。它可能属于旧版本,目前无法使用。";
   public static final String EXECUTE_AUTH_PLAN_SUCCESS_TO_INVALIDATE_DATANODES 
=
-      "执行 auth plan {} 成功。使 datanode 缓存失效:{}";
+      "执行 auth plan {} 成功。";
   public static final String 
EXECUTING_ON_REGION_FOR_COLUMN_IN_WHEN_DROPPING_COLUMN =
       "删除列时在表 {}.{} 中列 {} 对应的 region 上执行";
   public static final String FAILED_TO_ACTIVE_CQ_BECAUSE_OF_NO_SUCH_CQ =
@@ -531,7 +533,6 @@ public final class ProcedureMessages {
       "重试 {} 次后删除 pipe plugin [{}] 仍失败";
   public static final String FAIL_TO_DROP_TRIGGER_AT_STATE = "在 STATE [%s] 处删除 
trigger [%s] 失败";
   public static final String FAIL_TO_DROP_TRIGGER_ON_DATA_NODES = "在 DataNode 
上删除 trigger [%s] 失败";
-  public static final String FAIL_TO_EXECUTE_PLAN_AT_STATE = "在 state[%s] 处执行 
plan [%s] 失败";
   public static final String FAIL_TO_REMOVE_AINODE_AT_STATE = "在 STATE [%s] 
处移除 AINode [%s] 失败,%s";
   public static final String FAIL_TO_REMOVE_AINODE_ON_CONFIG_NODES =
       "在 ConfigNode [%s] 上移除 [%s] 个 AINode 失败";
@@ -818,8 +819,6 @@ public final class ProcedureMessages {
       "尝试创建 pipe plugin [{}] 时发生可重试错误,状态:{}";
   public static final String 
RETRIEVABLE_ERROR_TRYING_TO_DROP_PIPE_PLUGIN_STATE =
       "尝试删除 pipe plugin [{}] 时发生可重试错误,状态:{}";
-  public static final String RETRIEVABLE_ERROR_TRYING_TO_EXECUTE_PLAN_STATE =
-      "尝试执行 plan {} 时发生可重试错误,状态:{}";
   public static final String RETRIEVABLE_ERROR_TRYING_TO_REMOVE_AINODE_STATE =
       "尝试移除 AINode [{}] 时发生可重试错误,状态 [{}]";
   public static final String ROLLBACK_CREATETABLE_COSTS_MS = "Rollback 
CreateTable-{} costs {}ms.";
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
index 2946884e373..0749ba5f9b0 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
@@ -138,4 +138,7 @@ public enum CnToDnAsyncRequestType {
   // audit log and event write-back
   INSERT_RECORD,
   ENABLE_SEPARATION_OF_ADMIN_POWERS,
+
+  // authority
+  INVALIDATE_PERMISSION_CACHE,
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
index 4f66330f6ec..7ca698afa2c 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
@@ -82,6 +82,7 @@ import 
org.apache.iotdb.mpp.rpc.thrift.TInactiveTriggerInstanceReq;
 import org.apache.iotdb.mpp.rpc.thrift.TInvalidateCacheReq;
 import org.apache.iotdb.mpp.rpc.thrift.TInvalidateColumnCacheReq;
 import org.apache.iotdb.mpp.rpc.thrift.TInvalidateMatchedSchemaCacheReq;
+import org.apache.iotdb.mpp.rpc.thrift.TInvalidatePermissionCacheReq;
 import org.apache.iotdb.mpp.rpc.thrift.TInvalidateTableCacheReq;
 import org.apache.iotdb.mpp.rpc.thrift.TKillQueryInstanceReq;
 import org.apache.iotdb.mpp.rpc.thrift.TNotifyRegionMigrationReq;
@@ -527,6 +528,11 @@ public class CnToDnInternalServiceAsyncRequestManager
         CnToDnAsyncRequestType.GET_BUILTIN_SERVICE,
         (req, client, handler) ->
             client.getBuiltInService((GetBuiltInExternalServiceRPCHandler) 
handler));
+    actionMapBuilder.put(
+        CnToDnAsyncRequestType.INVALIDATE_PERMISSION_CACHE,
+        (req, client, handler) ->
+            client.invalidatePermissionCache(
+                (TInvalidatePermissionCacheReq) req, 
(DataNodeTSStatusRPCHandler) handler));
   }
 
   @Override
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/sync/AuthOperationProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/sync/AuthOperationProcedure.java
index 7ae95858ac6..e3ff2c08188 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/sync/AuthOperationProcedure.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/sync/AuthOperationProcedure.java
@@ -20,21 +20,25 @@
 package org.apache.iotdb.confignode.procedure.impl.sync;
 
 import org.apache.iotdb.common.rpc.thrift.TDataNodeConfiguration;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.conf.CommonConfig;
 import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.exception.IoTDBException;
 import org.apache.iotdb.commons.utils.ThriftCommonsSerDeUtils;
-import org.apache.iotdb.confignode.client.sync.CnToDnSyncRequestType;
-import org.apache.iotdb.confignode.client.sync.SyncDataNodeClientPool;
+import org.apache.iotdb.confignode.client.async.CnToDnAsyncRequestType;
+import 
org.apache.iotdb.confignode.client.async.CnToDnInternalServiceAsyncRequestManager;
+import 
org.apache.iotdb.confignode.client.async.handlers.DataNodeAsyncRequestContext;
 import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
 import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType;
 import org.apache.iotdb.confignode.consensus.request.write.auth.AuthorPlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.pipe.payload.PipeEnrichedPlan;
 import org.apache.iotdb.confignode.i18n.ProcedureMessages;
+import org.apache.iotdb.confignode.manager.lease.ClusterCachePropagator;
 import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
 import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
 import org.apache.iotdb.confignode.procedure.impl.node.AbstractNodeProcedure;
+import org.apache.iotdb.confignode.procedure.impl.schema.SchemaUtils;
 import 
org.apache.iotdb.confignode.procedure.state.auth.AuthOperationProcedureState;
 import org.apache.iotdb.confignode.procedure.store.ProcedureType;
 import org.apache.iotdb.consensus.exception.ConsensusException;
@@ -50,8 +54,8 @@ import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import static 
org.apache.iotdb.confignode.procedure.state.auth.AuthOperationProcedureState.DATANODE_AUTHCACHE_INVALIDING;
@@ -68,7 +72,6 @@ public class AuthOperationProcedure extends 
AbstractNodeProcedure<AuthOperationP
   private static final String CONSENSUS_WRITE_ERROR =
       
ProcedureMessages.FAILED_IN_THE_WRITE_API_EXECUTING_THE_CONSENSUS_LAYER_DUE;
 
-  private static final int RETRY_THRESHOLD = 2;
   private static final CommonConfig commonConfig = 
CommonDescriptor.getInstance().getConfig();
 
   private final List<Pair<TDataNodeConfiguration, Long>> dataNodesToInvalid = 
new ArrayList<>();
@@ -97,54 +100,40 @@ public class AuthOperationProcedure extends 
AbstractNodeProcedure<AuthOperationP
           writePlan(env);
           return Flow.HAS_MORE_STATE;
         case DATANODE_AUTHCACHE_INVALIDING:
-          TInvalidatePermissionCacheReq req = new 
TInvalidatePermissionCacheReq();
-          TSStatus status;
-          req.setUsername(user);
-          req.setRoleName(role);
+          TInvalidatePermissionCacheReq req = new 
TInvalidatePermissionCacheReq(user, role);
           if (plan.getAuthorType() == ConfigPhysicalPlanType.AccountUnlock
               || plan.getAuthorType() == 
ConfigPhysicalPlanType.RAccountUnlock) {
             // For account unlock, role carries the optional login address.
             req.setNeedDisconnect(true);
           }
-          Iterator<Pair<TDataNodeConfiguration, Long>> it = 
dataNodesToInvalid.iterator();
-          while (it.hasNext()) {
-            Pair<TDataNodeConfiguration, Long> pair = it.next();
-            if (pair.getRight() + this.timeoutMS < System.currentTimeMillis()) 
{
-              it.remove();
-              continue;
-            }
-            status =
-                (TSStatus)
-                    SyncDataNodeClientPool.getInstance()
-                        .sendSyncRequestToDataNodeWithRetry(
-                            pair.getLeft().getLocation().getInternalEndPoint(),
-                            req,
-                            CnToDnSyncRequestType.INVALIDATE_PERMISSION_CACHE);
-            if (status.getCode() == 
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
-              it.remove();
-            }
-          }
-          if (dataNodesToInvalid.isEmpty()) {
+          final boolean proceeded =
+              new 
ClusterCachePropagator(SchemaUtils.filterFencedDataNode(env.getConfigManager()))
+                  .propagate(targets -> broadcastAuthorityCache(req, targets));
+
+          if (proceeded) {
             
LOGGER.info(ProcedureMessages.AUTH_PROCEDURE_CLEAN_DATANODE_CACHE_SUCCESSFULLY);
             return Flow.NO_MORE_STATE;
-          } else {
-            
setNextState(AuthOperationProcedureState.DATANODE_AUTHCACHE_INVALIDING);
           }
-          break;
+          
LOGGER.warn(ProcedureMessages.AUTH_PROCEDURE_CACHE_INVALIDATION_FAILED);
+          setFailure(
+              new ProcedureException(
+                  new IoTDBException(
+                      
ProcedureMessages.AUTH_PROCEDURE_CACHE_INVALIDATION_FAILED,
+                      TSStatusCode.AUTH_OPERATE_EXCEPTION.getStatusCode())));
+          return Flow.NO_MORE_STATE;
       }
     } catch (Exception e) {
       if (isRollbackSupported(state)) {
         LOGGER.error(ProcedureMessages.FAIL_WHEN_EXECUTE, plan);
         setFailure(new ProcedureException(e));
       } else {
-        LOGGER.error(
-            ProcedureMessages.RETRIEVABLE_ERROR_TRYING_TO_EXECUTE_PLAN_STATE, 
plan, state, e);
-        if (getCycles() > RETRY_THRESHOLD) {
-          setFailure(
-              new ProcedureException(
-                  String.format(
-                      ProcedureMessages.FAIL_TO_EXECUTE_PLAN_AT_STATE, 
plan.toString(), state)));
-        }
+        
LOGGER.warn(ProcedureMessages.AUTH_PROCEDURE_CACHE_INVALIDATION_FAILED, e);
+        setFailure(
+            new ProcedureException(
+                new IoTDBException(
+                    ProcedureMessages.AUTH_PROCEDURE_CACHE_INVALIDATION_FAILED,
+                    e,
+                    TSStatusCode.AUTH_OPERATE_EXCEPTION.getStatusCode())));
       }
     }
     return Flow.HAS_MORE_STATE;
@@ -164,19 +153,26 @@ public class AuthOperationProcedure extends 
AbstractNodeProcedure<AuthOperationP
     }
     if (res.code == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
       setNextState(DATANODE_AUTHCACHE_INVALIDING);
-      for (TDataNodeConfiguration item : datanodes) {
-        this.dataNodesToInvalid.add(new Pair<>(item, 
System.currentTimeMillis()));
-      }
-      LOGGER.info(
-          ProcedureMessages.EXECUTE_AUTH_PLAN_SUCCESS_TO_INVALIDATE_DATANODES,
-          plan,
-          dataNodesToInvalid);
+      
LOGGER.info(ProcedureMessages.EXECUTE_AUTH_PLAN_SUCCESS_TO_INVALIDATE_DATANODES,
 plan);
     } else {
       LOGGER.info(ProcedureMessages.FAILED_TO_EXECUTE_PLAN_BECAUSE, plan, 
res.message);
       setFailure(new ProcedureException(new IoTDBException(res)));
     }
   }
 
+  private static Map<Integer, TSStatus> broadcastAuthorityCache(
+      final TInvalidatePermissionCacheReq req, final Map<Integer, 
TDataNodeLocation> targets) {
+    final DataNodeAsyncRequestContext<TInvalidatePermissionCacheReq, TSStatus> 
clientHandler =
+        new DataNodeAsyncRequestContext<>(
+            CnToDnAsyncRequestType.INVALIDATE_PERMISSION_CACHE, req, targets);
+    CnToDnInternalServiceAsyncRequestManager.getInstance()
+        .sendAsyncRequest(
+            clientHandler,
+            ClusterCachePropagator.BROADCAST_RPC_RETRY,
+            ClusterCachePropagator.BROADCAST_RPC_TIMEOUT_MS);
+    return clientHandler.getResponseMap();
+  }
+
   @Override
   protected boolean isRollbackSupported(AuthOperationProcedureState state) {
     return state == AuthOperationProcedureState.INIT;
diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
index 0853e59b9fa..080fabdc546 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
@@ -949,12 +949,6 @@ public final class DataNodeMiscMessages {
   public static final String CACHE_ROLE_PATH_PRIVILEGES_ERROR =
       "cache role's path privileges error";
 
-  // 
---------------------------------------------------------------------------
-  // auth – BasicAuthorityCache
-  // 
---------------------------------------------------------------------------
-  public static final String DATANODE_CACHE_INIT_FAILED =
-      "datanode cache initialization failed";
-
   // 
---------------------------------------------------------------------------
   // trigger – TriggerExecutor
   // 
---------------------------------------------------------------------------
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
index 1abc4bb25dc..346dc8dafc1 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
@@ -947,11 +947,6 @@ public final class DataNodeMiscMessages {
   public static final String CACHE_ROLE_PATH_PRIVILEGES_ERROR =
       "缓存角色路径权限时发生错误";
 
-  // 
---------------------------------------------------------------------------
-  // auth – BasicAuthorityCache
-  // 
---------------------------------------------------------------------------
-  public static final String DATANODE_CACHE_INIT_FAILED =
-      "DataNode 缓存初始化失败";
 
   // 
---------------------------------------------------------------------------
   // trigger – TriggerExecutor
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
index fe8748ba4c3..0593db10840 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
@@ -136,6 +136,7 @@ public class AuthorityChecker {
   }
 
   public static void invalidateAllCache() {
+    PipeInsertionDataNodeListener.getInstance().invalidateAllCache();
     authorityFetcher.get().getAuthorCache().invalidAllCache();
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/BasicAuthorityCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/BasicAuthorityCache.java
index 1056475f5c0..83965effec0 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/BasicAuthorityCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/BasicAuthorityCache.java
@@ -22,7 +22,6 @@ package org.apache.iotdb.db.auth;
 import org.apache.iotdb.commons.auth.entity.Role;
 import org.apache.iotdb.commons.auth.entity.User;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
-import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
 
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
@@ -90,19 +89,11 @@ public class BasicAuthorityCache implements IAuthorCache {
         }
         userCache.invalidate(userName);
       }
-      if (userCache.getIfPresent(userName) != null) {
-        LOGGER.error(DataNodeMiscMessages.DATANODE_CACHE_INIT_FAILED);
-        return false;
-      }
     }
     if (roleName != null) {
       if (roleCache.getIfPresent(roleName) != null) {
         roleCache.invalidate(roleName);
       }
-      if (roleCache.getIfPresent(roleName) != null) {
-        LOGGER.error(DataNodeMiscMessages.DATANODE_CACHE_INIT_FAILED);
-        return false;
-      }
     }
     return true;
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
index 5679a7ce6c9..1dd61c5b609 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
@@ -34,6 +34,7 @@ import org.apache.iotdb.commons.consensus.ConfigRegionId;
 import org.apache.iotdb.commons.exception.IoTDBException;
 import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
 import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.exception.MetadataLeaseFencedException;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.path.PathPatternTree;
 import org.apache.iotdb.commons.security.encrypt.AsymmetricEncrypt;
@@ -81,8 +82,6 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(ClusterAuthorityFetcher.class);
   private static final CommonConfig CONFIG = 
CommonDescriptor.getInstance().getConfig();
   private final IAuthorCache iAuthorCache;
-  private boolean cacheOutDate = false;
-  private long heartBeatTimeStamp = 0;
 
   private boolean acceptCache = true;
 
@@ -127,7 +126,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   @Override
   public TSStatus checkUserSysPrivilege(String username, PrivilegeType 
permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(permission, false),
@@ -139,7 +138,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public Collection<PrivilegeType> checkUserSysPrivileges(
       String username, Collection<PrivilegeType> permissions) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     Set<PrivilegeType> missingPrivileges = new HashSet<>();
     for (PrivilegeType permission : permissions) {
       TSStatus status =
@@ -158,7 +157,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   @Override
   public TSStatus checkUserSysPrivilegesGrantOpt(String username, 
PrivilegeType permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(permission, true),
@@ -174,7 +173,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
     if (username.equals(AuthorityChecker.INTERNAL_AUDIT_USER)) {
       return posList;
     }
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     User user = getUser(username, true);
     if (user.isOpenIdUser()) {
       return posList;
@@ -206,6 +205,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public TSStatus checkUserPathPrivilegesGrantOpt(
       String username, List<? extends PartialPath> paths, PrivilegeType 
permission) {
+    failIfMetadataLeaseFenced();
     User user = iAuthorCache.getUserCache(username);
     if (user != null) {
       if (user.isOpenIdUser()) {
@@ -249,7 +249,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public TSStatus checkUserDBPrivileges(
       String username, String database, PrivilegeType permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(database, permission),
@@ -262,7 +262,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public TSStatus checkUserDBPrivilegesGrantOpt(
       String username, String database, PrivilegeType permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(database, permission, true),
@@ -276,7 +276,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public TSStatus checkUserTBPrivileges(
       String username, String database, String table, PrivilegeType 
permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(database, table, permission),
@@ -292,7 +292,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public TSStatus checkUserTBPrivilegesGrantOpt(
       String username, String database, String table, PrivilegeType 
permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(database, table, permission, true),
@@ -307,7 +307,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   @Override
   public TSStatus checkUserAnyScopePrivilegeGrantOption(String username, 
PrivilegeType permission) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(permission, false, true),
@@ -319,7 +319,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   /** -- check database/table visible -- * */
   @Override
   public TSStatus checkDBVisible(String username, String database) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(database, null, false),
@@ -330,7 +330,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   @Override
   public TSStatus checkTBVisible(String username, String database, String 
table) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     return checkPrivilege(
         username,
         new PrivilegeUnion(database, table, null, false),
@@ -343,6 +343,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public PathPatternTree getAuthorizedPatternTree(String username, 
PrivilegeType permission)
       throws AuthException {
+    failIfMetadataLeaseFenced();
     PathPatternTree patternTree = new PathPatternTree();
     User user = iAuthorCache.getUserCache(username);
     if (user != null) {
@@ -389,6 +390,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   private SettableFuture<ConfigTaskResult> operatePermissionInternal(
       Object plan, boolean isRelational) {
+    failIfMetadataLeaseFenced();
     SettableFuture<ConfigTaskResult> future = SettableFuture.create();
     try (ConfigNodeClient configNodeClient =
         
CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) {
@@ -463,6 +465,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   private SettableFuture<ConfigTaskResult> queryPermissionInternal(
       Object plan, boolean isRelational) {
+    failIfMetadataLeaseFenced();
     SettableFuture<ConfigTaskResult> future = SettableFuture.create();
     TAuthorizerResp authorizerResp = new TAuthorizerResp();
     try (ConfigNodeClient configNodeClient =
@@ -506,35 +509,10 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
     return iAuthorCache;
   }
 
-  @Override
-  public void refreshToken() {
-    long currentTime = System.currentTimeMillis();
-    if (heartBeatTimeStamp == 0) {
-      heartBeatTimeStamp = currentTime;
-      return;
-    }
-    if (currentTime - heartBeatTimeStamp > CONFIG.getDatanodeTokenTimeoutMS()) 
{
-      cacheOutDate = true;
-    }
-    heartBeatTimeStamp = currentTime;
-  }
-
-  // Package-private for testing (ClusterAuthorityFetcherLeaseTest).
-  void checkCacheAvailable() {
-    // cacheOutDate is set by refreshToken() only when a heartbeat finally 
arrives after a long gap,
-    // so it cannot catch an *ongoing* ConfigNode partition (no heartbeat 
arrives, refreshToken() is
-    // never called). isFenced() is evaluated on this DataNode's own clock and 
fires without any
-    // heartbeat: while fenced we drop the permission cache and force a 
re-fetch from the
-    // ConfigNode,
-    // which fails closed while partitioned, so a missed REVOKE cannot keep 
authorizing a privilege.
-    if (cacheOutDate || isMetadataLeaseFenced()) {
-      iAuthorCache.invalidAllCache();
-    }
-    cacheOutDate = false;
-  }
-
-  boolean isMetadataLeaseFenced() {
-    return MetadataLeaseManager.getInstance().isFenced();
+  void failIfMetadataLeaseFenced() {
+    MetadataLeaseManager.getInstance()
+        .failIfMetadataLeaseFenced(
+            
MetadataLeaseFencedException.LeaseFencedRetryPolicy.RETRY_UNTIL_SUCCESS);
   }
 
   @TestOnly
@@ -545,7 +523,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
   @Override
   public TSStatus checkUser(
       final String username, final String password, final boolean 
useEncryptedPassword) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     final User user = iAuthorCache.getUserCache(username);
     if (user != null) {
       if (user.isOpenIdUser()) {
@@ -595,7 +573,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   @Override
   public User getUser(String userName, final boolean force) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     User user = iAuthorCache.getUserCache(userName);
     if (user != null) {
       return user;
@@ -628,7 +606,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
 
   @Override
   public boolean checkRole(String userName, String roleName) {
-    checkCacheAvailable();
+    failIfMetadataLeaseFenced();
     User user = iAuthorCache.getUserCache(userName);
     if (user != null) {
       return user.isOpenIdUser() || user.getRoleSet().contains(roleName);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
index b14d5c599c1..8193456eb15 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
@@ -84,7 +84,5 @@ public interface IAuthorityFetcher {
 
   IAuthorCache getAuthorCache();
 
-  void refreshToken();
-
   User getUser(String username, final boolean force);
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index a06d10413fd..84ad19df0ad 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -2352,7 +2352,6 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
           .forEach((key, value) -> 
regionRawDataSize.put(Integer.parseInt(key), value.getLeft()));
       resp.setDataRegionRawDataSize(regionRawDataSize);
     }
-    AuthorityChecker.getAuthorityFetcher().refreshToken();
     resp.setHeartbeatTimestamp(req.getHeartbeatTimestamp());
     resp.setStatus(commonConfig.getNodeStatus().getStatus());
     // Advertise that this DataNode supports metadata-lease self-fencing, so 
the ConfigNode may
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/lease/MetadataLeaseManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/lease/MetadataLeaseManager.java
index da198225243..7f7365a1550 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/lease/MetadataLeaseManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/lease/MetadataLeaseManager.java
@@ -24,6 +24,7 @@ import 
org.apache.iotdb.commons.concurrent.threadpool.ScheduledExecutorUtil;
 import org.apache.iotdb.commons.exception.MetadataLeaseFencedException;
 import 
org.apache.iotdb.commons.exception.MetadataLeaseFencedException.LeaseFencedRetryPolicy;
 import org.apache.iotdb.commons.utils.TestOnly;
+import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
 import org.apache.iotdb.db.queryengine.plan.analyze.ClusterPartitionFetcher;
@@ -106,7 +107,8 @@ public class MetadataLeaseManager {
     return Arrays.asList(
         () -> ClusterPartitionFetcher.getInstance().invalidAllCache(),
         () -> DataNodeTableCache.getInstance().invalidateAll(),
-        () -> TreeDeviceSchemaCacheManager.getInstance().cleanUp());
+        () -> TreeDeviceSchemaCacheManager.getInstance().cleanUp(),
+        AuthorityChecker::invalidateAllCache);
   }
 
   private static List<MetadataAction> defaultPullMetaList() {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcherLeaseTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcherLeaseTest.java
index 6933b11a055..fb0f04f989d 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcherLeaseTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcherLeaseTest.java
@@ -20,6 +20,7 @@
 package org.apache.iotdb.db.auth;
 
 import org.apache.iotdb.commons.auth.entity.User;
+import org.apache.iotdb.commons.exception.MetadataLeaseFencedException;
 import org.apache.iotdb.db.schemaengine.lease.MetadataLeaseManager;
 import org.apache.iotdb.db.schemaengine.lease.MetadataLeaseTestUtils;
 
@@ -41,19 +42,17 @@ public class ClusterAuthorityFetcherLeaseTest {
   }
 
   @Test
-  public void fencedLeaseDropsPermissionCache() {
+  public void fencedLeaseThrowsException() {
     final ClusterAuthorityFetcher fetcher =
         new TestingClusterAuthorityFetcher(new BasicAuthorityCache(), 
leaseManager);
-    final User user = new User("user_fenced", "password");
-    fetcher.getAuthorCache().putUserCache(user.getName(), user);
-    
Assert.assertNotNull(fetcher.getAuthorCache().getUserCache(user.getName()));
 
     clock.addMillis(T_FENCE_MS + 1);
-    fetcher.checkCacheAvailable();
-
-    Assert.assertNull(
-        "a fenced DataNode must drop its permission cache so a missed REVOKE 
cannot keep authorizing",
-        fetcher.getAuthorCache().getUserCache(user.getName()));
+    try {
+      fetcher.failIfMetadataLeaseFenced();
+      Assert.fail("Expected MetadataLeaseFencedException");
+    } catch (MetadataLeaseFencedException e) {
+      // Expected.
+    }
   }
 
   @Test
@@ -66,7 +65,7 @@ public class ClusterAuthorityFetcherLeaseTest {
     // An active lease (a ConfigNode heartbeat was just received) must not 
needlessly drop the
     // cache.
     clock.addMillis(1_000L);
-    fetcher.checkCacheAvailable();
+    fetcher.failIfMetadataLeaseFenced();
 
     Assert.assertNotNull(
         "an active lease must not needlessly drop the permission cache",
@@ -96,8 +95,9 @@ public class ClusterAuthorityFetcherLeaseTest {
     }
 
     @Override
-    boolean isMetadataLeaseFenced() {
-      return MetadataLeaseTestUtils.isFenced(leaseManager);
+    void failIfMetadataLeaseFenced() {
+      MetadataLeaseTestUtils.failIfMetadataLeaseFenced(
+          leaseManager, 
MetadataLeaseFencedException.LeaseFencedRetryPolicy.RETRY_UNTIL_SUCCESS);
     }
   }
 }

Reply via email to