This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 77b164ebbe [Cherry-pick to branch-1.3] [#14031] fix(flink-connector):
Validate credential providers for Iceberg non-REST backends (#14032) (#14132)
77b164ebbe is described below
commit 77b164ebbe88cdcb3ccd11a74f05bd75084e0ac9
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 23 22:55:27 2026 +0800
[Cherry-pick to branch-1.3] [#14031] fix(flink-connector): Validate
credential providers for Iceberg non-REST backends (#14032) (#14132)
**Cherry-pick Information:**
- Original commit: f559909447f33aca3abbfd15547dfad25e22ef36
- Target branch: `branch-1.3`
- Status: ✅ Conflicts resolved manually
---------
Co-authored-by: Yuhui <[email protected]>
---
docs/flink-connector/flink-catalog-iceberg.md | 9 ++
docs/security/credential-vending.md | 4 +-
.../connector/iceberg/GravitinoIcebergCatalog.java | 91 +++++++++++-
.../iceberg/IcebergPropertiesConstants.java | 9 ++
.../iceberg/TestGravitinoIcebergCatalog.java | 159 +++++++++++++++++++++
.../iceberg/TestGravitinoIcebergCatalogOpen.java | 101 +++++++++++++
6 files changed, 370 insertions(+), 3 deletions(-)
diff --git a/docs/flink-connector/flink-catalog-iceberg.md
b/docs/flink-connector/flink-catalog-iceberg.md
index b6c19a8de2..dea6de1936 100644
--- a/docs/flink-connector/flink-catalog-iceberg.md
+++ b/docs/flink-connector/flink-catalog-iceberg.md
@@ -119,6 +119,15 @@ Gravitino catalog property names with the prefix
`flink.bypass.` are passed to F
## Storage
+### Credential Vending
+
+Unless the catalog uses a `REST` backend with `data-access` set to
`vended-credentials`, the Flink connector fetches catalog-level credentials
from Gravitino once, when the catalog is opened, and never refreshes them. Only
static credential providers, such as `s3-secret-key`, `oss-secret-key` and
`azure-account-key`, are vended to Flink:
+
+- `s3-token`, `gcs-token`, `adls-token` and `oss-token` vend no credential at
catalog level. Flink logs a warning and Iceberg uses the credentials available
on the Flink nodes, such as an instance profile.
+- `aws-irsa` is rejected when the catalog is loaded, because its credential
expires and is never refreshed.
+
+For temporary credentials vended per table and refreshed automatically, use a
`REST` backend with `data-access` set to `vended-credentials`.
+
### OSS
Additionally, you need download the [Aliyun OSS
SDK](https://gosspublic.alicdn.com/sdks/java/aliyun_java_sdk_3.10.2.zip), and
copy `aliyun-sdk-oss-3.10.2.jar`, `hamcrest-core-1.1.jar`, `jdom2-2.0.6.jar` to
the Flink classpath.
diff --git a/docs/security/credential-vending.md
b/docs/security/credential-vending.md
index 7604f51548..dcae62aa75 100644
--- a/docs/security/credential-vending.md
+++ b/docs/security/credential-vending.md
@@ -27,7 +27,7 @@ Gravitino credential vending is used to generate temporary or
static credentials
- GCS: `GCSTokenCredential`
- ADLS: `ADLSTokenCredential`, `AzureAccountKeyCredential`
- OSS: `OSSTokenCredential`, `OSSSecretKeyCredential`
-- Spark, Flink, and Trino connectors automatically consume vended credentials
for Hive, Iceberg, Glue, JDBC, and Paimon catalogs.
+- Spark, Flink, and Trino connectors automatically consume vended credentials
for Hive, Iceberg, Glue, JDBC, and Paimon catalogs. For Iceberg catalogs
without a `REST` backend that vends credentials, the Flink connector receives
only static credentials, see [Flink Iceberg credential
vending](../flink-connector/flink-catalog-iceberg.md#credential-vending).
## General Configurations
@@ -225,7 +225,7 @@ Clients retrieve vended credentials from:
GET /api/metalakes/{metalake}/objects/catalog/{catalog}/credentials
```
-The server returns short-lived or static credentials that the client can use
to access the underlying storage directly.
+The server returns short-lived or static credentials that the client can use
to access the underlying storage directly. These credentials are catalog-level
and are not refreshed: token providers such as `s3-token` vend nothing at
catalog level, and `aws-irsa` credentials expire after
`s3-token-expire-in-secs`.
### Spark and Flink Connector Integration
diff --git
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/GravitinoIcebergCatalog.java
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/GravitinoIcebergCatalog.java
index 7bf33b5e1c..6edf8b255b 100644
---
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/GravitinoIcebergCatalog.java
+++
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/GravitinoIcebergCatalog.java
@@ -18,18 +18,37 @@
*/
package org.apache.gravitino.flink.connector.iceberg;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
import org.apache.flink.table.catalog.AbstractCatalog;
import org.apache.flink.table.catalog.exceptions.CatalogException;
import org.apache.flink.table.factories.Factory;
import org.apache.flink.util.Preconditions;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.credential.ADLSTokenCredential;
+import org.apache.gravitino.credential.AwsIrsaCredential;
+import org.apache.gravitino.credential.CredentialConstants;
import org.apache.gravitino.credential.CredentialPropertyUtils;
+import org.apache.gravitino.credential.GCSTokenCredential;
+import org.apache.gravitino.credential.OSSTokenCredential;
+import org.apache.gravitino.credential.S3SecretKeyCredential;
+import org.apache.gravitino.credential.S3TokenCredential;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.flink.connector.PartitionConverter;
import org.apache.gravitino.flink.connector.SchemaAndTablePropertiesConverter;
import org.apache.gravitino.flink.connector.catalog.BaseCatalog;
+import org.apache.gravitino.storage.GCSProperties;
import org.apache.iceberg.flink.FlinkCatalogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,6 +58,13 @@ public class GravitinoIcebergCatalog extends BaseCatalog {
private static final Logger LOG =
LoggerFactory.getLogger(GravitinoIcebergCatalog.class);
+ private static final Set<String> PATH_SCOPED_TOKEN_PROVIDERS =
+ ImmutableSet.of(
+ S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE,
+ GCSTokenCredential.GCS_TOKEN_CREDENTIAL_TYPE,
+ ADLSTokenCredential.ADLS_TOKEN_CREDENTIAL_TYPE,
+ OSSTokenCredential.OSS_TOKEN_CREDENTIAL_TYPE);
+
private final String icebergCatalogName;
// Mutable copy so credential injection in open() propagates to the inner
Iceberg catalog.
private final Map<String, String> mutableIcebergProperties;
@@ -83,8 +109,20 @@ public class GravitinoIcebergCatalog extends BaseCatalog {
public void open() throws CatalogException {
if (icebergCatalog == null) {
try {
+ Catalog gravitinoCatalog = catalog();
+ List<String> tokenProviders =
+ checkCredentialProviders(
+ catalogName(), mutableIcebergProperties,
gravitinoCatalog.properties());
+ if (!tokenProviders.isEmpty()) {
+ LOG.warn(
+ "Catalog '{}' uses credential providers {}, which vend no
credential at catalog"
+ + " level. The Flink connector injects no storage credential
for them, so"
+ + " Iceberg FileIO uses the credentials available on the
Flink nodes.",
+ catalogName(),
+ tokenProviders);
+ }
CredentialPropertyUtils.applyIcebergCredentials(
- CredentialPropertyUtils.getCredentials(catalog()),
mutableIcebergProperties);
+ CredentialPropertyUtils.getCredentials(gravitinoCatalog),
mutableIcebergProperties);
} catch (NoSuchCatalogException e) {
LOG.warn(
"Catalog '{}' not found in Gravitino during open(); credential
injection skipped."
@@ -119,4 +157,55 @@ public class GravitinoIcebergCatalog extends BaseCatalog {
catalog.getClass().getName());
return (AbstractCatalog) catalog;
}
+
+ // Without per-table vended credentials, the inner catalog only receives the
catalog-level
+ // credentials fetched once in open(). A REST backend with vended
credentials enabled is exempt
+ // because the inner REST catalog obtains per-table, refreshable credentials
itself. aws-irsa is
+ // rejected: its credential is injected but never refreshed, so it overrides
the node's own
+ // credentials and then expires. Path-scoped token providers vend nothing at
catalog level, which
+ // leaves the node's own credentials in effect, so they are only returned
for the caller to warn
+ // about. The REST check uses the effective inner catalog-type, since an
explicit catalog-type may
+ // differ from the persisted catalog-backend. When no provider list is set,
the server infers
+ // gcs-token from gcs-service-account-file, which is not reflected in the
catalog properties seen
+ // by the client, so the same inference is applied here.
+ @VisibleForTesting
+ static List<String> checkCredentialProviders(
+ String catalogName,
+ Map<String, String> icebergCatalogProperties,
+ @Nullable Map<String, String> gravitinoCatalogProperties) {
+ boolean restWithVendedCredentials =
+
IcebergPropertiesConstants.ICEBERG_CATALOG_BACKEND_REST.equalsIgnoreCase(
+
icebergCatalogProperties.get(IcebergPropertiesConstants.ICEBERG_CATALOG_TYPE))
+ &&
IcebergPropertiesConstants.ICEBERG_DATA_ACCESS_VENDED_CREDENTIALS.equalsIgnoreCase(
+
icebergCatalogProperties.get(IcebergPropertiesConstants.ICEBERG_ACCESS_DELEGATION));
+ if (restWithVendedCredentials || gravitinoCatalogProperties == null) {
+ return Collections.emptyList();
+ }
+ String providers =
gravitinoCatalogProperties.get(CredentialConstants.CREDENTIAL_PROVIDERS);
+ if (StringUtils.isBlank(providers)) {
+ return StringUtils.isNotBlank(
+
gravitinoCatalogProperties.get(GCSProperties.GRAVITINO_GCS_SERVICE_ACCOUNT_FILE))
+ ?
Collections.singletonList(GCSTokenCredential.GCS_TOKEN_CREDENTIAL_TYPE)
+ : Collections.emptyList();
+ }
+ List<String> providerList =
+
Splitter.on(',').trimResults().omitEmptyStrings().splitToList(providers);
+ if (providerList.stream()
+
.anyMatch(AwsIrsaCredential.AWS_IRSA_CREDENTIAL_TYPE::equalsIgnoreCase)) {
+ throw new CatalogException(
+ String.format(
+ "Catalog '%s' uses credential provider %s, which the Flink
connector does not"
+ + " support for Iceberg catalogs with a non-REST backend:
its credential is"
+ + " never refreshed and expires. Use a static provider such
as %s, or use a"
+ + " REST backend with %s=%s.",
+ catalogName,
+ AwsIrsaCredential.AWS_IRSA_CREDENTIAL_TYPE,
+ S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE,
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_DATA_ACCESS,
+
IcebergPropertiesConstants.ICEBERG_DATA_ACCESS_VENDED_CREDENTIALS));
+ }
+ return providerList.stream()
+ .filter(provider ->
PATH_SCOPED_TOKEN_PROVIDERS.contains(provider.toLowerCase(Locale.ROOT)))
+ .collect(Collectors.toList());
+ }
}
diff --git
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/IcebergPropertiesConstants.java
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/IcebergPropertiesConstants.java
index d879bb10e0..0d286c5d0c 100644
---
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/IcebergPropertiesConstants.java
+++
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/iceberg/IcebergPropertiesConstants.java
@@ -52,4 +52,13 @@ public class IcebergPropertiesConstants {
@VisibleForTesting
public static final String ICEBERG_CATALOG_BACKEND_REST =
CatalogUtil.ICEBERG_CATALOG_TYPE_REST;
+
+ /** Gravitino Iceberg catalog property that selects how clients access table
data. */
+ public static final String GRAVITINO_ICEBERG_DATA_ACCESS =
IcebergConstants.DATA_ACCESS;
+
+ /** Iceberg catalog property that the data access mode is passed to the REST
backend as. */
+ public static final String ICEBERG_ACCESS_DELEGATION =
IcebergConstants.ICEBERG_ACCESS_DELEGATION;
+
+ /** Data access mode in which the REST backend vends per-table storage
credentials. */
+ public static final String ICEBERG_DATA_ACCESS_VENDED_CREDENTIALS =
"vended-credentials";
}
diff --git
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalog.java
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalog.java
index d801841b53..8cb4972175 100644
---
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalog.java
+++
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalog.java
@@ -19,16 +19,31 @@
package org.apache.gravitino.flink.connector.iceberg;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Locale;
+import java.util.Map;
import org.apache.flink.table.catalog.AbstractCatalog;
import org.apache.flink.table.catalog.Catalog;
+import org.apache.flink.table.catalog.exceptions.CatalogException;
+import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.GCSTokenCredential;
+import org.apache.gravitino.storage.GCSProperties;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
class TestGravitinoIcebergCatalog {
+ private static final Map<String, String> HIVE_CATALOG =
+ ImmutableMap.of(IcebergPropertiesConstants.ICEBERG_CATALOG_TYPE, "hive");
+
@Test
void testRequireAbstractCatalogAcceptsAbstractCatalog() {
AbstractCatalog catalog = mock(AbstractCatalog.class);
@@ -43,4 +58,148 @@ class TestGravitinoIcebergCatalog {
assertThrows(
IllegalStateException.class, () ->
GravitinoIcebergCatalog.asAbstractCatalog(catalog));
}
+
+ @Test
+ void testCheckCredentialProvidersRejectsAwsIrsaForNonRestBackend() {
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "hive",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "s3-secret-key, AWS-IRSA");
+
+ CatalogException exception =
+ assertThrows(
+ CatalogException.class,
+ () -> GravitinoIcebergCatalog.checkCredentialProviders("c1",
HIVE_CATALOG, properties));
+ assertTrue(exception.getMessage().contains("aws-irsa"));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"s3-token", "gcs-token", "adls-token", "oss-token"})
+ void
testCheckCredentialProvidersReturnsTokenProvidersForNonRestBackend(String
provider) {
+ String upperCaseProvider = provider.toUpperCase(Locale.ROOT);
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "hive",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "s3-secret-key, " + upperCaseProvider);
+
+ assertEquals(
+ ImmutableList.of(upperCaseProvider),
+ GravitinoIcebergCatalog.checkCredentialProviders("c1", HIVE_CATALOG,
properties));
+ }
+
+ @Test
+ void testCheckCredentialProvidersIgnoresStaticProviders() {
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "jdbc",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "s3-secret-key,jdbc-user-password");
+
+ assertTrue(
+ GravitinoIcebergCatalog.checkCredentialProviders("c1",
ImmutableMap.of(), properties)
+ .isEmpty());
+ }
+
+ @Test
+ void testCheckCredentialProvidersIgnoresMissingProviders() {
+ Map<String, String> properties =
+
ImmutableMap.of(IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
"hive");
+
+ assertTrue(
+ GravitinoIcebergCatalog.checkCredentialProviders("c1", HIVE_CATALOG,
properties).isEmpty());
+ }
+
+ @Test
+ void testCheckCredentialProvidersInfersGcsTokenFromServiceAccountFile() {
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "hive",
+ GCSProperties.GRAVITINO_GCS_SERVICE_ACCOUNT_FILE,
+ "/path/to/sa.json");
+
+ assertEquals(
+ ImmutableList.of(GCSTokenCredential.GCS_TOKEN_CREDENTIAL_TYPE),
+ GravitinoIcebergCatalog.checkCredentialProviders("c1", HIVE_CATALOG,
properties));
+ }
+
+ @Test
+ void testCheckCredentialProvidersUsesExplicitProvidersOverGcsInference() {
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "hive",
+ GCSProperties.GRAVITINO_GCS_SERVICE_ACCOUNT_FILE,
+ "/path/to/sa.json",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "s3-secret-key");
+
+ assertTrue(
+ GravitinoIcebergCatalog.checkCredentialProviders("c1", HIVE_CATALOG,
properties).isEmpty());
+ }
+
+ @Test
+ void testCheckCredentialProvidersSkipsRestBackendWithVendedCredentials() {
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "REST",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "s3-token,aws-irsa");
+
+ assertTrue(
+ GravitinoIcebergCatalog.checkCredentialProviders(
+ "c1", restCatalog("REST", true), properties)
+ .isEmpty());
+ }
+
+ @Test
+ void testCheckCredentialProvidersUsesEffectiveCatalogType() {
+ Map<String, String> properties =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "hive",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "aws-irsa");
+
+ assertTrue(
+ GravitinoIcebergCatalog.checkCredentialProviders(
+ "c1", restCatalog("rest", true), properties)
+ .isEmpty());
+ }
+
+ @Test
+ void
testCheckCredentialProvidersRejectsAwsIrsaForRestBackendWithoutVendedCredentials()
{
+ Map<String, String> properties =
+ ImmutableMap.of(CredentialConstants.CREDENTIAL_PROVIDERS, "aws-irsa");
+
+ assertThrows(
+ CatalogException.class,
+ () ->
+ GravitinoIcebergCatalog.checkCredentialProviders(
+ "c1", restCatalog("rest", false), properties));
+ }
+
+ @Test
+ void testCheckCredentialProvidersIgnoresNullProperties() {
+ assertTrue(
+ GravitinoIcebergCatalog.checkCredentialProviders("c1", HIVE_CATALOG,
null).isEmpty());
+ }
+
+ private static Map<String, String> restCatalog(String catalogType, boolean
vendedCredentials) {
+ ImmutableMap.Builder<String, String> builder =
+ ImmutableMap.<String, String>builder()
+ .put(IcebergPropertiesConstants.ICEBERG_CATALOG_TYPE, catalogType);
+ if (vendedCredentials) {
+ builder.put(
+ IcebergPropertiesConstants.ICEBERG_ACCESS_DELEGATION,
+ IcebergPropertiesConstants.ICEBERG_DATA_ACCESS_VENDED_CREDENTIALS);
+ }
+ return builder.build();
+ }
}
diff --git
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalogOpen.java
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalogOpen.java
new file mode 100644
index 0000000000..453f1d6fc0
--- /dev/null
+++
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/iceberg/TestGravitinoIcebergCatalogOpen.java
@@ -0,0 +1,101 @@
+/*
+ * 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.flink.connector.iceberg;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.flink.table.catalog.exceptions.CatalogException;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.SupportsCredentials;
+import org.apache.gravitino.flink.connector.PartitionConverter;
+import org.apache.gravitino.flink.connector.SchemaAndTablePropertiesConverter;
+import org.apache.gravitino.flink.connector.utils.CatalogCompat;
+import org.junit.jupiter.api.Test;
+
+class TestGravitinoIcebergCatalogOpen {
+
+ private static final Map<String, String> HIVE_BACKEND_WITH_AWS_IRSA =
+ ImmutableMap.of(
+ IcebergPropertiesConstants.GRAVITINO_ICEBERG_CATALOG_BACKEND,
+ "hive",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "aws-irsa");
+
+ @Test
+ void testOpenRejectsAwsIrsaWhenEffectiveCatalogTypeIsNotRest() {
+ TestableIcebergCatalog catalog =
+ new TestableIcebergCatalog(
+ ImmutableMap.of(IcebergPropertiesConstants.ICEBERG_CATALOG_TYPE,
"hive"),
+ mockGravitinoCatalog(HIVE_BACKEND_WITH_AWS_IRSA));
+
+ assertThrows(CatalogException.class, catalog::open);
+ }
+
+ @Test
+ void testOpenRejectsAwsIrsaWhenRestBackendHasNoVendedCredentials() {
+ TestableIcebergCatalog catalog =
+ new TestableIcebergCatalog(
+ ImmutableMap.of(IcebergPropertiesConstants.ICEBERG_CATALOG_TYPE,
"rest"),
+ mockGravitinoCatalog(HIVE_BACKEND_WITH_AWS_IRSA));
+
+ assertThrows(CatalogException.class, catalog::open);
+ }
+
+ private static Catalog mockGravitinoCatalog(Map<String, String> properties) {
+ Catalog gravitinoCatalog = mock(Catalog.class);
+ SupportsCredentials supportsCredentials = mock(SupportsCredentials.class);
+ when(gravitinoCatalog.properties()).thenReturn(properties);
+
when(gravitinoCatalog.supportsCredentials()).thenReturn(supportsCredentials);
+ when(supportsCredentials.getCredentials()).thenReturn(new Credential[0]);
+ return gravitinoCatalog;
+ }
+
+ private static class TestableIcebergCatalog extends GravitinoIcebergCatalog {
+
+ private final Catalog gravitinoCatalog;
+
+ TestableIcebergCatalog(Map<String, String> icebergProperties, Catalog
gravitinoCatalog) {
+ super(
+ "c1",
+ "default",
+ mock(SchemaAndTablePropertiesConverter.class),
+ mock(PartitionConverter.class),
+ icebergProperties,
+ icebergProperties);
+ this.gravitinoCatalog = gravitinoCatalog;
+ }
+
+ @Override
+ protected Catalog catalog() {
+ return gravitinoCatalog;
+ }
+
+ @Override
+ protected CatalogCompat catalogCompat() {
+ return mock(CatalogCompat.class);
+ }
+ }
+}