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 30687f68ff [#12851] fix(iceberg-rest): Share the managed memory
catalog in auxiliary mode (#12852)
30687f68ff is described below
commit 30687f68ff8c2a276a4a0fbf13211435da36f7f9
Author: roryqi <[email protected]>
AuthorDate: Fri Sep 4 16:33:58 2026 +0800
[#12851] fix(iceberg-rest): Share the managed memory catalog in auxiliary
mode (#12852)
### What changes were proposed in this pull request?
- Expose the Iceberg backend owned by `IcebergCatalogOperations`.
- Reuse that backend when the auxiliary Iceberg REST service serves a
memory catalog.
- Resolve the REST `default_catalog` alias to the configured Gravitino
catalog.
- Prevent the REST wrapper from closing the borrowed catalog.
- Add regression tests covering namespace, table, and view creation for
both explicit catalog names
and the default catalog alias.
### Why are the changes needed?
The auxiliary Iceberg REST service currently creates a second
InMemoryCatalog. REST creates are
written to that instance, while metadata import uses the instance owned
by Gravitino CatalogManager.
Because in-memory state is local to each instance, the import cannot
find the newly created object
and leaves Iceberg and Gravitino metadata inconsistent.
Fix: #12851
### Does this PR introduce _any_ user-facing change?
Yes. Namespace, table, and view creation through the Iceberg REST API
now succeeds with a memory
backend, and the created objects are visible through the Gravitino
metadata API.
No public API or configuration property is added or removed.
### How was this patch tested?
- `./gradlew spotlessApply`
- `./gradlew :iceberg:iceberg-common:test --tests
org.apache.gravitino.iceberg.common.ops.TestIcebergCatalogWrapper`
- `./gradlew :catalogs:catalog-lakehouse-iceberg:compileJava`
- `./gradlew :iceberg:iceberg-rest-server:test --tests
org.apache.gravitino.iceberg.service.TestIcebergCatalogWrapperManagerForREST`
- `./gradlew :iceberg:iceberg-rest-server:test --tests
org.apache.gravitino.iceberg.service.TestIcebergCatalogWrapperManagerForREST
--tests
org.apache.gravitino.iceberg.service.dispatcher.TestIcebergNamespaceHookDispatcher
--tests
org.apache.gravitino.iceberg.service.dispatcher.TestIcebergTableHookDispatcher
--tests
org.apache.gravitino.iceberg.service.dispatcher.TestIcebergViewHookDispatcher`
- `./gradlew :catalogs:catalog-lakehouse-iceberg:test --tests
org.apache.gravitino.catalog.lakehouse.iceberg.integration.test.CatalogIcebergRestIT
-PskipTests -PtestMode=embedded -PjdbcBackend=h2 -PskipDockerTests=false
-PskipWeb=true`
---
.../lakehouse/iceberg/IcebergConstants.java | 3 +
.../iceberg/IcebergCatalogOperations.java | 16 +++-
.../iceberg/TestIcebergCatalogOperations.java | 23 ++++++
.../lakehouse/iceberg/TestIcebergSchema.java | 3 +-
.../apache/gravitino/catalog/CatalogManager.java | 12 +++
.../gravitino/connector/CatalogDropAware.java | 29 ++++++++
.../gravitino/catalog/TestCatalogManager.java | 13 ++++
.../iceberg/common/ops/IcebergCatalogWrapper.java | 6 +-
.../iceberg/common/utils/IcebergCatalogUtil.java | 35 +++++++++
.../common/ops/TestIcebergCatalogWrapper.java | 27 +++++++
.../common/utils/TestIcebergCatalogUtil.java | 86 ++++++++++++++++++++++
.../service/IcebergCatalogWrapperManager.java | 4 +
.../provider/DynamicIcebergConfigProvider.java | 13 ++--
.../TestIcebergCatalogWrapperManagerForREST.java | 46 +++++++++++-
.../provider/TestDynamicIcebergConfigProvider.java | 6 ++
15 files changed, 312 insertions(+), 10 deletions(-)
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
index 354153b303..2d29e9e754 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
@@ -24,6 +24,9 @@ public class IcebergConstants {
public static final String CATALOG_BACKEND = "catalog-backend";
public static final String CATALOG_BACKEND_IMPL = "catalog-backend-impl";
+ /** Internal property containing the unique identifier of a Gravitino
catalog. */
+ public static final String CATALOG_UUID = "catalog_uuid";
+
public static final String GRAVITINO_JDBC_USER = "jdbc-user";
public static final String ICEBERG_JDBC_USER = "jdbc.user";
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
index ea53a7fd0f..140db99ff0 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogOperations.java
@@ -31,12 +31,14 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import javax.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.SchemaChange;
import
org.apache.gravitino.catalog.lakehouse.iceberg.ops.IcebergCatalogWrapperHelper;
+import org.apache.gravitino.connector.CatalogDropAware;
import org.apache.gravitino.connector.CatalogInfo;
import org.apache.gravitino.connector.CatalogOperations;
import org.apache.gravitino.connector.HasPropertyMetadata;
@@ -56,6 +58,7 @@ import
org.apache.gravitino.iceberg.common.authentication.SupportsKerberos;
import org.apache.gravitino.iceberg.common.ops.IcebergCatalogWrapper;
import
org.apache.gravitino.iceberg.common.ops.IcebergCatalogWrapper.IcebergTableChange;
import
org.apache.gravitino.iceberg.common.ops.KerberosAwareIcebergCatalogProxy;
+import org.apache.gravitino.iceberg.common.utils.IcebergCatalogUtil;
import org.apache.gravitino.iceberg.common.utils.IcebergIdentifierUtils;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.rel.Column;
@@ -92,7 +95,7 @@ import org.slf4j.LoggerFactory;
/** Operations for interacting with an Apache Iceberg catalog in Apache
Gravitino. */
public class IcebergCatalogOperations
- implements CatalogOperations, SupportsSchemas, TableCatalog, ViewCatalog {
+ implements CatalogOperations, SupportsSchemas, TableCatalog, ViewCatalog,
CatalogDropAware {
private static final String ICEBERG_TABLE_DOES_NOT_EXIST_MSG = "Iceberg
table does not exist: %s";
@@ -100,6 +103,7 @@ public class IcebergCatalogOperations
@VisibleForTesting IcebergCatalogWrapper icebergCatalogWrapper;
+ @VisibleForTesting @Nullable String catalogUuid;
private IcebergCatalogWrapperHelper icebergCatalogWrapperHelper;
private IcebergViewCatalogOperations icebergViewCatalogOperations;
@@ -124,7 +128,8 @@ public class IcebergCatalogOperations
Map<String, String> resultConf = Maps.newHashMap(prefixMap);
resultConf.putAll(gravitinoConfig);
- resultConf.put("catalog_uuid", info.id().toString());
+ this.catalogUuid = info.id().toString();
+ resultConf.put(IcebergConstants.CATALOG_UUID, catalogUuid);
IcebergConfig icebergConfig = new IcebergConfig(resultConf);
IcebergCatalogWrapper rawWrapper = new
IcebergCatalogWrapper(icebergConfig);
@@ -154,6 +159,13 @@ public class IcebergCatalogOperations
}
}
+ @Override
+ public void onCatalogDropped() {
+ if (catalogUuid != null) {
+ IcebergCatalogUtil.removeMemoryCatalog(catalogUuid);
+ }
+ }
+
// Maps an Iceberg REST-client exception into Gravitino's taxonomy
@VisibleForTesting
static RuntimeException handleRestException(RESTException e) {
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
index 841d3ecfaa..9b42c1849f 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogOperations.java
@@ -31,6 +31,7 @@ import
org.apache.gravitino.exceptions.ConnectionFailedException;
import org.apache.gravitino.exceptions.GravitinoRuntimeException;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.gravitino.iceberg.common.ops.IcebergCatalogWrapper;
+import org.apache.gravitino.iceberg.common.utils.IcebergCatalogUtil;
import org.apache.iceberg.exceptions.BadRequestException;
import org.apache.iceberg.exceptions.RESTException;
import org.apache.iceberg.exceptions.ServiceUnavailableException;
@@ -148,4 +149,26 @@ public class TestIcebergCatalogOperations {
Assertions.assertTrue(Arrays.stream(result).anyMatch(id ->
"db1".equals(id.name())));
Assertions.assertTrue(Arrays.stream(result).anyMatch(id ->
"db2".equals(id.name())));
}
+
+ @Test
+ public void testCatalogDropRemovesInternedMemoryCatalog() {
+ String catalogUuid = "catalog-uuid";
+ IcebergConfig config =
+ new IcebergConfig(
+ ImmutableMap.of(
+ IcebergConstants.CATALOG_BACKEND,
+ "memory",
+ IcebergConstants.CATALOG_UUID,
+ catalogUuid));
+ Object original =
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+
+ IcebergCatalogOperations catalogOperations = new
IcebergCatalogOperations();
+ catalogOperations.catalogUuid = catalogUuid;
+ catalogOperations.onCatalogDropped();
+
+ Object replacement =
+ IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY,
config);
+ Assertions.assertNotSame(original, replacement);
+ IcebergCatalogUtil.removeMemoryCatalog(catalogUuid);
+ }
}
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergSchema.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergSchema.java
index 9c9f6f5de5..c028c24b2e 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergSchema.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergSchema.java
@@ -187,7 +187,8 @@ public class TestIcebergSchema {
private IcebergCatalog initIcebergCatalog(String name) {
CatalogEntity entity =
CatalogEntity.builder()
- .withId(1L)
+ // Each test creates a different catalog, so it must also use a
distinct catalog ID.
+ .withId(Integer.toUnsignedLong(name.hashCode()))
.withName(name)
.withNamespace(Namespace.of(META_LAKE_NAME))
.withType(IcebergCatalog.Type.RELATIONAL)
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index c78438838c..c3ca9701c9 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -79,6 +79,7 @@ import org.apache.gravitino.Namespace;
import org.apache.gravitino.Schema;
import org.apache.gravitino.StringIdentifier;
import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.CatalogDropAware;
import org.apache.gravitino.connector.CatalogOperations;
import org.apache.gravitino.connector.HasPropertyMetadata;
import org.apache.gravitino.connector.SupportsSchemas;
@@ -1142,6 +1143,17 @@ public class CatalogManager implements
CatalogDispatcher, Closeable {
boolean deleted = store.delete(ident, EntityType.CATALOG, true);
if (deleted) {
markLocalMutation(ident);
+ try {
+ catalogWrapper.doWithCatalogOps(
+ operations -> {
+ if (operations instanceof CatalogDropAware) {
+ ((CatalogDropAware) operations).onCatalogDropped();
+ }
+ return null;
+ });
+ } catch (Exception e) {
+ LOG.warn("Failed to clean up resources for dropped catalog
{}", ident, e);
+ }
// Unmanaged: schemas removed only via store cascade — clean
secrets captured above.
if (!managedStorage) {
for (Map<String, String> schemaProperties :
unmanagedSchemaSecrets) {
diff --git
a/core/src/main/java/org/apache/gravitino/connector/CatalogDropAware.java
b/core/src/main/java/org/apache/gravitino/connector/CatalogDropAware.java
new file mode 100644
index 0000000000..2972808c1c
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/connector/CatalogDropAware.java
@@ -0,0 +1,29 @@
+/*
+ * 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.connector;
+
+import org.apache.gravitino.annotation.Evolving;
+
+/** Supports cleanup that must run only when a catalog is permanently dropped.
*/
+@Evolving
+public interface CatalogDropAware {
+
+ /** Performs cleanup after the catalog metadata has been permanently
dropped. */
+ void onCatalogDropped();
+}
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
index 7697431211..26380a5833 100644
--- a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
@@ -62,6 +62,7 @@ import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.Schema;
import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.CatalogDropAware;
import org.apache.gravitino.connector.CatalogOperations;
import org.apache.gravitino.connector.HiddenPropertyMaskUtils;
import org.apache.gravitino.connector.TestCatalogOperations;
@@ -1369,10 +1370,21 @@ public class TestCatalogManager {
Mockito.mock(CatalogManager.CatalogWrapper.class,
Mockito.RETURNS_DEEP_STUBS);
Capability capability = Mockito.mock(Capability.class);
CapabilityResult unsupportedResult = CapabilityResult.unsupported("Not
managed");
+ CatalogOperations operations =
+ Mockito.mock(
+ CatalogOperations.class,
+ Mockito.withSettings().extraInterfaces(CatalogDropAware.class));
Mockito.doReturn(catalogWrapper).when(catalogManager).loadCatalogAndWrap(ident);
Mockito.doReturn(catalog).when(catalogWrapper).catalog();
Mockito.doReturn(capability).when(catalogWrapper).capabilities();
Mockito.doReturn(unsupportedResult).when(capability).managedStorage(any());
+ Mockito.doAnswer(
+ invocation -> {
+ ThrowableFunction<CatalogOperations, ?> function =
invocation.getArgument(0);
+ return function.apply(operations);
+ })
+ .when(catalogWrapper)
+ .doWithCatalogOps(any());
catalogManager.getCatalogCache().put(ident, catalogWrapper);
boolean dropped = catalogManager.dropCatalog(ident);
@@ -1380,6 +1392,7 @@ public class TestCatalogManager {
Assertions.assertTrue(dropped);
Assertions.assertFalse(entityStore.exists(ident, EntityType.CATALOG));
Assertions.assertNull(catalogManager.getCatalogCache().getIfPresent(ident));
+ Mockito.verify((CatalogDropAware) operations).onCatalogDropped();
}
@Test
diff --git
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java
index 8d2196ecca..ed58b2885b 100644
---
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java
+++
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java
@@ -27,6 +27,7 @@ import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.catalog.hadoop.fs.FileSystemUtils;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
+import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.gravitino.iceberg.common.cache.SupportsMetadataLocation;
import org.apache.gravitino.iceberg.common.cache.TableMetadataCache;
@@ -389,7 +390,10 @@ public class IcebergCatalogWrapper implements
AutoCloseable {
} else {
LOG.info("Closing IcebergCatalogWrapper before catalog is initialized");
}
- if (loadedCatalog instanceof AutoCloseable) {
+ boolean internedMemoryCatalog =
+ catalogBackend == IcebergCatalogBackend.MEMORY
+ &&
icebergConfig.getAllConfig().containsKey(IcebergConstants.CATALOG_UUID);
+ if (!internedMemoryCatalog && loadedCatalog instanceof AutoCloseable) {
// JdbcCatalog and ClosableHiveCatalog implement AutoCloseable and will
handle their own
// cleanup
((AutoCloseable) loadedCatalog).close();
diff --git
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
index 5674b6c1a7..745b6b1d4f 100644
---
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
+++
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
@@ -23,11 +23,13 @@ import static
org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
+import java.io.IOException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.exceptions.ConnectionFailedException;
@@ -63,7 +65,19 @@ public class IcebergCatalogUtil {
*/
private static final String ICEBERG_TYPE_COLUMN = "iceberg_type";
+ private static final ConcurrentHashMap<String, InMemoryCatalog>
MEMORY_CATALOGS =
+ new ConcurrentHashMap<>();
+
private static InMemoryCatalog loadMemoryCatalog(IcebergConfig
icebergConfig) {
+ String catalogUuid =
icebergConfig.getAllConfig().get(IcebergConstants.CATALOG_UUID);
+ if (catalogUuid == null) {
+ return createMemoryCatalog(icebergConfig);
+ }
+ return MEMORY_CATALOGS.computeIfAbsent(
+ catalogUuid, ignored -> createMemoryCatalog(icebergConfig));
+ }
+
+ private static InMemoryCatalog createMemoryCatalog(IcebergConfig
icebergConfig) {
String icebergCatalogName = icebergConfig.getCatalogBackendName();
InMemoryCatalog memoryCatalog = new
MemoryCatalogWithMetadataLocationSupport();
Map<String, String> resultProperties =
icebergConfig.getIcebergCatalogProperties();
@@ -75,6 +89,27 @@ public class IcebergCatalogUtil {
return memoryCatalog;
}
+ /**
+ * Removes the in-memory Iceberg catalog associated with a permanently
dropped Gravitino catalog.
+ *
+ * @param catalogUuid the unique Gravitino catalog identifier
+ */
+ public static void removeMemoryCatalog(String catalogUuid) {
+ InMemoryCatalog memoryCatalog = MEMORY_CATALOGS.remove(catalogUuid);
+ if (memoryCatalog != null) {
+ try {
+ memoryCatalog.close();
+ } catch (IOException e) {
+ LOG.warn("Failed to close dropped in-memory Iceberg catalog {}",
catalogUuid, e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static void clearMemoryCatalogs() {
+ MEMORY_CATALOGS.clear();
+ }
+
private static HiveCatalog loadHiveCatalog(IcebergConfig icebergConfig) {
ClosableHiveCatalog hiveCatalog = new
HiveCatalogWithMetadataLocationSupport();
HdfsConfiguration hdfsConfiguration = new HdfsConfiguration();
diff --git
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/ops/TestIcebergCatalogWrapper.java
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/ops/TestIcebergCatalogWrapper.java
index e34f1becb3..e6928e56a8 100644
---
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/ops/TestIcebergCatalogWrapper.java
+++
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/ops/TestIcebergCatalogWrapper.java
@@ -30,8 +30,12 @@ import
org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.gravitino.iceberg.common.cache.SupportsMetadataLocation;
import org.apache.gravitino.iceberg.common.cache.TableMetadataCache;
+import org.apache.gravitino.iceberg.common.utils.IcebergCatalogUtil;
import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Catalog;
+import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -57,6 +61,29 @@ public class TestIcebergCatalogWrapper {
});
}
+ @Test
+ public void testMemoryCatalogSurvivesWrapperClose() throws Exception {
+ String catalogUuid = "catalog-1";
+ IcebergConfig config =
+ new IcebergConfig(
+ Map.of(
+ IcebergConstants.CATALOG_BACKEND,
+ "memory",
+ IcebergConstants.CATALOG_UUID,
+ catalogUuid));
+ IcebergCatalogWrapper firstWrapper = new IcebergCatalogWrapper(config);
+ Catalog firstCatalog = firstWrapper.getCatalog();
+ Namespace namespace = Namespace.of("preserved");
+
firstWrapper.createNamespace(CreateNamespaceRequest.builder().withNamespace(namespace).build());
+
+ firstWrapper.close();
+ IcebergCatalogWrapper secondWrapper = new IcebergCatalogWrapper(config);
+
+ Assertions.assertSame(firstCatalog, secondWrapper.getCatalog());
+ Assertions.assertTrue(secondWrapper.namespaceExists(namespace));
+ IcebergCatalogUtil.removeMemoryCatalog(catalogUuid);
+ }
+
@Test
public void testMetadataCacheShouldInitializeOnFirstAccessAndClose(@TempDir
Path warehouseDir)
throws Exception {
diff --git
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
index a575352d3e..f5ee1b8cf0 100644
---
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
+++
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
@@ -40,6 +40,7 @@ import org.apache.iceberg.inmemory.InMemoryCatalog;
import org.apache.iceberg.jdbc.JdbcCatalogWithMetadataLocationSupport;
import org.apache.iceberg.jdbc.UncheckedSQLException;
import org.apache.iceberg.types.Types;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -48,6 +49,91 @@ public class TestIcebergCatalogUtil {
@TempDir private Path warehouse;
+ @AfterEach
+ void tearDown() {
+ IcebergCatalogUtil.clearMemoryCatalogs();
+ }
+
+ @Test
+ void testMemoryCatalogIsInternedByCatalogUuid() {
+ Map<String, String> properties =
+ Map.of(
+ IcebergConstants.CATALOG_BACKEND, "memory",
IcebergConstants.CATALOG_UUID, "catalog-1");
+ IcebergConfig config = new IcebergConfig(properties);
+
+ InMemoryCatalog first =
+ (InMemoryCatalog)
+
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+ Namespace namespace = Namespace.of("shared");
+ first.createNamespace(namespace);
+ InMemoryCatalog second =
+ (InMemoryCatalog)
+
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+
+ Assertions.assertSame(first, second);
+ Assertions.assertTrue(second.namespaceExists(namespace));
+ }
+
+ @Test
+ void testMemoryCatalogsWithDifferentUuidsAreIsolated() {
+ IcebergConfig firstConfig =
+ new IcebergConfig(
+ Map.of(
+ IcebergConstants.CATALOG_BACKEND,
+ "memory",
+ IcebergConstants.CATALOG_UUID,
+ "catalog-1"));
+ IcebergConfig secondConfig =
+ new IcebergConfig(
+ Map.of(
+ IcebergConstants.CATALOG_BACKEND,
+ "memory",
+ IcebergConstants.CATALOG_UUID,
+ "catalog-2"));
+
+ Catalog first =
+ IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY,
firstConfig);
+ Catalog second =
+ IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY,
secondConfig);
+
+ Assertions.assertNotSame(first, second);
+ }
+
+ @Test
+ void testMemoryCatalogWithoutUuidIsNotInterned() {
+ IcebergConfig config = new
IcebergConfig(Map.of(IcebergConstants.CATALOG_BACKEND, "memory"));
+
+ Catalog first =
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+ Catalog second =
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+
+ Assertions.assertNotSame(first, second);
+ }
+
+ @Test
+ void testDroppedMemoryCatalogIsRemoved() {
+ String catalogUuid = "catalog-1";
+ IcebergConfig config =
+ new IcebergConfig(
+ Map.of(
+ IcebergConstants.CATALOG_BACKEND,
+ "memory",
+ IcebergConstants.CATALOG_UUID,
+ catalogUuid));
+ InMemoryCatalog original =
+ (InMemoryCatalog)
+
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+ Namespace namespace = Namespace.of("removed");
+ original.createNamespace(namespace);
+
+ IcebergCatalogUtil.removeMemoryCatalog(catalogUuid);
+ InMemoryCatalog replacement =
+ (InMemoryCatalog)
+
IcebergCatalogUtil.loadCatalogBackend(IcebergCatalogBackend.MEMORY, config);
+
+ Assertions.assertNotSame(original, replacement);
+ Assertions.assertFalse(replacement.namespaceExists(namespace));
+ }
+
@Test
void testLoadCatalog() {
Catalog catalog;
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/IcebergCatalogWrapperManager.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/IcebergCatalogWrapperManager.java
index d5a2d27206..5d8fbf52f1 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/IcebergCatalogWrapperManager.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/IcebergCatalogWrapperManager.java
@@ -30,6 +30,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
+import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
@@ -87,6 +88,9 @@ public class IcebergCatalogWrapperManager implements
AutoCloseable {
ident -> {
if (ident.namespace().level(0).equals(metalakeName)) {
catalogWrapperCache.invalidate(ident.name());
+ if
(ident.name().equals(configProvider.getDefaultCatalogName())) {
+
catalogWrapperCache.invalidate(IcebergConstants.ICEBERG_REST_DEFAULT_CATALOG);
+ }
}
});
}
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 b4f724626f..fc9efbf8ab 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
@@ -112,11 +112,14 @@ public class DynamicIcebergConfigProvider implements
IcebergConfigProvider {
private static Map<String, String> resolveProps(Catalog catalog) {
if (catalog instanceof BaseCatalog) {
- return new HashMap<>(
- GravitinoEnv.getInstance()
- .secretManager()
- .toPlaintextProperties(
- ((BaseCatalog<?>)
catalog).propertiesWithCredentialProviders()));
+ BaseCatalog<?> baseCatalog = (BaseCatalog<?>) catalog;
+ Map<String, String> props =
+ new HashMap<>(
+ GravitinoEnv.getInstance()
+ .secretManager()
+
.toPlaintextProperties(baseCatalog.propertiesWithCredentialProviders()));
+ props.put(IcebergConstants.CATALOG_UUID,
baseCatalog.entity().id().toString());
+ return props;
}
Map<String, String> props =
new HashMap<>(catalog.properties() == null ? Map.of() :
catalog.properties());
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/TestIcebergCatalogWrapperManagerForREST.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/TestIcebergCatalogWrapperManagerForREST.java
index 2d4386b730..45eb62f69e 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/TestIcebergCatalogWrapperManagerForREST.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/TestIcebergCatalogWrapperManagerForREST.java
@@ -21,9 +21,13 @@ package org.apache.gravitino.iceberg.service;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.iceberg.common.IcebergConfig;
@@ -42,11 +46,12 @@ import org.mockito.Mockito;
public class TestIcebergCatalogWrapperManagerForREST {
private static final String DEFAULT_CATALOG = "memory";
+ private static CatalogManager mockCatalogManager;
@BeforeAll
public static void setup() throws IllegalAccessException {
// Mock CatalogManager for GravitinoEnv to avoid initialization errors
- CatalogManager mockCatalogManager = Mockito.mock(CatalogManager.class);
+ mockCatalogManager = Mockito.mock(CatalogManager.class);
FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogManager",
mockCatalogManager, true);
}
@@ -145,6 +150,45 @@ public class TestIcebergCatalogWrapperManagerForREST {
Assertions.assertEquals(CatalogWrapperForREST.class, wrapper.getClass());
}
+ @Test
+ public void testDefaultCatalogAliasInvalidatedWhenCatalogRemoved() throws
Exception {
+ Mockito.clearInvocations(mockCatalogManager);
+ AtomicReference<Consumer<NameIdentifier>> removeListener = new
AtomicReference<>();
+ Mockito.doAnswer(
+ invocation -> {
+ removeListener.set(invocation.getArgument(0));
+ return null;
+ })
+ .when(mockCatalogManager)
+ .addCatalogCacheRemoveListener(Mockito.any());
+
+ IcebergConfigProvider configProvider =
Mockito.mock(IcebergConfigProvider.class);
+ Mockito.when(configProvider.getDefaultCatalogName()).thenReturn("test");
+ IcebergConfig icebergConfig =
+ new IcebergConfig(
+ ImmutableMap.of(
+ IcebergConstants.CATALOG_BACKEND,
+ "memory",
+ IcebergConstants.WAREHOUSE,
+ "/tmp/warehouse"));
+ Mockito.when(
+
configProvider.getIcebergCatalogConfig(IcebergConstants.ICEBERG_REST_DEFAULT_CATALOG))
+ .thenReturn(Optional.of(icebergConfig));
+
+ try (IcebergCatalogWrapperManager manager =
+ new IcebergCatalogWrapperManager(Maps.newHashMap(), configProvider,
true, "metalake")) {
+ IcebergRESTServerContext.create(configProvider, false, false, true,
manager);
+ CatalogWrapperForREST original =
+
manager.getCatalogWrapper(IcebergConstants.ICEBERG_REST_DEFAULT_CATALOG);
+
+ removeListener.get().accept(NameIdentifier.of("metalake", "test"));
+
+ CatalogWrapperForREST replacement =
+
manager.getCatalogWrapper(IcebergConstants.ICEBERG_REST_DEFAULT_CATALOG);
+ Assertions.assertNotSame(original, replacement);
+ }
+ }
+
private static IcebergCatalogWrapperManager newManager() {
Map<String, String> config = Maps.newHashMap();
IcebergConfigProvider configProvider =
IcebergConfigProviderFactory.create(config);
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 42807c1816..3389768d54 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
@@ -45,6 +45,7 @@ import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.gravitino.iceberg.common.ops.IcebergCatalogWrapper;
import
org.apache.gravitino.iceberg.service.authorization.IcebergRESTServerContext;
+import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.secret.SecretBinding;
import org.apache.gravitino.secret.SecretManager;
import org.apache.gravitino.secret.SecretMaterial;
@@ -760,8 +761,11 @@ public class TestDynamicIcebergConfigProvider {
@SuppressWarnings("unchecked")
BaseCatalog<?> baseCatalog = Mockito.mock(BaseCatalog.class);
+ CatalogEntity catalogEntity = Mockito.mock(CatalogEntity.class);
Mockito.when(baseCatalog.provider()).thenReturn("lakehouse-iceberg");
Mockito.when(baseCatalog.propertiesWithCredentialProviders()).thenReturn(entityProps);
+ Mockito.when(baseCatalog.entity()).thenReturn(catalogEntity);
+ Mockito.when(catalogEntity.id()).thenReturn(9L);
FieldUtils.writeField(GravitinoEnv.getInstance(), "secretManager", sm,
true);
@@ -781,6 +785,8 @@ public class TestDynamicIcebergConfigProvider {
Assertions.assertEquals(
"root",
config.get().getIcebergCatalogProperties().get(IcebergConstants.GRAVITINO_JDBC_USER));
+ Assertions.assertEquals(
+ "9",
config.get().getIcebergCatalogProperties().get(IcebergConstants.CATALOG_UUID));
}
}