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

yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new eb11e9e1c7 [MINOR] test: Fix flaky Ranger E2E ITs caused by stale 
Ranger Spark plugin policies (#14288)
eb11e9e1c7 is described below

commit eb11e9e1c7c435bccde4943e0fe1ce9c0f4753f2
Author: Qi Yu <[email protected]>
AuthorDate: Wed Oct 28 11:11:42 2026 +0800

    [MINOR] test: Fix flaky Ranger E2E ITs caused by stale Ranger Spark plugin 
policies (#14288)
    
    ### What changes were proposed in this pull request?
    
    Two changes to `RangerBaseE2EIT`:
    
    1. `waitForUpdatingPolicies()` now has the Ranger Spark plugin download
    the policies and roles from Ranger admin right away
    (`SparkRangerAdminPlugin.refreshPoliciesAndTags()`), instead of relying
    only on the 1s sleep to cover a background poll. The sleep stays for the
    Ranger plugins the test can't refresh, such as the HDFS one in the Hive
    container.
    2. `cleanIT()` drops the leftover schemas with `cascade`, and closes the
    Spark session in a `finally` block.
    
    ### Why are the changes needed?
    
    The Ranger Spark plugin polls Ranger admin every 500ms, and the tests
    sleep 1s after each grant. On a busy CI runner the poll can finish after
    the sleep, so the next SQL runs against stale policies. For example,
    `RangerIcebergE2EIT.testCreateTable` fails on `CREATE DATABASE` with
    `Permission denied: user [gravitino] does not have [create] privilege`.
    
    One such failure then breaks 25 more tests:
    
    - The schema created by the failed test stays, so the later tests fail
    with `SCHEMA_ALREADY_EXISTS`.
    - `cleanIT()` fails on the non-empty schema before it closes the Spark
    session.
    - `RangerPaimonE2EIT` then gets the Iceberg session back from
    `getOrCreate()`, so every Paimon test fails because
    `PaimonSparkSessionExtensions` is not configured.
    
    Example run:
    
https://github.com/yuqi1129/gravitino/actions/runs/40481390616/job/122114194992
    (115 tests, 26 failed).
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, test only.
    
    ### How was this patch tested?
    
    The `access-control-IT` job in CI.
---
 .../ranger/TestRangerPolicyRefresh.java            | 355 +++++++++++++++++++++
 .../ranger/integration/test/RangerBaseE2EIT.java   |  73 +++--
 .../ranger/integration/test/RangerPaimonE2EIT.java |  18 +-
 3 files changed, 420 insertions(+), 26 deletions(-)

diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/TestRangerPolicyRefresh.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/TestRangerPolicyRefresh.java
new file mode 100644
index 0000000000..accbb52100
--- /dev/null
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/TestRangerPolicyRefresh.java
@@ -0,0 +1,355 @@
+/*
+ * 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.gravitino.authorization.ranger;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Throwables;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.apache.gravitino.Catalog;
+import 
org.apache.gravitino.authorization.ranger.integration.test.RangerBaseE2EIT;
+import org.apache.gravitino.authorization.ranger.integration.test.RangerITEnv;
+import 
org.apache.gravitino.authorization.ranger.integration.test.RangerPaimonE2EIT;
+import org.apache.gravitino.client.GravitinoAdminClient;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.RoleAlreadyExistsException;
+import org.apache.kyuubi.plugin.spark.authz.ranger.SparkRangerAdminPlugin;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.spark.SparkConf;
+import org.apache.spark.SparkContext;
+import org.apache.spark.sql.SparkSession;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/** Regression tests for Ranger policy refresh and temporary-role cleanup 
without containers. */
+public class TestRangerPolicyRefresh {
+  private static final String RANGER_EXTENSION =
+      "org.apache.kyuubi.plugin.spark.authz.ranger.RangerSparkExtension";
+
+  private SparkSession previousSession;
+  private GravitinoMetalake previousMetalake;
+  private String previousMetalakeName;
+  private Catalog previousCatalog;
+  private boolean registeredCloseable;
+
+  @BeforeEach
+  void saveSharedState() {
+    previousSession = PolicyWaitProbe.session();
+    previousMetalake = PolicyWaitProbe.metalake();
+    previousMetalakeName = RangerBaseE2EIT.metalakeName;
+    previousCatalog = PolicyWaitProbe.catalog();
+    RangerBaseE2EIT.metalakeName = "metalake_policy_refresh_test";
+    PolicyWaitProbe.session(null);
+    PolicyWaitProbe.metalake(mock(GravitinoMetalake.class));
+  }
+
+  @AfterEach
+  void restoreSharedState() throws Exception {
+    try {
+      if (registeredCloseable) {
+        PolicyWaitProbe.closeResources();
+      }
+    } finally {
+      PolicyWaitProbe.session(previousSession);
+      PolicyWaitProbe.metalake(previousMetalake);
+      PolicyWaitProbe.catalog(previousCatalog);
+      RangerBaseE2EIT.metalakeName = previousMetalakeName;
+    }
+  }
+
+  @Test
+  void testNoSparkSessionDoesNotTouchPlugin() {
+    try (MockedStatic<SparkRangerAdminPlugin> plugin = 
mockStatic(SparkRangerAdminPlugin.class)) {
+      PolicyWaitProbe.waitForPolicies();
+      plugin.verifyNoInteractions();
+    }
+  }
+
+  @Test
+  void testNonRangerSessionDoesNotTouchPlugin() {
+    
configureSession("org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions");
+    try (MockedStatic<SparkRangerAdminPlugin> plugin = 
mockStatic(SparkRangerAdminPlugin.class)) {
+      PolicyWaitProbe.waitForPolicies();
+      plugin.verifyNoInteractions();
+    }
+  }
+
+  @Test
+  void testUninitializedPluginDoesNotRefresh() {
+    configureSession(RANGER_EXTENSION);
+    try (MockedStatic<SparkRangerAdminPlugin> plugin = 
mockStatic(SparkRangerAdminPlugin.class)) {
+      PolicyWaitProbe.waitForPolicies();
+      plugin.verify(SparkRangerAdminPlugin::getServiceDef);
+      plugin.verify(SparkRangerAdminPlugin::refreshPoliciesAndTags, never());
+    }
+  }
+
+  @Test
+  void testInitializedPluginRefreshesWithCompatibleApi() {
+    configureSession(
+        RANGER_EXTENSION + 
",org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions");
+    try (MockedStatic<SparkRangerAdminPlugin> plugin = 
mockStatic(SparkRangerAdminPlugin.class)) {
+      plugin.when(SparkRangerAdminPlugin::getServiceDef).thenReturn(new 
RangerServiceDef());
+      PolicyWaitProbe.waitForPolicies();
+      plugin.verify(SparkRangerAdminPlugin::getServiceDef);
+      plugin.verify(SparkRangerAdminPlugin::refreshPoliciesAndTags);
+    }
+  }
+
+  @Test
+  void testRefreshFailureIsReported() {
+    configureSession(RANGER_EXTENSION);
+    IllegalStateException failure = new IllegalStateException("policy refresh 
failed");
+    try (MockedStatic<SparkRangerAdminPlugin> plugin = 
mockStatic(SparkRangerAdminPlugin.class)) {
+      plugin.when(SparkRangerAdminPlugin::getServiceDef).thenReturn(new 
RangerServiceDef());
+      
plugin.when(SparkRangerAdminPlugin::refreshPoliciesAndTags).thenThrow(failure);
+      Assertions.assertSame(
+          failure,
+          Assertions.assertThrows(IllegalStateException.class, 
PolicyWaitProbe::waitForPolicies));
+    }
+  }
+
+  @Test
+  void testPaimonResetDeletesRoleAfterRefreshFailure() {
+    IllegalStateException failure = new IllegalStateException("policy refresh 
failed");
+    try (MockedStatic<RangerBaseE2EIT> policies = 
mockStatic(RangerBaseE2EIT.class)) {
+      policies.when(PolicyWaitProbe::waitForPolicies).thenThrow(failure);
+      Assertions.assertSame(
+          failure, Assertions.assertThrows(IllegalStateException.class, () -> 
resetPaimon()));
+      verify(PolicyWaitProbe.metalake()).deleteRole("reset");
+    }
+  }
+
+  @Test
+  void testPaimonResetDeletesRoleAfterCatalogFailure() {
+    configureSession(RANGER_EXTENSION);
+    IllegalStateException failure = new IllegalStateException("use catalog 
failed");
+    when(PolicyWaitProbe.session().sql("USE paimon")).thenThrow(failure);
+    try (MockedStatic<RangerBaseE2EIT> policies = 
mockStatic(RangerBaseE2EIT.class)) {
+      Assertions.assertSame(
+          failure, Assertions.assertThrows(IllegalStateException.class, () -> 
resetPaimon()));
+      verify(PolicyWaitProbe.metalake()).deleteRole("reset");
+    }
+  }
+
+  @Test
+  void testPaimonResetDoesNotDeleteRoleWhenCreationFails() {
+    RoleAlreadyExistsException failure = new RoleAlreadyExistsException("role 
exists");
+    when(PolicyWaitProbe.metalake().createRole(eq("reset"), anyMap(), 
anyList()))
+        .thenThrow(failure);
+    Assertions.assertSame(
+        failure, Assertions.assertThrows(RoleAlreadyExistsException.class, () 
-> resetPaimon()));
+    verify(PolicyWaitProbe.metalake(), never()).deleteRole(any());
+  }
+
+  @Test
+  void testPaimonResetPreservesRefreshFailureWhenCleanupFails() {
+    IllegalStateException failure = new IllegalStateException("policy refresh 
failed");
+    IllegalStateException cleanupFailure = new IllegalStateException("role 
cleanup failed");
+    
when(PolicyWaitProbe.metalake().deleteRole("reset")).thenThrow(cleanupFailure);
+    try (MockedStatic<RangerBaseE2EIT> policies = 
mockStatic(RangerBaseE2EIT.class)) {
+      policies.when(PolicyWaitProbe::waitForPolicies).thenThrow(failure);
+      Assertions.assertSame(
+          failure,
+          Assertions.assertThrows(
+              IllegalStateException.class, 
TestRangerPolicyRefresh::resetPaimon));
+      Assertions.assertArrayEquals(new Throwable[] {cleanupFailure}, 
failure.getSuppressed());
+    }
+  }
+
+  @Test
+  void testPaimonResetCleansRoleAfterLinkageError() {
+    NoSuchMethodError failure = new NoSuchMethodError("policy version method 
unavailable");
+    try (MockedStatic<RangerBaseE2EIT> policies = 
mockStatic(RangerBaseE2EIT.class)) {
+      policies.when(PolicyWaitProbe::waitForPolicies).thenThrow(failure);
+      Assertions.assertSame(
+          failure,
+          Assertions.assertThrows(NoSuchMethodError.class, 
TestRangerPolicyRefresh::resetPaimon));
+      verify(PolicyWaitProbe.metalake()).deleteRole("reset");
+    }
+  }
+
+  @Test
+  void testPaimonResetAvoidsSelfSuppression() {
+    IllegalStateException failure = new IllegalStateException("shared 
failure");
+    when(PolicyWaitProbe.metalake().deleteRole("reset")).thenThrow(failure);
+    try (MockedStatic<RangerBaseE2EIT> policies = 
mockStatic(RangerBaseE2EIT.class)) {
+      policies.when(PolicyWaitProbe::waitForPolicies).thenThrow(failure);
+      Assertions.assertSame(
+          failure,
+          Assertions.assertThrows(
+              IllegalStateException.class, 
TestRangerPolicyRefresh::resetPaimon));
+      Assertions.assertEquals(0, failure.getSuppressed().length);
+    }
+  }
+
+  @Test
+  void testSuccessfulPaimonResetRefreshesAfterRoleDeletion() {
+    configureSession(RANGER_EXTENSION);
+    try (MockedStatic<RangerBaseE2EIT> policies = 
mockStatic(RangerBaseE2EIT.class)) {
+      resetPaimon();
+      verify(PolicyWaitProbe.metalake()).deleteRole("reset");
+      policies.verify(PolicyWaitProbe::waitForPolicies, times(2));
+    }
+  }
+
+  @Test
+  void testCleanupPreservesDefaultSchemaAndCascadesOtherSchemas() throws 
Exception {
+    PolicyWaitProbe probe = mock(PolicyWaitProbe.class, CALLS_REAL_METHODS);
+    GravitinoAdminClient client = mock(GravitinoAdminClient.class);
+    probe.adminClient(client);
+    Catalog catalog = mock(Catalog.class, RETURNS_DEEP_STUBS);
+    PolicyWaitProbe.catalog(catalog);
+    when(catalog.asSchemas().listSchemas()).thenReturn(new String[] 
{"default", "leftover_schema"});
+    when(PolicyWaitProbe.metalake().listCatalogs()).thenReturn(new String[] 
{"leftover_catalog"});
+    SparkSession session = mock(SparkSession.class);
+    PolicyWaitProbe.session(session);
+    AutoCloseable resource = mock(AutoCloseable.class);
+    PolicyWaitProbe.registerResource(resource);
+    registeredCloseable = true;
+
+    try (MockedStatic<RangerITEnv> environment = 
mockStatic(RangerITEnv.class)) {
+      probe.cleanup();
+      verify(catalog.asSchemas()).dropSchema("leftover_schema", true);
+      verify(catalog.asSchemas(), never()).dropSchema(eq("default"), 
anyBoolean());
+      verify(PolicyWaitProbe.metalake()).dropCatalog("leftover_catalog", true);
+      verify(client).disableMetalake("metalake_policy_refresh_test");
+      verify(client).dropMetalake("metalake_policy_refresh_test");
+      verify(session).close();
+      verify(resource).close();
+      Assertions.assertNull(PolicyWaitProbe.session());
+      Assertions.assertNull(probe.adminClient());
+      environment.verify(RangerITEnv::cleanup);
+    }
+  }
+
+  @Test
+  void testMetadataCleanupFailureStillClosesSparkAndResources() throws 
Exception {
+    PolicyWaitProbe probe = mock(PolicyWaitProbe.class, CALLS_REAL_METHODS);
+    probe.adminClient(mock(GravitinoAdminClient.class));
+    Catalog catalog = mock(Catalog.class, RETURNS_DEEP_STUBS);
+    PolicyWaitProbe.catalog(catalog);
+    IllegalStateException failure = new IllegalStateException("metadata 
cleanup failed");
+    when(catalog.asSchemas().listSchemas()).thenThrow(failure);
+    SparkSession session = mock(SparkSession.class);
+    PolicyWaitProbe.session(session);
+    AutoCloseable resource = mock(AutoCloseable.class);
+    PolicyWaitProbe.registerResource(resource);
+    registeredCloseable = true;
+
+    try (MockedStatic<RangerITEnv> environment = 
mockStatic(RangerITEnv.class)) {
+      Assertions.assertSame(
+          failure, Assertions.assertThrows(IllegalStateException.class, 
probe::cleanup));
+      verify(session).close();
+      verify(resource).close();
+      Assertions.assertNull(PolicyWaitProbe.session());
+      Assertions.assertNull(probe.adminClient());
+      environment.verify(RangerITEnv::cleanup);
+    }
+  }
+
+  private void configureSession(String extensions) {
+    SparkSession session = mock(SparkSession.class);
+    SparkContext context = mock(SparkContext.class);
+    when(session.sparkContext()).thenReturn(context);
+    when(context.getConf())
+        .thenReturn(new SparkConf(false).set("spark.sql.extensions", 
extensions));
+    PolicyWaitProbe.session(session);
+  }
+
+  private static void resetPaimon() {
+    try {
+      Method method = RangerPaimonE2EIT.class.getDeclaredMethod("reset");
+      method.setAccessible(true);
+      method.invoke(new RangerPaimonE2EIT());
+    } catch (InvocationTargetException e) {
+      Throwables.throwIfUnchecked(e.getCause());
+      throw new AssertionError(e.getCause());
+    } catch (ReflectiveOperationException e) {
+      throw new LinkageError(e.getMessage(), e);
+    }
+  }
+
+  // Keep this probe abstract so it cannot run inherited E2E tests or their 
container lifecycle.
+  private abstract static class PolicyWaitProbe extends RangerBaseE2EIT {
+    static SparkSession session() {
+      return sparkSession;
+    }
+
+    static void session(SparkSession session) {
+      sparkSession = session;
+    }
+
+    static GravitinoMetalake metalake() {
+      return metalake;
+    }
+
+    static void metalake(GravitinoMetalake value) {
+      metalake = value;
+    }
+
+    static Catalog catalog() {
+      return catalog;
+    }
+
+    static void catalog(Catalog value) {
+      catalog = value;
+    }
+
+    static void registerResource(AutoCloseable resource) {
+      closer.register(resource);
+    }
+
+    static void closeResources() throws Exception {
+      closer.close();
+    }
+
+    GravitinoAdminClient adminClient() {
+      return client;
+    }
+
+    void adminClient(GravitinoAdminClient value) {
+      client = value;
+    }
+
+    void cleanup() {
+      cleanIT();
+    }
+
+    static void waitForPolicies() {
+      waitForUpdatingPolicies();
+    }
+  }
+}
diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
index e404e77a36..355e93fa22 100644
--- 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
@@ -46,6 +46,7 @@ import org.apache.gravitino.integration.test.util.BaseIT;
 import org.apache.gravitino.integration.test.util.GravitinoITUtils;
 import org.apache.gravitino.rel.TableChange;
 import org.apache.kyuubi.plugin.spark.authz.AccessControlException;
+import org.apache.kyuubi.plugin.spark.authz.ranger.SparkRangerAdminPlugin;
 import org.apache.spark.sql.Dataset;
 import org.apache.spark.sql.Row;
 import org.apache.spark.sql.SparkSession;
@@ -68,6 +69,8 @@ public abstract class RangerBaseE2EIT extends BaseIT {
 
   protected static SparkSession sparkSession = null;
   protected static final String HADOOP_USER_NAME = "HADOOP_USER_NAME";
+  private static final String RANGER_SPARK_EXTENSION =
+      "org.apache.kyuubi.plugin.spark.authz.ranger.RangerSparkExtension";
 
   protected static final String SQL_SHOW_DATABASES =
       String.format("SHOW DATABASES like '%s'", schemaName);
@@ -145,27 +148,34 @@ public abstract class RangerBaseE2EIT extends BaseIT {
   }
 
   protected void cleanIT() {
-    if (client != null) {
-      Arrays.stream(catalog.asSchemas().listSchemas())
-          .filter(schema -> !schema.equals("default"))
-          .forEach((schema -> catalog.asSchemas().dropSchema(schema, false)));
-
-      // The `dropCatalog` call will invoke the catalog metadata object to 
remove privileges
-      Arrays.stream(metalake.listCatalogs())
-          .forEach((catalogName -> metalake.dropCatalog(catalogName, true)));
-      client.disableMetalake(metalakeName);
-      client.dropMetalake(metalakeName);
-    }
-    if (sparkSession != null) {
-      sparkSession.close();
-    }
     try {
-      closer.close();
-    } catch (Exception e) {
-      LOG.error("Failed to close CloseableGroup", e);
+      if (client != null) {
+        // Drop the schemas with their tables, since a failed test can leave a 
table behind
+        Arrays.stream(catalog.asSchemas().listSchemas())
+            .filter(schema -> !schema.equals("default"))
+            .forEach((schema -> catalog.asSchemas().dropSchema(schema, true)));
+
+        // The `dropCatalog` call will invoke the catalog metadata object to 
remove privileges
+        Arrays.stream(metalake.listCatalogs())
+            .forEach((catalogName -> metalake.dropCatalog(catalogName, true)));
+        client.disableMetalake(metalakeName);
+        client.dropMetalake(metalakeName);
+      }
+    } finally {
+      // Close the Spark session even if the cleanup fails, otherwise the next 
test class gets it
+      // back from `SparkSession.builder().getOrCreate()` with this class's 
catalogs and extensions
+      if (sparkSession != null) {
+        sparkSession.close();
+        sparkSession = null;
+      }
+      try {
+        closer.close();
+      } catch (Exception e) {
+        LOG.error("Failed to close CloseableGroup", e);
+      }
+      client = null;
+      RangerITEnv.cleanup();
     }
-    client = null;
-    RangerITEnv.cleanup();
   }
 
   protected void createMetalake() {
@@ -180,10 +190,16 @@ public abstract class RangerBaseE2EIT extends BaseIT {
   }
 
   protected static void waitForUpdatingPolicies() {
-    // After Ranger authorization, Must wait a period of time for the Ranger 
Spark plugin to update
-    // the policy Sleep time must be greater than the policy update interval
-    // (ranger.plugin.spark.policy.pollIntervalMs) in the
-    // `resources/ranger-spark-security.xml.template`
+    // The Ranger Spark plugin polls Ranger admin every 
`ranger.plugin.spark.policy.pollIntervalMs`
+    // (see `resources/ranger-spark-security.xml.template`), but on a busy 
machine a poll can come
+    // later than the sleep below. Download the policies and roles now instead.
+    // Kyuubi exposes getPolicyVersion(), but RangerBasePlugin in Ranger 2.4 
does not implement
+    // it. getServiceDef() is compatible with both jars and is null until a 
policy engine exists.
+    if (usesRangerSparkPlugin() && SparkRangerAdminPlugin.getServiceDef() != 
null) {
+      SparkRangerAdminPlugin.refreshPoliciesAndTags();
+    }
+
+    // Other Ranger plugins, such as the HDFS one in the Hive container, can 
only be waited for
     try {
       Thread.sleep(1000L);
     } catch (InterruptedException e) {
@@ -1289,4 +1305,15 @@ public abstract class RangerBaseE2EIT extends BaseIT {
           AccessControlException.class, () -> 
sparkSession.sql(SQL_CREATE_SCHEMA));
     }
   }
+
+  private static boolean usesRangerSparkPlugin() {
+    // Check the configuration rather than the plugin itself: touching the 
plugin creates it, and it
+    // reads its configuration from the classpath only once, when it is created
+    return sparkSession != null
+        && sparkSession
+            .sparkContext()
+            .getConf()
+            .get("spark.sql.extensions", "")
+            .contains(RANGER_SPARK_EXTENSION);
+  }
 }
diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerPaimonE2EIT.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerPaimonE2EIT.java
index 015c444b79..35b41f72ce 100644
--- 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerPaimonE2EIT.java
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerPaimonE2EIT.java
@@ -122,9 +122,21 @@ public class RangerPaimonE2EIT extends RangerBaseE2EIT {
         SecurableObjects.ofMetalake(
             metalakeName, Lists.newArrayList(Privileges.UseCatalog.allow()));
     metalake.createRole(roleName, Collections.emptyMap(), 
Lists.newArrayList(securableObject));
-    metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1);
-    waitForUpdatingPolicies();
-    sparkSession.sql(SQL_USE_CATALOG);
+    try {
+      metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1);
+      waitForUpdatingPolicies();
+      sparkSession.sql(SQL_USE_CATALOG);
+    } catch (RuntimeException | Error failure) {
+      // A failed refresh must not leave "reset" behind and fail every 
subsequent test.
+      try {
+        metalake.deleteRole(roleName);
+      } catch (RuntimeException | Error cleanupFailure) {
+        if (cleanupFailure != failure) {
+          failure.addSuppressed(cleanupFailure);
+        }
+      }
+      throw failure;
+    }
     metalake.deleteRole(roleName);
     waitForUpdatingPolicies();
   }

Reply via email to