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();
}