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 06d0b439e4 [#12427] test(iceberg): fix cloud credential vending ITs in
deploy mode (#12428)
06d0b439e4 is described below
commit 06d0b439e4fbe3bb64cc3c54c68505b5799b3c03
Author: Bharath Krishna <[email protected]>
AuthorDate: Wed Aug 12 18:44:04 2026 -0700
[#12427] test(iceberg): fix cloud credential vending ITs in deploy mode
(#12428)
### What changes were proposed in this pull request?
Makes the cloud credential vending ITs work in deploy mode, and tidies
up the base class they share.
- `setupCloudBundles()` now runs before `super.startIntegrationTest()`,
because the server resolves its `FileIO` from the classpath while
starting.
- Bundles are installed into `catalogs/lakehouse-iceberg/libs/` as well
as `iceberg-rest-server/libs/`. The catalog is loaded through
`IsolatedClassLoader` and ships `iceberg-aws` / `iceberg-gcp` without
the cloud SDK, so `FileIO` resolved and then failed with
`NoClassDefFoundError`. Both bundles now come from the local build, so
nothing is downloaded from Maven Central.
- `downloadCloudBundleJar()` is removed: every subclass implemented it
as a no-op once the jars came from the local build.
- The schema create/drop lifecycle moves into the base class, which
already owns `SCHEMA_NAME` and `clearTable()`. The tests no longer leak
`schema` into sibling Iceberg ITs.
GCS needs two jars per directory, since `bundles/iceberg-gcp-bundle`
does not shade `bundles/gcp`; the AWS bundle already shades
`bundles/aws`, so one is enough there.
### Why are the changes needed?
The S3 and GCS ITs fail immediately under `-PtestMode=deploy`. They pass
in embedded mode, where `setupCloudBundles()` returns early and the
bundle code never runs, which is why this went unnoticed.
Fix: #12427
### Does this PR introduce _any_ user-facing change?
No. Test only.
### How was this patch tested?
GCS against a real bucket, and MinIO, both in deploy mode:
```
./gradlew :iceberg:iceberg-rest-server:test --tests
"*IcebergRESTGCSTokenAuthorizationIT" \
-PskipTests -PtestMode=deploy -PskipDockerTests=false
```
| Test | Before | After |
| --- | --- | --- |
| GCS deploy | `ServiceFailureException` at `initAdminIcebergCatalog` |
3 pass |
| GCS embedded | 3 pass | 3 pass |
| MinIO deploy | 3 pass | 3 pass |
MinIO is covered here because it shares the base class and runs in CI.
The S3 change is identical in shape but unverified, since running that
test needs an AWS account with an assumable role. S3 and GCS both stay
gated behind `GRAVITINO_TEST_CLOUD_IT`, so neither runs in CI.
---
.../IcebergRESTCloudTokenAuthorizationBaseIT.java | 39 +++++++++++++---------
.../test/IcebergRESTGCSTokenAuthorizationIT.java | 30 +++++++----------
.../test/IcebergRESTMinIOTokenAuthorizationIT.java | 28 ++--------------
.../test/IcebergRESTS3TokenAuthorizationIT.java | 29 ++++++----------
4 files changed, 47 insertions(+), 79 deletions(-)
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTCloudTokenAuthorizationBaseIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTCloudTokenAuthorizationBaseIT.java
index 83b14fc70d..583be10d55 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTCloudTokenAuthorizationBaseIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTCloudTokenAuthorizationBaseIT.java
@@ -20,6 +20,7 @@
package org.apache.gravitino.iceberg.integration.test;
import com.google.common.collect.ImmutableList;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -37,6 +38,7 @@ import org.apache.gravitino.authorization.SecurableObjects;
import org.apache.gravitino.integration.test.util.ITUtils;
import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.spark.SparkException;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -82,14 +84,6 @@ public abstract class
IcebergRESTCloudTokenAuthorizationBaseIT extends IcebergAu
@Override
public abstract Map<String, String> getCustomProperties();
- /**
- * Downloads cloud-specific bundle JARs (e.g., iceberg-aws-bundle,
iceberg-gcp-bundle). Subclasses
- * implement this to download the appropriate bundle for their cloud
provider.
- *
- * @throws Exception if download fails
- */
- protected abstract void downloadCloudBundleJar() throws Exception;
-
/**
* Copies cloud-specific bundle JARs to the Iceberg REST server libs
directory. Subclasses
* implement this to copy the appropriate bundle (e.g., "aws", "gcp",
"azure").
@@ -105,10 +99,9 @@ public abstract class
IcebergRESTCloudTokenAuthorizationBaseIT extends IcebergAu
protected abstract String getCloudProviderName();
/**
- * Sets up cloud-specific bundle JARs by downloading and copying them. This
method should be
- * called from subclass {@code startIntegrationTest()} methods before
calling {@code
- * super.startIntegrationTest()}, because the server resolves the cloud
{@code FileIO} from its
- * classpath while starting.
+ * Copies the cloud-specific bundle JARs into place. This method should be
called from subclass
+ * {@code startIntegrationTest()} methods before calling {@code
super.startIntegrationTest()},
+ * because the server resolves the cloud {@code FileIO} from its classpath
while starting.
*
* <p>Skips setup if running in embedded mode.
*/
@@ -116,12 +109,26 @@ public abstract class
IcebergRESTCloudTokenAuthorizationBaseIT extends IcebergAu
if (ITUtils.isEmbedded()) {
return;
}
+ copyCloudBundleJar();
+ }
+
+ /** Subclasses call this after {@code super.startIntegrationTest()}. */
+ protected void createSchemaIfAbsent() {
+ if (!catalogClientWithAllPrivilege.asSchemas().schemaExists(SCHEMA_NAME)) {
+ catalogClientWithAllPrivilege.asSchemas().createSchema(SCHEMA_NAME,
"test", new HashMap<>());
+ }
+ }
+
+ @AfterAll
+ public void stopIntegrationTest() throws IOException, InterruptedException {
+ // super drops the metalake, so it has to run even when setup failed part
way through.
try {
- downloadCloudBundleJar();
- } catch (Exception e) {
- throw new RuntimeException("Failed to download cloud bundle JAR", e);
+ // The Iceberg JDBC backend is shared with sibling ITs; Iceberg has no
cascading drop.
+ clearTable();
+ catalogClientWithAllPrivilege.asSchemas().dropSchema(SCHEMA_NAME, false);
+ } finally {
+ super.stopIntegrationTest();
}
- copyCloudBundleJar();
}
@Test
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTGCSTokenAuthorizationIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTGCSTokenAuthorizationIT.java
index b630072636..b341c826aa 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTGCSTokenAuthorizationIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTGCSTokenAuthorizationIT.java
@@ -19,14 +19,12 @@
package org.apache.gravitino.iceberg.integration.test;
-import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.credential.CredentialConstants;
import org.apache.gravitino.credential.GCSTokenCredential;
import org.apache.gravitino.integration.test.util.BaseIT;
-import org.apache.gravitino.integration.test.util.DownloaderUtils;
import org.apache.gravitino.integration.test.util.ITUtils;
import org.apache.gravitino.storage.GCSProperties;
import org.junit.jupiter.api.BeforeAll;
@@ -63,11 +61,12 @@ public class IcebergRESTGCSTokenAuthorizationIT extends
IcebergRESTCloudTokenAut
// Use null to trigger ADC (Application Default Credentials) if not
explicitly provided
this.serviceAccountFile =
System.getenv().get("GOOGLE_APPLICATION_CREDENTIALS");
- super.startIntegrationTest();
+ // The server resolves GCSFileIO from its classpath at startup, so bundles
must land first.
+ setupCloudBundles();
- catalogClientWithAllPrivilege.asSchemas().createSchema(SCHEMA_NAME,
"test", new HashMap<>());
+ super.startIntegrationTest();
- setupCloudBundles();
+ createSchemaIfAbsent();
}
@Override
@@ -83,22 +82,17 @@ public class IcebergRESTGCSTokenAuthorizationIT extends
IcebergRESTCloudTokenAut
}
@Override
- protected void downloadCloudBundleJar() throws IOException {
- String icebergBundleJarUri =
- String.format(
- "https://repo1.maven.org/maven2/org/apache/iceberg/"
- + "iceberg-gcp-bundle/%s/iceberg-gcp-bundle-%s.jar",
- ITUtils.icebergVersion(), ITUtils.icebergVersion());
+ protected void copyCloudBundleJar() {
String gravitinoHome = System.getenv("GRAVITINO_HOME");
- String targetDir = String.format("%s/iceberg-rest-server/libs/",
gravitinoHome);
- DownloaderUtils.downloadFile(icebergBundleJarUri, targetDir);
+ // The REST server and the catalog use separate classloaders, so each
needs its own copy.
+ copyBundlesTo(ITUtils.joinPath(gravitinoHome, "iceberg-rest-server",
"libs"));
+ copyBundlesTo(ITUtils.joinPath(gravitinoHome, "catalogs",
"lakehouse-iceberg", "libs"));
}
- @Override
- protected void copyCloudBundleJar() {
- String gravitinoHome = System.getenv("GRAVITINO_HOME");
- String targetDir = String.format("%s/iceberg-rest-server/libs/",
gravitinoHome);
- BaseIT.copyBundleJarsToDirectory("gcp", targetDir);
+ private void copyBundlesTo(String directory) {
+ // iceberg-gcp-bundle carries GCSFileIO and the GCS SDK, gcp carries the
credential providers.
+ BaseIT.copyBundleJarsToDirectory("iceberg-gcp-bundle", directory);
+ BaseIT.copyBundleJarsToDirectory("gcp", directory);
}
private Map<String, String> getGCSConfig() {
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTMinIOTokenAuthorizationIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTMinIOTokenAuthorizationIT.java
index fe9dd561c0..5b7fb88bcd 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTMinIOTokenAuthorizationIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTMinIOTokenAuthorizationIT.java
@@ -18,7 +18,6 @@
*/
package org.apache.gravitino.iceberg.integration.test;
-import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
@@ -29,7 +28,6 @@ import
org.apache.gravitino.integration.test.container.MinIOContainer;
import org.apache.gravitino.integration.test.util.BaseIT;
import org.apache.gravitino.integration.test.util.ITUtils;
import org.apache.gravitino.storage.S3Properties;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
@@ -69,22 +67,7 @@ public class IcebergRESTMinIOTokenAuthorizationIT extends
IcebergRESTCloudTokenA
super.startIntegrationTest();
- // A sibling IT may have left this schema behind in the shared Iceberg
JDBC backend.
- if (!catalogClientWithAllPrivilege.asSchemas().schemaExists(SCHEMA_NAME)) {
- catalogClientWithAllPrivilege.asSchemas().createSchema(SCHEMA_NAME,
"test", new HashMap<>());
- }
- }
-
- @AfterAll
- public void stopIntegrationTest() throws IOException, InterruptedException {
- // super drops the metalake, so it has to run even when setup failed part
way through.
- try {
- // The Iceberg JDBC backend is shared with sibling ITs; Iceberg has no
cascading drop.
- clearTable();
- catalogClientWithAllPrivilege.asSchemas().dropSchema(SCHEMA_NAME, false);
- } finally {
- super.stopIntegrationTest();
- }
+ createSchemaIfAbsent();
}
@Override
@@ -110,17 +93,10 @@ public class IcebergRESTMinIOTokenAuthorizationIT extends
IcebergRESTCloudTokenA
return "minio";
}
- @Override
- protected void downloadCloudBundleJar() {
- // gravitino-iceberg-aws-bundle already shades the Iceberg AWS bundle and
the Gravitino
- // credential providers, so it is built locally and nothing has to be
fetched.
- }
-
@Override
protected void copyCloudBundleJar() {
String gravitinoHome = System.getenv("GRAVITINO_HOME");
- // The REST server and the lakehouse-iceberg catalog resolve S3FileIO
through separate
- // classloaders, so each needs its own copy.
+ // The REST server and the catalog use separate classloaders, so each
needs its own copy.
BaseIT.copyBundleJarsToDirectory(
BUNDLE_NAME, ITUtils.joinPath(gravitinoHome, "iceberg-rest-server",
"libs"));
BaseIT.copyBundleJarsToDirectory(
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3TokenAuthorizationIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3TokenAuthorizationIT.java
index ac5c85729b..10fd8f15e4 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3TokenAuthorizationIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3TokenAuthorizationIT.java
@@ -19,14 +19,12 @@
package org.apache.gravitino.iceberg.integration.test;
-import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.credential.CredentialConstants;
import org.apache.gravitino.credential.S3TokenCredential;
import org.apache.gravitino.integration.test.util.BaseIT;
-import org.apache.gravitino.integration.test.util.DownloaderUtils;
import org.apache.gravitino.integration.test.util.ITUtils;
import org.apache.gravitino.storage.S3Properties;
import org.junit.jupiter.api.BeforeAll;
@@ -55,11 +53,12 @@ public class IcebergRESTS3TokenAuthorizationIT extends
IcebergRESTCloudTokenAuth
this.roleArn = System.getenv().getOrDefault("GRAVITINO_S3_ROLE_ARN",
"{ROLE_ARN}");
this.externalId = System.getenv().getOrDefault("GRAVITINO_S3_EXTERNAL_ID",
"");
- super.startIntegrationTest();
+ // The server resolves S3FileIO from its classpath at startup, so the
bundle must land first.
+ setupCloudBundles();
- catalogClientWithAllPrivilege.asSchemas().createSchema(SCHEMA_NAME,
"test", new HashMap<>());
+ super.startIntegrationTest();
- setupCloudBundles();
+ createSchemaIfAbsent();
}
@Override
@@ -74,23 +73,15 @@ public class IcebergRESTS3TokenAuthorizationIT extends
IcebergRESTCloudTokenAuth
return "s3";
}
- @Override
- protected void downloadCloudBundleJar() throws IOException {
- String icebergBundleJarUri =
- String.format(
- "https://repo1.maven.org/maven2/org/apache/iceberg/"
- + "iceberg-aws-bundle/%s/iceberg-aws-bundle-%s.jar",
- ITUtils.icebergVersion(), ITUtils.icebergVersion());
- String gravitinoHome = System.getenv("GRAVITINO_HOME");
- String targetDir = String.format("%s/iceberg-rest-server/libs/",
gravitinoHome);
- DownloaderUtils.downloadFile(icebergBundleJarUri, targetDir);
- }
-
@Override
protected void copyCloudBundleJar() {
String gravitinoHome = System.getenv("GRAVITINO_HOME");
- String targetDir = String.format("%s/iceberg-rest-server/libs/",
gravitinoHome);
- BaseIT.copyBundleJarsToDirectory("aws", targetDir);
+ // The REST server and the catalog use separate classloaders, so each
needs its own copy.
+ BaseIT.copyBundleJarsToDirectory(
+ "iceberg-aws-bundle", ITUtils.joinPath(gravitinoHome,
"iceberg-rest-server", "libs"));
+ BaseIT.copyBundleJarsToDirectory(
+ "iceberg-aws-bundle",
+ ITUtils.joinPath(gravitinoHome, "catalogs", "lakehouse-iceberg",
"libs"));
}
private Map<String, String> getS3Config() {