This is an automated email from the ASF dual-hosted git repository.
roryqi 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 a2839877d3 [#11172] fix(audit): skip audit events for internal Iceberg
REST metadata operations (#11188)
a2839877d3 is described below
commit a2839877d3e833dc0b5ed852043c92504fe602bb
Author: Shane <[email protected]>
AuthorDate: Fri Jun 5 09:44:34 2026 +0800
[#11172] fix(audit): skip audit events for internal Iceberg REST metadata
operations (#11188)
### What changes were proposed in this pull request?
This PR prevents internal metadata operations triggered by Iceberg REST
server and authorization infrastructure from being recorded as
standalone audit events.
The main changes are:
1. Add internal dispatcher accessors in `GravitinoEnv` for catalog,
schema, table, view, owner, and access control operations.
These internal dispatchers keep the normalize/operation layer but bypass
hook and event dispatchers.
2. Use internal dispatchers for Iceberg REST metadata synchronization:
- schema import after namespace creation
- table import after table creation, staged create commit, and table
registration
- view import after view creation
- owner synchronization for schemas, tables, and views
3. Use internal access control dispatcher for authorization user lookups
to avoid emitting `GET_USER` audit events during auth checks.
4. Use internal catalog dispatcher in `DynamicIcebergConfigProvider`
when running in auxiliary mode, so Iceberg REST catalog config lookups
do not emit `LOAD_CATALOG` audit events.
5. Inject schema dispatcher suppliers into table/view operation
dispatchers so internal table/view imports also use the internal schema
dispatcher for dependent schema loading.
### Why are the changes needed?
Iceberg REST server performs several metadata operations internally as
part of handling a single user request. For example, after creating a
namespace/table/view in the underlying Iceberg catalog, Gravitino
imports the corresponding schema/table/view metadata and
synchronizes ownership.
Before this change, those internal operations reused public dispatchers.
As a result, they emitted audit events such as `LOAD_SCHEMA`,
`LOAD_TABLE`, `LOAD_VIEW`, `SET_OWNER`, `GET_USER`, and `LOAD_CATALOG`,
even though users did not directly invoke those operations.
This caused noisy and misleading audit logs. In particular, dynamic
Iceberg catalog config lookups could repeatedly produce `LOAD_CATALOG
... FAILURE` audit entries for missing catalogs.
This PR separates internal infrastructure calls from user-facing API
calls by routing them through internal dispatchers that bypass event and
hook dispatchers.
### Fix
Fix: #11172
### Does this PR introduce any user-facing change?
No user-facing API or configuration change is introduced.
The observable behavior change is limited to audit logs: internal
Iceberg REST metadata synchronization, authorization user checks, and
auxiliary-mode catalog config lookups are no longer recorded as
standalone user audit events.
User-facing API operations are still audited normally.
### How was this patch tested?
Added and updated unit tests to verify internal dispatcher usage and
prevent regressions:
1. Iceberg table hook dispatcher tests verify that table import and
owner synchronization use internal table/owner dispatchers and do not
call public dispatchers.
2. Iceberg namespace hook dispatcher tests verify that schema/table
imports and owner synchronization use internal schema/table/owner
dispatchers.
3. Iceberg view hook dispatcher tests verify that view import and owner
synchronization use internal view/owner dispatchers.
4. Dynamic Iceberg config provider tests verify that auxiliary-mode
catalog config lookup uses internal catalog dispatcher and does not call
public catalog dispatcher.
5. Authorization tests verify that PassThrough and Jcasbin authorizers
use internal access control dispatcher for user lookup.
---
.../java/org/apache/gravitino/GravitinoEnv.java | 98 +++++++++++++++++++++
.../catalog/TableOperationDispatcher.java | 36 +++++++-
.../gravitino/catalog/ViewOperationDispatcher.java | 35 +++++++-
.../catalog/TestTableOperationDispatcher.java | 38 +++++++++
.../catalog/TestViewOperationDispatcher.java | 36 ++++++++
.../dispatcher/IcebergNamespaceHookDispatcher.java | 8 +-
.../dispatcher/IcebergTableHookDispatcher.java | 4 +-
.../dispatcher/IcebergViewHookDispatcher.java | 4 +-
.../provider/DynamicIcebergConfigProvider.java | 4 +-
.../TestIcebergNamespaceHookDispatcher.java | 99 +++++++++++++++++++---
.../dispatcher/TestIcebergTableHookDispatcher.java | 69 ++++++++++++---
.../dispatcher/TestIcebergViewHookDispatcher.java | 41 +++++++--
.../provider/TestDynamicIcebergConfigProvider.java | 64 ++++++++++----
.../authorization/PassThroughAuthorizer.java | 3 +-
.../authorization/TestPassThroughAuthorizer.java | 35 ++++++++
.../jcasbin/TestJcasbinAuthorizer.java | 6 ++
16 files changed, 513 insertions(+), 67 deletions(-)
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index df80fad2ca..c845c43c49 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -120,14 +120,17 @@ public class GravitinoEnv {
private EntityStore entityStore;
private CatalogDispatcher catalogDispatcher;
+ private CatalogDispatcher internalCatalogDispatcher;
private CatalogManager catalogManager;
private MetalakeManager metalakeManager;
private SchemaDispatcher schemaDispatcher;
+ private SchemaDispatcher internalSchemaDispatcher;
private TableDispatcher tableDispatcher;
+ private TableDispatcher internalTableDispatcher;
private PartitionDispatcher partitionDispatcher;
@@ -140,6 +143,7 @@ public class GravitinoEnv {
private FunctionDispatcher functionDispatcher;
private ViewDispatcher viewDispatcher;
+ private ViewDispatcher internalViewDispatcher;
private MetalakeDispatcher metalakeDispatcher;
@@ -150,6 +154,7 @@ public class GravitinoEnv {
private PolicyDispatcher policyDispatcher;
private AccessControlDispatcher accessControlDispatcher;
+ private AccessControlDispatcher internalAccessControlDispatcher;
private IdGenerator idGenerator;
@@ -167,6 +172,7 @@ public class GravitinoEnv {
private EventBus eventBus;
private OwnerDispatcher ownerDispatcher;
+ private OwnerDispatcher internalOwnerDispatcher;
private FutureGrantManager futureGrantManager;
private GravitinoAuthorizer gravitinoAuthorizer;
private StatisticDispatcher statisticDispatcher;
@@ -241,6 +247,19 @@ public class GravitinoEnv {
return catalogDispatcher;
}
+ /**
+ * Get the internal CatalogDispatcher associated with the Gravitino
environment.
+ *
+ * <p>The internal dispatcher preserves normalization but skips hooks and
event emission. It is
+ * intended for infrastructure catalog lookups that should not be recorded
as user API audit
+ * events.
+ *
+ * @return The internal CatalogDispatcher instance.
+ */
+ public CatalogDispatcher internalCatalogDispatcher() {
+ return internalCatalogDispatcher;
+ }
+
/**
* Get the SchemaDispatcher associated with the Gravitino environment.
*
@@ -250,6 +269,19 @@ public class GravitinoEnv {
return schemaDispatcher;
}
+ /**
+ * Get the internal SchemaDispatcher associated with the Gravitino
environment.
+ *
+ * <p>The internal dispatcher preserves normalization but skips hooks and
event emission. It is
+ * intended for infrastructure code that synchronizes metadata as part of
another user-visible
+ * operation.
+ *
+ * @return The internal SchemaDispatcher instance.
+ */
+ public SchemaDispatcher internalSchemaDispatcher() {
+ return internalSchemaDispatcher;
+ }
+
/**
* Get the TableDispatcher associated with the Gravitino environment.
*
@@ -259,6 +291,19 @@ public class GravitinoEnv {
return tableDispatcher;
}
+ /**
+ * Get the internal TableDispatcher associated with the Gravitino
environment.
+ *
+ * <p>The internal dispatcher preserves normalization but skips hooks and
event emission. It is
+ * intended for infrastructure code that synchronizes metadata as part of
another user-visible
+ * operation.
+ *
+ * @return The internal TableDispatcher instance.
+ */
+ public TableDispatcher internalTableDispatcher() {
+ return internalTableDispatcher;
+ }
+
/**
* Get the ModelDispatcher associated with the Gravitino environment.
*
@@ -286,6 +331,19 @@ public class GravitinoEnv {
return viewDispatcher;
}
+ /**
+ * Get the internal ViewDispatcher associated with the Gravitino environment.
+ *
+ * <p>The internal dispatcher preserves normalization but skips hook/event
side effects from
+ * dependent schema lookups. It is intended for infrastructure code that
synchronizes metadata as
+ * part of another user-visible operation.
+ *
+ * @return The internal ViewDispatcher instance.
+ */
+ public ViewDispatcher internalViewDispatcher() {
+ return internalViewDispatcher;
+ }
+
/**
* * Get the PartitionDispatcher associated with the Gravitino environment.
*
@@ -382,6 +440,18 @@ public class GravitinoEnv {
return accessControlDispatcher;
}
+ /**
+ * Get the internal AccessControlDispatcher associated with the Gravitino
environment.
+ *
+ * <p>The internal dispatcher skips hooks and event emission. It is intended
for authorization
+ * infrastructure lookups that should not be recorded as user API audit
events.
+ *
+ * @return The internal AccessControlDispatcher instance.
+ */
+ public AccessControlDispatcher internalAccessControlDispatcher() {
+ return internalAccessControlDispatcher;
+ }
+
/**
* Get the tagDispatcher associated with the Gravitino environment.
*
@@ -409,6 +479,18 @@ public class GravitinoEnv {
return ownerDispatcher;
}
+ /**
+ * Get the internal OwnerDispatcher associated with the Gravitino
environment.
+ *
+ * <p>The internal dispatcher skips event emission. It is intended for
infrastructure ownership
+ * synchronization that happens as part of another user-visible operation.
+ *
+ * @return The internal OwnerDispatcher instance.
+ */
+ public OwnerDispatcher internalOwnerDispatcher() {
+ return internalOwnerDispatcher;
+ }
+
/**
* Get the FutureGrantManager associated with the Gravitino environment.
*
@@ -569,6 +651,7 @@ public class GravitinoEnv {
this.catalogManager = new CatalogManager(config, entityStore, idGenerator);
CatalogNormalizeDispatcher catalogNormalizeDispatcher =
new CatalogNormalizeDispatcher(catalogManager);
+ this.internalCatalogDispatcher = catalogNormalizeDispatcher;
CatalogEventDispatcher catalogEventDispatcher =
new CatalogEventDispatcher(eventBus, catalogNormalizeDispatcher);
this.catalogDispatcher = new CatalogHookDispatcher(catalogEventDispatcher);
@@ -580,6 +663,7 @@ public class GravitinoEnv {
new SchemaOperationDispatcher(catalogManager, entityStore,
idGenerator);
SchemaNormalizeDispatcher schemaNormalizeDispatcher =
new SchemaNormalizeDispatcher(schemaOperationDispatcher,
catalogManager);
+ this.internalSchemaDispatcher = schemaNormalizeDispatcher;
SchemaEventDispatcher schemaEventDispatcher =
new SchemaEventDispatcher(eventBus, schemaNormalizeDispatcher);
this.schemaDispatcher = new SchemaHookDispatcher(schemaEventDispatcher);
@@ -588,6 +672,11 @@ public class GravitinoEnv {
new TableOperationDispatcher(catalogManager, entityStore, idGenerator);
TableNormalizeDispatcher tableNormalizeDispatcher =
new TableNormalizeDispatcher(tableOperationDispatcher, catalogManager);
+ TableOperationDispatcher internalTableOperationDispatcher =
+ new TableOperationDispatcher(
+ catalogManager, entityStore, idGenerator, () ->
internalSchemaDispatcher);
+ this.internalTableDispatcher =
+ new TableNormalizeDispatcher(internalTableOperationDispatcher,
catalogManager);
TableEventDispatcher tableEventDispatcher =
new TableEventDispatcher(eventBus, tableNormalizeDispatcher);
this.tableDispatcher = new TableHookDispatcher(tableEventDispatcher);
@@ -644,6 +733,11 @@ public class GravitinoEnv {
new ViewOperationDispatcher(catalogManager, entityStore, idGenerator);
ViewNormalizeDispatcher viewNormalizeDispatcher =
new ViewNormalizeDispatcher(viewOperationDispatcher, catalogManager);
+ ViewOperationDispatcher internalViewOperationDispatcher =
+ new ViewOperationDispatcher(
+ catalogManager, entityStore, idGenerator, () ->
internalSchemaDispatcher);
+ this.internalViewDispatcher =
+ new ViewNormalizeDispatcher(internalViewOperationDispatcher,
catalogManager);
ViewEventDispatcher viewEventDispatcher =
new ViewEventDispatcher(eventBus, viewNormalizeDispatcher);
this.viewDispatcher = viewEventDispatcher;
@@ -657,15 +751,19 @@ public class GravitinoEnv {
if (enableAuthorization) {
AccessControlManager accessControlManager =
new AccessControlManager(entityStore, idGenerator, config);
+ this.internalAccessControlDispatcher = accessControlManager;
AccessControlEventDispatcher accessControlEventDispatcher =
new AccessControlEventDispatcher(eventBus, accessControlManager);
this.accessControlDispatcher = new
AccessControlHookDispatcher(accessControlEventDispatcher);
OwnerDispatcher ownerManager = new OwnerManager(entityStore);
+ this.internalOwnerDispatcher = ownerManager;
this.ownerDispatcher = new OwnerEventManager(eventBus, ownerManager);
this.futureGrantManager = new FutureGrantManager(entityStore,
ownerManager);
} else {
this.accessControlDispatcher = null;
+ this.internalAccessControlDispatcher = null;
this.ownerDispatcher = null;
+ this.internalOwnerDispatcher = null;
this.futureGrantManager = null;
}
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
index 244edec88d..34677f3e31 100644
---
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
@@ -26,6 +26,7 @@ import static
org.apache.gravitino.utils.NameIdentifierUtil.getCatalogIdentifier
import static
org.apache.gravitino.utils.NameIdentifierUtil.getSchemaIdentifier;
import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.time.Instant;
import java.util.Arrays;
@@ -33,6 +34,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.tuple.Pair;
@@ -72,6 +74,8 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
private static final Logger LOG =
LoggerFactory.getLogger(TableOperationDispatcher.class);
+ private final Supplier<SchemaDispatcher> schemaDispatcherSupplier;
+
/**
* Creates a new TableOperationDispatcher instance.
*
@@ -81,7 +85,26 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
*/
public TableOperationDispatcher(
CatalogManager catalogManager, EntityStore store, IdGenerator
idGenerator) {
+ this(catalogManager, store, idGenerator, () ->
GravitinoEnv.getInstance().schemaDispatcher());
+ }
+
+ /**
+ * Creates a new TableOperationDispatcher instance.
+ *
+ * @param catalogManager The CatalogManager instance to be used for table
operations.
+ * @param store The EntityStore instance to be used for table operations.
+ * @param idGenerator The IdGenerator instance to be used for table
operations.
+ * @param schemaDispatcherSupplier The SchemaDispatcher supplier to ensure
schemas are imported.
+ */
+ public TableOperationDispatcher(
+ CatalogManager catalogManager,
+ EntityStore store,
+ IdGenerator idGenerator,
+ Supplier<SchemaDispatcher> schemaDispatcherSupplier) {
super(catalogManager, store, idGenerator);
+ this.schemaDispatcherSupplier =
+ Preconditions.checkNotNull(
+ schemaDispatcherSupplier, "schemaDispatcherSupplier must not be
null");
}
/**
@@ -118,7 +141,7 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
if (!entityCombinedTable.imported()) {
// Load the schema to make sure the schema is imported.
- SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().schemaDispatcher();
+ SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
NameIdentifier schemaIdent =
NameIdentifier.of(ident.namespace().levels());
schemaDispatcher.loadSchema(schemaIdent);
@@ -180,7 +203,7 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
throws NoSuchSchemaException, TableAlreadyExistsException {
// Load the schema to make sure the schema exists.
- SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().schemaDispatcher();
+ SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
schemaDispatcher.loadSchema(schemaIdent);
@@ -505,6 +528,15 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
table.tableFromCatalog().properties()));
}
+ private SchemaDispatcher getSchemaDispatcher() {
+ SchemaDispatcher schemaDispatcher = schemaDispatcherSupplier.get();
+ Preconditions.checkArgument(
+ schemaDispatcher != null,
+ "schemaDispatcherSupplier returned null. "
+ + "SchemaDispatcher must be available for table operations.");
+ return schemaDispatcher;
+ }
+
private EntityCombinedTable internalLoadTable(NameIdentifier ident) {
NameIdentifier catalogIdentifier = getCatalogIdentifier(ident);
Table table =
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
index a7b9ab1416..5b3937ee48 100644
---
a/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
@@ -29,6 +29,7 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.apache.gravitino.EntityAlreadyExistsException;
import org.apache.gravitino.EntityStore;
@@ -60,6 +61,8 @@ public class ViewOperationDispatcher extends
OperationDispatcher implements View
private static final Logger LOG =
LoggerFactory.getLogger(ViewOperationDispatcher.class);
+ private final Supplier<SchemaDispatcher> schemaDispatcherSupplier;
+
/**
* Creates a new ViewOperationDispatcher instance.
*
@@ -69,7 +72,26 @@ public class ViewOperationDispatcher extends
OperationDispatcher implements View
*/
public ViewOperationDispatcher(
CatalogManager catalogManager, EntityStore store, IdGenerator
idGenerator) {
+ this(catalogManager, store, idGenerator, () ->
GravitinoEnv.getInstance().schemaDispatcher());
+ }
+
+ /**
+ * Creates a new ViewOperationDispatcher instance.
+ *
+ * @param catalogManager The CatalogManager instance to be used for view
operations.
+ * @param store The EntityStore instance to be used for view operations.
+ * @param idGenerator The IdGenerator instance to be used for view
operations.
+ * @param schemaDispatcherSupplier The SchemaDispatcher supplier to ensure
schemas are imported.
+ */
+ public ViewOperationDispatcher(
+ CatalogManager catalogManager,
+ EntityStore store,
+ IdGenerator idGenerator,
+ Supplier<SchemaDispatcher> schemaDispatcherSupplier) {
super(catalogManager, store, idGenerator);
+ this.schemaDispatcherSupplier =
+ Preconditions.checkNotNull(
+ schemaDispatcherSupplier, "schemaDispatcherSupplier must not be
null");
}
/**
@@ -107,7 +129,7 @@ public class ViewOperationDispatcher extends
OperationDispatcher implements View
TreeLockUtils.doWithTreeLock(ident, LockType.READ, () ->
internalLoadView(ident));
if (!entityCombinedView.imported()) {
- SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().schemaDispatcher();
+ SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
NameIdentifier schemaIdent =
NameIdentifier.of(ident.namespace().levels());
schemaDispatcher.loadSchema(schemaIdent);
@@ -148,7 +170,7 @@ public class ViewOperationDispatcher extends
OperationDispatcher implements View
"representations must not be null or empty");
// Load the schema to make sure the schema exists.
- SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().schemaDispatcher();
+ SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
schemaDispatcher.loadSchema(schemaIdent);
@@ -527,6 +549,15 @@ public class ViewOperationDispatcher extends
OperationDispatcher implements View
.withImported(true);
}
+ private SchemaDispatcher getSchemaDispatcher() {
+ SchemaDispatcher schemaDispatcher = schemaDispatcherSupplier.get();
+ Preconditions.checkArgument(
+ schemaDispatcher != null,
+ "schemaDispatcherSupplier returned null. "
+ + "SchemaDispatcher must be available for view operations.");
+ return schemaDispatcher;
+ }
+
private ViewEntity applyChangesToEntity(
ViewEntity current, View alteredView, ViewChange[] changes) {
String name = alteredView.name();
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
b/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
index 9c44e46c7e..10b21dc41d 100644
---
a/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
+++
b/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
@@ -43,6 +43,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.reflect.FieldUtils;
@@ -258,6 +259,43 @@ public class TestTableOperationDispatcher extends
TestOperationDispatcher {
Assertions.assertEquals("test", loadedTable4.auditInfo().creator());
}
+ @Test
+ public void
testTableOperationDispatcherRejectsNullSchemaDispatcherSupplier() {
+ Assertions.assertThrows(
+ NullPointerException.class,
+ () -> new TableOperationDispatcher(catalogManager, entityStore,
idGenerator, null));
+ }
+
+ @Test
+ public void
testCreateTableFailsFastWhenSchemaDispatcherSupplierReturnsNull() throws
IOException {
+ Namespace tableNs = Namespace.of(metalake, catalog,
"schema-null-dispatcher");
+ Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+
schemaOperationDispatcher.createSchema(NameIdentifier.of(tableNs.levels()),
"comment", props);
+
+ Supplier<SchemaDispatcher> nullSchemaDispatcherSupplier = () -> null;
+ TableOperationDispatcher dispatcher =
+ new TableOperationDispatcher(
+ catalogManager, entityStore, idGenerator,
nullSchemaDispatcherSupplier);
+ NameIdentifier tableIdent = NameIdentifier.of(tableNs,
"table_null_dispatcher");
+ Column[] columns =
+ new Column[] {
+ TestColumn.builder()
+ .withName("col1")
+ .withPosition(0)
+ .withType(Types.StringType.get())
+ .build()
+ };
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> dispatcher.createTable(tableIdent, columns, "comment",
props, new Transform[0]));
+ Assertions.assertEquals(
+ "schemaDispatcherSupplier returned null. "
+ + "SchemaDispatcher must be available for table operations.",
+ exception.getMessage());
+ }
+
@Test
public void testConcurrentImportTableReusesExistingEntity() throws
IOException {
Namespace tableNs = Namespace.of(metalake, catalog, "schema52");
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
b/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
index a3c4146e8a..5f28bcd20e 100644
---
a/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
+++
b/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
@@ -40,6 +40,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Config;
import org.apache.gravitino.Entity;
@@ -198,6 +199,41 @@ public class TestViewOperationDispatcher extends
TestOperationDispatcher {
}
}
+ @Test
+ public void testViewOperationDispatcherRejectsNullSchemaDispatcherSupplier()
{
+ Assertions.assertThrows(
+ NullPointerException.class,
+ () -> new ViewOperationDispatcher(catalogManager, entityStore,
idGenerator, null));
+ }
+
+ @Test
+ public void testCreateViewFailsFastWhenSchemaDispatcherSupplierReturnsNull()
throws IOException {
+ Namespace viewNs = Namespace.of(metalake, catalog,
"schema-null-view-dispatcher");
+ Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+ schemaOperationDispatcher.createSchema(NameIdentifier.of(viewNs.levels()),
"comment", props);
+
+ Supplier<SchemaDispatcher> nullSchemaDispatcherSupplier = () -> null;
+ ViewOperationDispatcher dispatcher =
+ new ViewOperationDispatcher(
+ catalogManager, entityStore, idGenerator,
nullSchemaDispatcherSupplier);
+ NameIdentifier viewIdent = NameIdentifier.of(viewNs,
"view_null_dispatcher");
+ Representation[] representations =
+ new Representation[] {
+ SQLRepresentation.builder().withDialect("spark-sql").withSql("SELECT
1").build()
+ };
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ dispatcher.createView(
+ viewIdent, "comment", new Column[0], representations,
null, null, props));
+ Assertions.assertEquals(
+ "schemaDispatcherSupplier returned null. "
+ + "SchemaDispatcher must be available for view operations.",
+ exception.getMessage());
+ }
+
@Test
public void testLoadViewAutoImportsIntoEntityStore() throws IOException {
Namespace viewNs = Namespace.of(metalake, catalog, "schema63");
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
index 505b5dc87e..e316352339 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
@@ -96,7 +96,7 @@ public class IcebergNamespaceHookDispatcher implements
IcebergNamespaceOperation
catalogName,
newlyOwned,
context.userName(),
- GravitinoEnv.getInstance().ownerDispatcher());
+ GravitinoEnv.getInstance().internalOwnerDispatcher());
return createNamespaceResponse;
}
@@ -228,13 +228,13 @@ public class IcebergNamespaceHookDispatcher implements
IcebergNamespaceOperation
namespace,
registerTableRequest.name(),
context.userName(),
- GravitinoEnv.getInstance().ownerDispatcher());
+ GravitinoEnv.getInstance().internalOwnerDispatcher());
return response;
}
private void importTable(String catalogName, Namespace namespace, String
tableName) {
- TableDispatcher tableDispatcher =
GravitinoEnv.getInstance().tableDispatcher();
+ TableDispatcher tableDispatcher =
GravitinoEnv.getInstance().internalTableDispatcher();
if (tableDispatcher != null) {
tableDispatcher.loadTable(
IcebergIdentifierUtils.toGravitinoTableIdentifier(
@@ -246,7 +246,7 @@ public class IcebergNamespaceHookDispatcher implements
IcebergNamespaceOperation
}
private void importSchema(String catalogName, Namespace namespace) {
- SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().schemaDispatcher();
+ SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().internalSchemaDispatcher();
if (schemaDispatcher != null) {
schemaDispatcher.loadSchema(
IcebergIdentifierUtils.toGravitinoSchemaIdentifier(
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
index 9f081571f5..d39d93f1ae 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
@@ -209,11 +209,11 @@ public class IcebergTableHookDispatcher implements
IcebergTableOperationDispatch
namespace,
tableName,
context.userName(),
- GravitinoEnv.getInstance().ownerDispatcher());
+ GravitinoEnv.getInstance().internalOwnerDispatcher());
}
private void importTableEntity(String catalogName, Namespace namespace,
String tableName) {
- TableDispatcher tableDispatcher =
GravitinoEnv.getInstance().tableDispatcher();
+ TableDispatcher tableDispatcher =
GravitinoEnv.getInstance().internalTableDispatcher();
if (tableDispatcher != null) {
tableDispatcher.loadTable(
IcebergIdentifierUtils.toGravitinoTableIdentifier(
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
index a6e35fef50..dcde32b92c 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
@@ -76,7 +76,7 @@ public class IcebergViewHookDispatcher implements
IcebergViewOperationDispatcher
namespace,
createViewRequest.name(),
context.userName(),
- GravitinoEnv.getInstance().ownerDispatcher());
+ GravitinoEnv.getInstance().internalOwnerDispatcher());
return response;
}
@@ -181,7 +181,7 @@ public class IcebergViewHookDispatcher implements
IcebergViewOperationDispatcher
* @param viewName The name of the view.
*/
private void importView(String catalogName, Namespace namespace, String
viewName) {
- ViewDispatcher viewDispatcher =
GravitinoEnv.getInstance().viewDispatcher();
+ ViewDispatcher viewDispatcher =
GravitinoEnv.getInstance().internalViewDispatcher();
if (viewDispatcher != null) {
try {
viewDispatcher.loadView(
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
index 9194b4dc07..2bbefbade0 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
@@ -251,10 +251,10 @@ public class DynamicIcebergConfigProvider implements
IcebergConfigProvider {
InternalCatalogFetcher(String metalake) {
this.metalake = metalake;
- CatalogDispatcher dispatcher =
GravitinoEnv.getInstance().catalogDispatcher();
+ CatalogDispatcher dispatcher =
GravitinoEnv.getInstance().internalCatalogDispatcher();
Preconditions.checkState(
dispatcher != null,
- "CatalogDispatcher is not available. "
+ "Internal CatalogDispatcher is not available. "
+ "Internal catalog fetcher requires running within Gravitino
server.");
this.catalogDispatcher = dispatcher;
}
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
index eb212f8e4f..7d5e76922b 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
@@ -67,21 +67,65 @@ public class TestIcebergNamespaceHookDispatcher {
private IcebergNamespaceHookDispatcher hookDispatcher;
private IcebergNamespaceOperationDispatcher mockDispatcher;
private OwnerDispatcher mockOwnerDispatcher;
+ private OwnerDispatcher mockInternalOwnerDispatcher;
+ private SchemaDispatcher mockSchemaDispatcher;
+ private SchemaDispatcher mockInternalSchemaDispatcher;
+ private TableDispatcher mockTableDispatcher;
+ private TableDispatcher mockInternalTableDispatcher;
private EntityStore mockEntityStore;
private LockManager mockLockManager;
private IcebergRequestContext mockContext;
+ private OwnerDispatcher previousOwnerDispatcher;
+ private OwnerDispatcher previousInternalOwnerDispatcher;
+ private SchemaDispatcher previousSchemaDispatcher;
+ private SchemaDispatcher previousInternalSchemaDispatcher;
+ private TableDispatcher previousTableDispatcher;
+ private TableDispatcher previousInternalTableDispatcher;
+ private EntityStore previousEntityStore;
+ private LockManager previousLockManager;
+
@BeforeEach
public void setUp() throws IllegalAccessException {
mockDispatcher = mock(IcebergNamespaceOperationDispatcher.class);
mockOwnerDispatcher = mock(OwnerDispatcher.class);
- SchemaDispatcher mockSchemaDispatcher = mock(SchemaDispatcher.class);
- TableDispatcher mockTableDispatcher = mock(TableDispatcher.class);
+ mockInternalOwnerDispatcher = mock(OwnerDispatcher.class);
+ mockSchemaDispatcher = mock(SchemaDispatcher.class);
+ mockInternalSchemaDispatcher = mock(SchemaDispatcher.class);
+ mockTableDispatcher = mock(TableDispatcher.class);
+ mockInternalTableDispatcher = mock(TableDispatcher.class);
+
+ previousOwnerDispatcher =
+ (OwnerDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(),
"ownerDispatcher", true);
+ previousInternalOwnerDispatcher =
+ (OwnerDispatcher)
+ FieldUtils.readField(GravitinoEnv.getInstance(),
"internalOwnerDispatcher", true);
+ previousSchemaDispatcher =
+ (SchemaDispatcher)
+ FieldUtils.readField(GravitinoEnv.getInstance(),
"schemaDispatcher", true);
+ previousInternalSchemaDispatcher =
+ (SchemaDispatcher)
+ FieldUtils.readField(GravitinoEnv.getInstance(),
"internalSchemaDispatcher", true);
+ previousTableDispatcher =
+ (TableDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(),
"tableDispatcher", true);
+ previousInternalTableDispatcher =
+ (TableDispatcher)
+ FieldUtils.readField(GravitinoEnv.getInstance(),
"internalTableDispatcher", true);
+ previousEntityStore =
+ (EntityStore) FieldUtils.readField(GravitinoEnv.getInstance(),
"entityStore", true);
+ previousLockManager =
+ (LockManager) FieldUtils.readField(GravitinoEnv.getInstance(),
"lockManager", true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher",
mockOwnerDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalOwnerDispatcher",
mockInternalOwnerDispatcher, true);
FieldUtils.writeField(
GravitinoEnv.getInstance(), "schemaDispatcher", mockSchemaDispatcher,
true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalSchemaDispatcher",
mockInternalSchemaDispatcher, true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher",
mockTableDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalTableDispatcher",
mockInternalTableDispatcher, true);
mockEntityStore = mock(EntityStore.class);
FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore",
mockEntityStore, true);
@@ -105,11 +149,29 @@ public class TestIcebergNamespaceHookDispatcher {
@AfterEach
public void tearDown() throws IllegalAccessException {
- FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher", null,
true);
- FieldUtils.writeField(GravitinoEnv.getInstance(), "schemaDispatcher",
null, true);
- FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher", null,
true);
- FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", null,
true);
- FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", null,
true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "ownerDispatcher",
previousOwnerDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalOwnerDispatcher",
+ previousInternalOwnerDispatcher,
+ true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "schemaDispatcher",
previousSchemaDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalSchemaDispatcher",
+ previousInternalSchemaDispatcher,
+ true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "tableDispatcher",
previousTableDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalTableDispatcher",
+ previousInternalTableDispatcher,
+ true);
+ FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore",
previousEntityStore, true);
+ FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager",
previousLockManager, true);
Class<?> holderClass =
Arrays.stream(IcebergRESTServerContext.class.getDeclaredClasses())
@@ -129,7 +191,7 @@ public class TestIcebergNamespaceHookDispatcher {
when(mockDispatcher.createNamespace(mockContext,
mockRequest)).thenReturn(mockResponse);
doThrow(new RuntimeException("Set owner failed"))
- .when(mockOwnerDispatcher)
+ .when(mockInternalOwnerDispatcher)
.setOwners(any(), any(), any(), any());
RuntimeException thrown =
@@ -137,6 +199,7 @@ public class TestIcebergNamespaceHookDispatcher {
RuntimeException.class, () ->
hookDispatcher.createNamespace(mockContext, mockRequest));
Assertions.assertEquals("Set owner failed", thrown.getMessage());
verify(mockDispatcher).createNamespace(mockContext, mockRequest);
+ verify(mockOwnerDispatcher, never()).setOwners(any(), any(), any(), any());
}
@Test
@@ -150,7 +213,7 @@ public class TestIcebergNamespaceHookDispatcher {
.thenReturn(mockResponse);
doThrow(new RuntimeException("Set owner failed"))
- .when(mockOwnerDispatcher)
+ .when(mockInternalOwnerDispatcher)
.setOwner(any(), any(), any(), any());
RuntimeException thrown =
@@ -159,6 +222,7 @@ public class TestIcebergNamespaceHookDispatcher {
() -> hookDispatcher.registerTable(mockContext, namespace,
mockRequest));
Assertions.assertEquals("Set owner failed", thrown.getMessage());
verify(mockDispatcher).registerTable(mockContext, namespace, mockRequest);
+ verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
}
@Test
@@ -172,15 +236,18 @@ public class TestIcebergNamespaceHookDispatcher {
// Schema import (loadSchema) throwing must propagate so the caller learns
the namespace
// exists in Iceberg but is not registered in Gravitino. setOwner is
therefore unreachable.
- SchemaDispatcher schemaDispatcher =
GravitinoEnv.getInstance().schemaDispatcher();
- doThrow(new RuntimeException("Import
failed")).when(schemaDispatcher).loadSchema(any());
+ doThrow(new RuntimeException("Import failed"))
+ .when(mockInternalSchemaDispatcher)
+ .loadSchema(any());
RuntimeException thrown =
Assertions.assertThrows(
RuntimeException.class, () ->
hookDispatcher.createNamespace(mockContext, mockRequest));
Assertions.assertEquals("Import failed", thrown.getMessage());
+ verify(mockInternalOwnerDispatcher, never()).setOwners(any(), any(),
any(), any());
verify(mockOwnerDispatcher, never()).setOwners(any(), any(), any(), any());
+ verify(mockSchemaDispatcher, never()).loadSchema(any());
}
@Test
@@ -195,8 +262,9 @@ public class TestIcebergNamespaceHookDispatcher {
// Table import (loadTable) throwing must propagate so the caller learns
the table exists in
// Iceberg but is not registered in Gravitino. setOwner is therefore
unreachable.
- TableDispatcher tableDispatcher =
GravitinoEnv.getInstance().tableDispatcher();
- doThrow(new RuntimeException("Import
failed")).when(tableDispatcher).loadTable(any());
+ doThrow(new RuntimeException("Import failed"))
+ .when(mockInternalTableDispatcher)
+ .loadTable(any());
RuntimeException thrown =
Assertions.assertThrows(
@@ -204,7 +272,9 @@ public class TestIcebergNamespaceHookDispatcher {
() -> hookDispatcher.registerTable(mockContext, namespace,
mockRequest));
Assertions.assertEquals("Import failed", thrown.getMessage());
+ verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(),
any());
verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
+ verify(mockTableDispatcher, never()).loadTable(any());
}
@Test
@@ -248,8 +318,9 @@ public class TestIcebergNamespaceHookDispatcher {
@SuppressWarnings("unchecked")
ArgumentCaptor<List<MetadataObject>> captor =
ArgumentCaptor.forClass(List.class);
- verify(mockOwnerDispatcher)
+ verify(mockInternalOwnerDispatcher)
.setOwners(eq(TEST_METALAKE), captor.capture(), eq(TEST_USER),
eq(Owner.Type.USER));
+ verify(mockOwnerDispatcher, never()).setOwners(any(), any(), any(), any());
List<String> names =
captor.getValue().stream().map(MetadataObject::fullName).collect(Collectors.toList());
Assertions.assertEquals(
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
index 2d4f2e125c..40a11ec015 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
@@ -76,13 +76,17 @@ public class TestIcebergTableHookDispatcher {
private IcebergTableOperationDispatcher mockDispatcher;
private EntityStore mockEntityStore;
private TableDispatcher mockTableDispatcher;
+ private TableDispatcher mockInternalTableDispatcher;
private OwnerDispatcher mockOwnerDispatcher;
+ private OwnerDispatcher mockInternalOwnerDispatcher;
private IcebergRequestContext mockContext;
private Config previousConfig;
private EntityStore previousEntityStore;
private TableDispatcher previousTableDispatcher;
+ private TableDispatcher previousInternalTableDispatcher;
private OwnerDispatcher previousOwnerDispatcher;
+ private OwnerDispatcher previousInternalOwnerDispatcher;
@BeforeEach
public void setUp() throws IllegalAccessException {
@@ -92,7 +96,9 @@ public class TestIcebergTableHookDispatcher {
// Mock GravitinoEnv components
mockEntityStore = mock(EntityStore.class);
mockTableDispatcher = mock(TableDispatcher.class);
+ mockInternalTableDispatcher = mock(TableDispatcher.class);
mockOwnerDispatcher = mock(OwnerDispatcher.class);
+ mockInternalOwnerDispatcher = mock(OwnerDispatcher.class);
Config mockConfig = mock(Config.class);
when(mockConfig.get(Configs.SCHEMA_SEPARATOR)).thenReturn(":");
@@ -101,12 +107,22 @@ public class TestIcebergTableHookDispatcher {
(EntityStore) FieldUtils.readField(GravitinoEnv.getInstance(),
"entityStore", true);
previousTableDispatcher =
(TableDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(),
"tableDispatcher", true);
+ previousInternalTableDispatcher =
+ (TableDispatcher)
+ FieldUtils.readField(GravitinoEnv.getInstance(),
"internalTableDispatcher", true);
previousOwnerDispatcher =
(OwnerDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(),
"ownerDispatcher", true);
+ previousInternalOwnerDispatcher =
+ (OwnerDispatcher)
+ FieldUtils.readField(GravitinoEnv.getInstance(),
"internalOwnerDispatcher", true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "config", mockConfig,
true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore",
mockEntityStore, true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher",
mockTableDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalTableDispatcher",
mockInternalTableDispatcher, true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher",
mockOwnerDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalOwnerDispatcher",
mockInternalOwnerDispatcher, true);
// Create mock IcebergRESTServerContext
IcebergConfigProvider mockConfigProvider =
mock(IcebergConfigProvider.class);
@@ -131,8 +147,18 @@ public class TestIcebergTableHookDispatcher {
FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore",
previousEntityStore, true);
FieldUtils.writeField(
GravitinoEnv.getInstance(), "tableDispatcher",
previousTableDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalTableDispatcher",
+ previousInternalTableDispatcher,
+ true);
FieldUtils.writeField(
GravitinoEnv.getInstance(), "ownerDispatcher",
previousOwnerDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalOwnerDispatcher",
+ previousInternalOwnerDispatcher,
+ true);
// Reset IcebergRESTServerContext singleton
Class<?> holderClass =
@@ -161,12 +187,14 @@ public class TestIcebergTableHookDispatcher {
NameIdentifier expectedIdentifier =
IcebergIdentifierUtils.toGravitinoTableIdentifier(
TEST_METALAKE, TEST_CATALOG, TableIdentifier.of(namespace,
"test_table"), ":");
- verify(mockTableDispatcher).loadTable(expectedIdentifier);
+ verify(mockInternalTableDispatcher).loadTable(expectedIdentifier);
+ verify(mockTableDispatcher, never()).loadTable(any());
// Verify ownership was set
ArgumentCaptor<String> userCaptor = ArgumentCaptor.forClass(String.class);
- verify(mockOwnerDispatcher)
+ verify(mockInternalOwnerDispatcher)
.setOwner(eq(TEST_METALAKE), any(), userCaptor.capture(),
eq(Owner.Type.USER));
+ verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
Assertions.assertEquals(TEST_USER, userCaptor.getValue());
}
@@ -196,7 +224,8 @@ public class TestIcebergTableHookDispatcher {
IcebergIdentifierUtils.toGravitinoTableIdentifier(
TEST_METALAKE, TEST_CATALOG, tableId, ":");
verify(mockEntityStore, never()).delete(expectedIdentifier,
Entity.EntityType.TABLE);
- verify(mockTableDispatcher).loadTable(expectedIdentifier);
+ verify(mockInternalTableDispatcher).loadTable(expectedIdentifier);
+ verify(mockTableDispatcher, never()).loadTable(any());
}
@Test
@@ -211,7 +240,8 @@ public class TestIcebergTableHookDispatcher {
IcebergIdentifierUtils.toGravitinoTableIdentifier(
TEST_METALAKE, TEST_CATALOG, tableId, ":");
verify(mockEntityStore).delete(expectedIdentifier,
Entity.EntityType.TABLE);
- verify(mockTableDispatcher).loadTable(expectedIdentifier);
+ verify(mockInternalTableDispatcher).loadTable(expectedIdentifier);
+ verify(mockTableDispatcher, never()).loadTable(any());
}
@Test
@@ -293,7 +323,8 @@ public class TestIcebergTableHookDispatcher {
NameIdentifier destIdentifier =
IcebergIdentifierUtils.toGravitinoTableIdentifier(TEST_METALAKE,
TEST_CATALOG, dest, ":");
verify(mockEntityStore).delete(sourceIdentifier, Entity.EntityType.TABLE);
- verify(mockTableDispatcher).loadTable(destIdentifier);
+ verify(mockInternalTableDispatcher).loadTable(destIdentifier);
+ verify(mockTableDispatcher, never()).loadTable(any());
}
@Test
@@ -312,12 +343,15 @@ public class TestIcebergTableHookDispatcher {
when(mockEntityStore.update(any(), eq(TableEntity.class),
eq(Entity.EntityType.TABLE), any()))
.thenReturn(mockTableEntity);
when(mockDispatcher.tableExists(mockContext, dest)).thenReturn(true);
- doThrow(new RuntimeException("import
failed")).when(mockTableDispatcher).loadTable(any());
+ doThrow(new RuntimeException("import failed"))
+ .when(mockInternalTableDispatcher)
+ .loadTable(any());
Assertions.assertDoesNotThrow(() ->
hookDispatcher.renameTable(mockContext, request));
verify(mockDispatcher).renameTable(mockContext, request);
- verify(mockTableDispatcher).loadTable(any());
+ verify(mockInternalTableDispatcher).loadTable(any());
+ verify(mockTableDispatcher, never()).loadTable(any());
}
@Test
@@ -364,7 +398,7 @@ public class TestIcebergTableHookDispatcher {
when(mockDispatcher.createTable(mockContext, namespace,
request)).thenReturn(mockResponse);
doThrow(new RuntimeException("Set owner failed"))
- .when(mockOwnerDispatcher)
+ .when(mockInternalOwnerDispatcher)
.setOwner(any(), any(), any(), any());
RuntimeException thrown =
@@ -373,6 +407,7 @@ public class TestIcebergTableHookDispatcher {
() -> hookDispatcher.createTable(mockContext, namespace, request));
Assertions.assertEquals("Set owner failed", thrown.getMessage());
verify(mockDispatcher).createTable(mockContext, namespace, request);
+ verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
}
@Test
@@ -394,9 +429,11 @@ public class TestIcebergTableHookDispatcher {
verify(mockDispatcher).createTable(mockContext, namespace, request);
// Verify table import was NOT called for staged create
+ verify(mockInternalTableDispatcher, never()).loadTable(any());
verify(mockTableDispatcher, never()).loadTable(any());
// Verify ownership was NOT set for staged create
+ verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(),
any());
verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
}
@@ -419,11 +456,13 @@ public class TestIcebergTableHookDispatcher {
NameIdentifier gravitinoTableId =
IcebergIdentifierUtils.toGravitinoTableIdentifier(
TEST_METALAKE, TEST_CATALOG, tableId, ":");
- verify(mockTableDispatcher).loadTable(gravitinoTableId);
+ verify(mockInternalTableDispatcher).loadTable(gravitinoTableId);
+ verify(mockTableDispatcher, never()).loadTable(any());
ArgumentCaptor<String> userCaptor = ArgumentCaptor.forClass(String.class);
- verify(mockOwnerDispatcher)
+ verify(mockInternalOwnerDispatcher)
.setOwner(eq(TEST_METALAKE), any(), userCaptor.capture(),
eq(Owner.Type.USER));
+ verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
Assertions.assertEquals(TEST_USER, userCaptor.getValue());
}
@@ -444,9 +483,11 @@ public class TestIcebergTableHookDispatcher {
verify(mockDispatcher).updateTable(mockContext, tableId, request);
// Verify table import was NOT called for a regular update
+ verify(mockInternalTableDispatcher, never()).loadTable(any());
verify(mockTableDispatcher, never()).loadTable(any());
// Verify ownership was NOT set
+ verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(),
any());
verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
}
@@ -474,7 +515,9 @@ public class TestIcebergTableHookDispatcher {
// Import failure (the loadTable call) must propagate so the caller learns
the table exists in
// Iceberg but is not registered in Gravitino. setOwner is therefore
unreachable.
- doThrow(new RuntimeException("Import
failed")).when(mockTableDispatcher).loadTable(any());
+ doThrow(new RuntimeException("Import failed"))
+ .when(mockInternalTableDispatcher)
+ .loadTable(any());
RuntimeException thrown =
Assertions.assertThrows(
@@ -483,7 +526,9 @@ public class TestIcebergTableHookDispatcher {
Assertions.assertEquals("Import failed", thrown.getMessage());
verify(mockDispatcher).createTable(mockContext, namespace, request);
- verify(mockTableDispatcher).loadTable(any());
+ verify(mockInternalTableDispatcher).loadTable(any());
+ verify(mockTableDispatcher, never()).loadTable(any());
+ verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(),
any());
verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
}
}
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
index 4fdf1426b9..929aeac842 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
@@ -74,21 +74,27 @@ public class TestIcebergViewHookDispatcher {
private IcebergViewOperationDispatcher mockExecutor;
private EntityStore mockEntityStore;
private ViewDispatcher mockViewDispatcher;
+ private ViewDispatcher mockInternalViewDispatcher;
private OwnerDispatcher mockOwnerDispatcher;
+ private OwnerDispatcher mockInternalOwnerDispatcher;
private IcebergRequestContext mockContext;
private GravitinoEnv gravitinoEnv;
private Config previousConfig;
private EntityStore previousEntityStore;
private ViewDispatcher previousViewDispatcher;
+ private ViewDispatcher previousInternalViewDispatcher;
private OwnerDispatcher previousOwnerDispatcher;
+ private OwnerDispatcher previousInternalOwnerDispatcher;
@BeforeEach
public void setUp() {
mockExecutor = mock(IcebergViewOperationDispatcher.class);
mockEntityStore = mock(EntityStore.class);
mockViewDispatcher = mock(ViewDispatcher.class);
+ mockInternalViewDispatcher = mock(ViewDispatcher.class);
mockOwnerDispatcher = mock(OwnerDispatcher.class);
+ mockInternalOwnerDispatcher = mock(OwnerDispatcher.class);
mockContext = mock(IcebergRequestContext.class);
when(mockContext.catalogName()).thenReturn(CATALOG);
@@ -103,12 +109,20 @@ public class TestIcebergViewHookDispatcher {
previousEntityStore = (EntityStore) FieldUtils.readField(gravitinoEnv,
"entityStore", true);
previousViewDispatcher =
(ViewDispatcher) FieldUtils.readField(gravitinoEnv,
"viewDispatcher", true);
+ previousInternalViewDispatcher =
+ (ViewDispatcher) FieldUtils.readField(gravitinoEnv,
"internalViewDispatcher", true);
previousOwnerDispatcher =
(OwnerDispatcher) FieldUtils.readField(gravitinoEnv,
"ownerDispatcher", true);
+ previousInternalOwnerDispatcher =
+ (OwnerDispatcher) FieldUtils.readField(gravitinoEnv,
"internalOwnerDispatcher", true);
FieldUtils.writeField(gravitinoEnv, "config", mockConfig, true);
FieldUtils.writeField(gravitinoEnv, "entityStore", mockEntityStore,
true);
FieldUtils.writeField(gravitinoEnv, "viewDispatcher",
mockViewDispatcher, true);
+ FieldUtils.writeField(
+ gravitinoEnv, "internalViewDispatcher", mockInternalViewDispatcher,
true);
FieldUtils.writeField(gravitinoEnv, "ownerDispatcher",
mockOwnerDispatcher, true);
+ FieldUtils.writeField(
+ gravitinoEnv, "internalOwnerDispatcher",
mockInternalOwnerDispatcher, true);
} catch (Exception e) {
throw new RuntimeException("Failed to setup test", e);
}
@@ -124,7 +138,11 @@ public class TestIcebergViewHookDispatcher {
FieldUtils.writeField(gravitinoEnv, "config", previousConfig, true);
FieldUtils.writeField(gravitinoEnv, "entityStore", previousEntityStore,
true);
FieldUtils.writeField(gravitinoEnv, "viewDispatcher",
previousViewDispatcher, true);
+ FieldUtils.writeField(
+ gravitinoEnv, "internalViewDispatcher",
previousInternalViewDispatcher, true);
FieldUtils.writeField(gravitinoEnv, "ownerDispatcher",
previousOwnerDispatcher, true);
+ FieldUtils.writeField(
+ gravitinoEnv, "internalOwnerDispatcher",
previousInternalOwnerDispatcher, true);
} catch (Exception e) {
// Ignore cleanup errors
}
@@ -161,10 +179,12 @@ public class TestIcebergViewHookDispatcher {
// Verify view was imported into Gravitino
NameIdentifier expectedIdent = NameIdentifier.of(METALAKE, CATALOG,
SCHEMA_NAME, VIEW_NAME);
- verify(mockViewDispatcher, times(1)).loadView(eq(expectedIdent));
+ verify(mockInternalViewDispatcher, times(1)).loadView(eq(expectedIdent));
+ verify(mockViewDispatcher, never()).loadView(any());
// Verify ownership was set
- verify(mockOwnerDispatcher, times(1)).setOwner(any(), any(), eq(USER),
any());
+ verify(mockInternalOwnerDispatcher, times(1)).setOwner(any(), any(),
eq(USER), any());
+ verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
assertEquals(mockResponse, response);
}
@@ -195,7 +215,7 @@ public class TestIcebergViewHookDispatcher {
// Simulate import failure
doThrow(new RuntimeException("Import failed"))
- .when(mockViewDispatcher)
+ .when(mockInternalViewDispatcher)
.loadView(any(NameIdentifier.class));
// Should not throw - import is best-effort
@@ -230,7 +250,7 @@ public class TestIcebergViewHookDispatcher {
when(mockExecutor.createView(mockContext, namespace,
createRequest)).thenReturn(mockResponse);
doThrow(new RuntimeException("Set owner failed"))
- .when(mockOwnerDispatcher)
+ .when(mockInternalOwnerDispatcher)
.setOwner(any(), any(), any(), any());
RuntimeException thrown =
@@ -239,7 +259,9 @@ public class TestIcebergViewHookDispatcher {
() -> hookDispatcher.createView(mockContext, namespace,
createRequest));
assertEquals("Set owner failed", thrown.getMessage());
verify(mockExecutor, times(1)).createView(mockContext, namespace,
createRequest);
- verify(mockViewDispatcher, times(1)).loadView(any(NameIdentifier.class));
+ verify(mockInternalViewDispatcher,
times(1)).loadView(any(NameIdentifier.class));
+ verify(mockViewDispatcher, never()).loadView(any());
+ verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
}
@Test
@@ -268,7 +290,8 @@ public class TestIcebergViewHookDispatcher {
NameIdentifier expectedIdent =
IcebergIdentifierUtils.toGravitinoTableIdentifier(METALAKE, CATALOG,
viewIdent, ":");
verify(mockEntityStore, never()).delete(eq(expectedIdent),
eq(Entity.EntityType.VIEW));
- verify(mockViewDispatcher, times(1)).loadView(eq(expectedIdent));
+ verify(mockInternalViewDispatcher, times(1)).loadView(eq(expectedIdent));
+ verify(mockViewDispatcher, never()).loadView(any());
}
@Test
@@ -282,7 +305,8 @@ public class TestIcebergViewHookDispatcher {
NameIdentifier expectedIdent =
IcebergIdentifierUtils.toGravitinoTableIdentifier(METALAKE, CATALOG,
viewIdent, ":");
verify(mockEntityStore, times(1)).delete(eq(expectedIdent),
eq(Entity.EntityType.VIEW));
- verify(mockViewDispatcher, times(1)).loadView(eq(expectedIdent));
+ verify(mockInternalViewDispatcher, times(1)).loadView(eq(expectedIdent));
+ verify(mockViewDispatcher, never()).loadView(any());
}
@Test
@@ -354,7 +378,8 @@ public class TestIcebergViewHookDispatcher {
NameIdentifier destGravitinoIdent =
IcebergIdentifierUtils.toGravitinoTableIdentifier(METALAKE, CATALOG,
destIdent, ":");
verify(mockEntityStore, times(1)).delete(eq(sourceGravitinoIdent),
eq(Entity.EntityType.VIEW));
- verify(mockViewDispatcher, times(1)).loadView(eq(destGravitinoIdent));
+ verify(mockInternalViewDispatcher,
times(1)).loadView(eq(destGravitinoIdent));
+ verify(mockViewDispatcher, never()).loadView(any());
}
@Test
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
index 669e41a24d..2177778734 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
@@ -63,6 +63,7 @@ public class TestDynamicIcebergConfigProvider {
public void tearDown() throws IllegalAccessException {
// Clean up GravitinoEnv and IcebergRESTServerContext state after each test
FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogDispatcher",
null, true);
+ FieldUtils.writeField(GravitinoEnv.getInstance(),
"internalCatalogDispatcher", null, true);
resetServerContext();
}
@@ -265,12 +266,13 @@ public class TestDynamicIcebergConfigProvider {
// Enable authorization to use internal fetcher
createMockServerContext(true);
- // Mock CatalogDispatcher
+ // Mock CatalogDispatchers
CatalogDispatcher mockCatalogDispatcher =
Mockito.mock(CatalogDispatcher.class);
+ CatalogDispatcher mockInternalCatalogDispatcher =
Mockito.mock(CatalogDispatcher.class);
Catalog mockCatalog = Mockito.mock(Catalog.class);
NameIdentifier catalogIdent = NameIdentifierUtil.ofCatalog(metalakeName,
catalogName);
-
Mockito.when(mockCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
+
Mockito.when(mockInternalCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
Mockito.when(mockCatalog.provider()).thenReturn("lakehouse-iceberg");
Mockito.when(mockCatalog.properties())
.thenReturn(
@@ -281,9 +283,14 @@ public class TestDynamicIcebergConfigProvider {
}
});
- // Set the mock CatalogDispatcher to GravitinoEnv
+ // Set the mock CatalogDispatchers to GravitinoEnv
FieldUtils.writeField(
GravitinoEnv.getInstance(), "catalogDispatcher",
mockCatalogDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalCatalogDispatcher",
+ mockInternalCatalogDispatcher,
+ true);
// Initialize provider with required properties
Map<String, String> properties = new HashMap<>();
@@ -292,11 +299,12 @@ public class TestDynamicIcebergConfigProvider {
DynamicIcebergConfigProvider provider = new DynamicIcebergConfigProvider();
provider.initialize(properties);
- // Test that internal interface is used (CatalogDispatcher should be
called)
+ // Test that internal interface is used (internal CatalogDispatcher should
be called)
Optional<IcebergConfig> icebergConfig =
provider.getIcebergCatalogConfig(catalogName);
Assertions.assertTrue(icebergConfig.isPresent());
- Mockito.verify(mockCatalogDispatcher).loadCatalog(catalogIdent);
+ Mockito.verify(mockInternalCatalogDispatcher).loadCatalog(catalogIdent);
+ Mockito.verify(mockCatalogDispatcher,
Mockito.never()).loadCatalog(catalogIdent);
}
@Test
@@ -345,8 +353,9 @@ public class TestDynamicIcebergConfigProvider {
// Enable authorization to use internal fetcher
createMockServerContext(true);
- // Ensure CatalogDispatcher is null (simulating GravitinoEnv not
initialized)
+ // Ensure internal CatalogDispatcher is null (simulating GravitinoEnv not
initialized)
FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogDispatcher",
null, true);
+ FieldUtils.writeField(GravitinoEnv.getInstance(),
"internalCatalogDispatcher", null, true);
// Initialize provider with required properties
Map<String, String> properties = new HashMap<>();
@@ -355,9 +364,13 @@ public class TestDynamicIcebergConfigProvider {
DynamicIcebergConfigProvider provider = new DynamicIcebergConfigProvider();
provider.initialize(properties);
- // Test that IllegalStateException is thrown when CatalogDispatcher is null
- Assertions.assertThrows(
- IllegalStateException.class, () ->
provider.getIcebergCatalogConfig(catalogName));
+ IllegalStateException exception =
+ Assertions.assertThrows(
+ IllegalStateException.class, () ->
provider.getIcebergCatalogConfig(catalogName));
+ Assertions.assertEquals(
+ "Internal CatalogDispatcher is not available. "
+ + "Internal catalog fetcher requires running within Gravitino
server.",
+ exception.getMessage());
}
@Test
@@ -368,16 +381,22 @@ public class TestDynamicIcebergConfigProvider {
// Enable authorization to use internal fetcher
createMockServerContext(true);
- // Mock CatalogDispatcher to throw NoSuchCatalogException
+ // Mock internal CatalogDispatcher to throw NoSuchCatalogException
CatalogDispatcher mockCatalogDispatcher =
Mockito.mock(CatalogDispatcher.class);
+ CatalogDispatcher mockInternalCatalogDispatcher =
Mockito.mock(CatalogDispatcher.class);
NameIdentifier catalogIdent =
NameIdentifierUtil.ofCatalog(metalakeName, nonExistentCatalogName);
- Mockito.when(mockCatalogDispatcher.loadCatalog(catalogIdent))
+ Mockito.when(mockInternalCatalogDispatcher.loadCatalog(catalogIdent))
.thenThrow(new NoSuchCatalogException("Catalog not found: %s",
nonExistentCatalogName));
- // Set the mock CatalogDispatcher to GravitinoEnv
+ // Set the mock CatalogDispatchers to GravitinoEnv
FieldUtils.writeField(
GravitinoEnv.getInstance(), "catalogDispatcher",
mockCatalogDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalCatalogDispatcher",
+ mockInternalCatalogDispatcher,
+ true);
// Initialize provider with required properties
Map<String, String> properties = new HashMap<>();
@@ -390,7 +409,8 @@ public class TestDynamicIcebergConfigProvider {
Optional<IcebergConfig> result =
provider.getIcebergCatalogConfig(nonExistentCatalogName);
Assertions.assertFalse(result.isPresent());
- Mockito.verify(mockCatalogDispatcher).loadCatalog(catalogIdent);
+ Mockito.verify(mockInternalCatalogDispatcher).loadCatalog(catalogIdent);
+ Mockito.verify(mockCatalogDispatcher,
Mockito.never()).loadCatalog(catalogIdent);
}
@Test
@@ -474,12 +494,13 @@ public class TestDynamicIcebergConfigProvider {
// Enable authorization to use internal fetcher
createMockServerContext(true);
- // Mock CatalogDispatcher
+ // Mock CatalogDispatchers
CatalogDispatcher mockCatalogDispatcher =
Mockito.mock(CatalogDispatcher.class);
+ CatalogDispatcher mockInternalCatalogDispatcher =
Mockito.mock(CatalogDispatcher.class);
Catalog mockCatalog = Mockito.mock(Catalog.class);
NameIdentifier catalogIdent = NameIdentifierUtil.ofCatalog(metalakeName,
catalogName);
-
Mockito.when(mockCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
+
Mockito.when(mockInternalCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
Mockito.when(mockCatalog.provider()).thenReturn("lakehouse-iceberg");
Mockito.when(mockCatalog.properties())
.thenReturn(
@@ -490,9 +511,14 @@ public class TestDynamicIcebergConfigProvider {
}
});
- // Set the mock CatalogDispatcher to GravitinoEnv
+ // Set the mock CatalogDispatchers to GravitinoEnv
FieldUtils.writeField(
GravitinoEnv.getInstance(), "catalogDispatcher",
mockCatalogDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(),
+ "internalCatalogDispatcher",
+ mockInternalCatalogDispatcher,
+ true);
// Initialize provider with required properties
Map<String, String> properties = new HashMap<>();
@@ -535,8 +561,10 @@ public class TestDynamicIcebergConfigProvider {
Assertions.assertTrue(result.isPresent(), "Each thread should get a
valid config");
}
- // Verify CatalogDispatcher was called (at least once, possibly more due
to concurrency)
- Mockito.verify(mockCatalogDispatcher,
Mockito.atLeastOnce()).loadCatalog(catalogIdent);
+ // Verify internal CatalogDispatcher was called (at least once, possibly
more due to
+ // concurrency)
+ Mockito.verify(mockInternalCatalogDispatcher,
Mockito.atLeastOnce()).loadCatalog(catalogIdent);
+ Mockito.verify(mockCatalogDispatcher,
Mockito.never()).loadCatalog(catalogIdent);
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
index c58ffea12e..deab84b743 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
@@ -83,7 +83,8 @@ public class PassThroughAuthorizer implements
GravitinoAuthorizer {
@Override
public boolean isMetalakeUser(String metalake, AuthorizationRequestContext
requestContext) {
- AccessControlDispatcher dispatcher =
GravitinoEnv.getInstance().accessControlDispatcher();
+ AccessControlDispatcher dispatcher =
+ GravitinoEnv.getInstance().internalAccessControlDispatcher();
if (dispatcher != null) {
try {
dispatcher.getUser(metalake, PrincipalUtils.getCurrentUserName());
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
index e6099a6c47..4f7efb875e 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
@@ -18,15 +18,22 @@
package org.apache.gravitino.server.authorization;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.security.Principal;
+import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.AccessControlDispatcher;
import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.authorization.User;
+import org.apache.gravitino.utils.PrincipalUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -73,4 +80,32 @@ public class TestPassThroughAuthorizer {
"metalake", "type", "fullName", new
AuthorizationRequestContext()));
}
}
+
+ @Test
+ public void testIsMetalakeUserUsesInternalAccessControlDispatcher() throws
Exception {
+ AccessControlDispatcher dispatcher = mock(AccessControlDispatcher.class);
+ User user = mock(User.class);
+ when(dispatcher.getUser("metalake", "testUser")).thenReturn(user);
+
+ AccessControlDispatcher previousDispatcher =
+ (AccessControlDispatcher)
+ FieldUtils.readField(
+ GravitinoEnv.getInstance(), "internalAccessControlDispatcher",
true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalAccessControlDispatcher",
dispatcher, true);
+ try (PassThroughAuthorizer passThroughAuthorizer = new
PassThroughAuthorizer()) {
+ PrincipalUtils.doAs(
+ new UserPrincipal("testUser"),
+ () -> {
+ Assertions.assertTrue(
+ passThroughAuthorizer.isMetalakeUser(
+ "metalake", new AuthorizationRequestContext()));
+ return null;
+ });
+ verify(dispatcher).getUser("metalake", "testUser");
+ } finally {
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "internalAccessControlDispatcher",
previousDispatcher, true);
+ }
+ }
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
index 67f84a6f99..5f28bfc7dd 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
@@ -372,6 +372,12 @@ public class TestJcasbinAuthorizer {
}
}
+ @Test
+ public void testIsMetalakeUserUsesUserInfoCache() {
+ assertTrue(jcasbinAuthorizer.isMetalakeUser(METALAKE, new
AuthorizationRequestContext()));
+ verify(userMetaMapper).getUserUpdatedAt(METALAKE, USERNAME);
+ }
+
@Test
public void testAuthorize() throws Exception {
makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);