This is an automated email from the ASF dual-hosted git repository.
danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new ba26a891e8fc fix: include Google Cloud Storage runtime dependencies in
GCP bundle (#20020)
ba26a891e8fc is described below
commit ba26a891e8fc87704767606f11d2cc4eaf3a4950
Author: Xinyao Zhang <[email protected]>
AuthorDate: Tue Sep 29 22:38:41 2026 -0400
fix: include Google Cloud Storage runtime dependencies in GCP bundle
(#20020)
---
packaging/hudi-gcp-bundle/pom.xml | 162 ++++++++++++++++++++-
.../hudi/gcp/bundle/ITGcpBundleCompatibility.java | 155 ++++++++++++++++++++
2 files changed, 316 insertions(+), 1 deletion(-)
diff --git a/packaging/hudi-gcp-bundle/pom.xml
b/packaging/hudi-gcp-bundle/pom.xml
index 37113bdee69e..fb0f16827c3f 100644
--- a/packaging/hudi-gcp-bundle/pom.xml
+++ b/packaging/hudi-gcp-bundle/pom.xml
@@ -32,7 +32,6 @@
<properties>
<checkstyle.skip>true</checkstyle.skip>
<main.basedir>${project.parent.basedir}</main.basedir>
- <skipTests>true</skipTests>
</properties>
<dependencyManagement>
<dependencies>
@@ -99,7 +98,76 @@
<include>org.apache.hudi:hudi-hive-sync</include>
<include>org.apache.hudi:hudi-gcp</include>
<include>org.apache.parquet:parquet-avro</include>
+ <!-- GCS lock client and BigQuery sync SDK dependencies -->
<include>com.google.cloud:google-cloud-bigquery</include>
+
<include>com.google.cloud:google-cloud-bigquerystorage</include>
+ <include>com.google.cloud:google-cloud-core</include>
+ <include>com.google.cloud:google-cloud-core-http</include>
+ <include>com.google.cloud:google-cloud-core-grpc</include>
+ <include>com.google.cloud:google-cloud-storage</include>
+ <include>com.google.cloud:google-cloud-monitoring</include>
+ <include>com.google.api:api-common</include>
+ <include>com.google.api:gax</include>
+ <include>com.google.api:gax-grpc</include>
+ <include>com.google.api:gax-httpjson</include>
+
<include>com.google.apis:google-api-services-bigquery</include>
+
<include>com.google.apis:google-api-services-storage</include>
+ <include>com.google.api-client:google-api-client</include>
+
<include>com.google.api.grpc:proto-google-common-protos</include>
+ <include>com.google.api.grpc:proto-google-iam-v1</include>
+
<include>com.google.api.grpc:proto-google-cloud-storage-v2</include>
+
<include>com.google.api.grpc:grpc-google-cloud-storage-v2</include>
+
<include>com.google.api.grpc:gapic-google-cloud-storage-v2</include>
+
<include>com.google.api.grpc:proto-google-cloud-bigquerystorage-v1</include>
+
<include>com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1</include>
+
<include>com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2</include>
+
<include>com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1</include>
+
<include>com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1</include>
+
<include>com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2</include>
+
<include>com.google.api.grpc:proto-google-cloud-monitoring-v3</include>
+
<include>com.google.auth:google-auth-library-credentials</include>
+
<include>com.google.auth:google-auth-library-oauth2-http</include>
+ <include>com.google.code.gson:gson</include>
+ <include>com.google.http-client:google-http-client</include>
+
<include>com.google.http-client:google-http-client-apache-v2</include>
+
<include>com.google.http-client:google-http-client-appengine</include>
+
<include>com.google.http-client:google-http-client-gson</include>
+
<include>com.google.http-client:google-http-client-jackson2</include>
+
<include>com.google.oauth-client:google-oauth-client</include>
+ <include>com.google.protobuf:protobuf-java</include>
+ <include>com.google.protobuf:protobuf-java-util</include>
+ <include>io.grpc:grpc-api</include>
+ <include>io.grpc:grpc-context</include>
+ <include>io.grpc:grpc-core</include>
+ <include>io.grpc:grpc-stub</include>
+ <include>io.grpc:grpc-protobuf</include>
+ <include>io.grpc:grpc-protobuf-lite</include>
+ <include>io.grpc:grpc-auth</include>
+ <include>io.grpc:grpc-alts</include>
+ <include>io.grpc:grpc-grpclb</include>
+ <include>io.grpc:grpc-netty-shaded</include>
+ <include>io.grpc:grpc-googleapis</include>
+ <include>io.grpc:grpc-xds</include>
+ <include>io.grpc:grpc-services</include>
+ <include>io.grpc:grpc-rls</include>
+ <!-- Keep the OpenCensus implementation with its exporter
and Disruptor dependencies. -->
+ <include>io.opencensus:opencensus-api</include>
+ <include>io.opencensus:opencensus-contrib-http-util</include>
+ <include>io.opencensus:opencensus-impl</include>
+ <include>io.opencensus:opencensus-impl-core</include>
+
<include>io.opencensus:opencensus-exporter-stats-stackdriver</include>
+
<include>io.opencensus:opencensus-exporter-metrics-util</include>
+
<include>io.opencensus:opencensus-contrib-exemplar-util</include>
+
<include>io.opencensus:opencensus-contrib-resource-util</include>
+
<include>io.opencensus:opencensus-contrib-grpc-metrics</include>
+ <include>io.opencensus:opencensus-proto</include>
+ <include>io.perfmark:perfmark-api</include>
+ <include>com.lmax:disruptor</include>
+ <include>org.threeten:threetenbp</include>
+ <include>org.threeten:threeten-extra</include>
+ <include>com.google.guava:guava</include>
+ <include>com.google.guava:failureaccess</include>
+ <include>com.google.guava:listenablefuture</include>
<include>com.beust:jcommander</include>
<include>commons-io:commons-io</include>
<include>org.openjdk.jol:jol-core</include>
@@ -114,6 +182,24 @@
<pattern>org.openjdk.jol.</pattern>
<shadedPattern>org.apache.hudi.org.openjdk.jol.</shadedPattern>
</relocation>
+ <relocation>
+ <pattern>com.google.common.</pattern>
+
<shadedPattern>org.apache.hudi.com.google.common.</shadedPattern>
+ </relocation>
+ <relocation>
+ <pattern>com.google.thirdparty.</pattern>
+
<shadedPattern>org.apache.hudi.com.google.thirdparty.</shadedPattern>
+ </relocation>
+ <!-- hudi-io already uses org.apache.hudi.com.google.protobuf,
so keep the GCP
+ protobuf and gRPC copies in a separate namespace. -->
+ <relocation>
+ <pattern>com.google.protobuf.</pattern>
+
<shadedPattern>org.apache.hudi.gcp.shaded.com.google.protobuf.</shadedPattern>
+ </relocation>
+ <relocation>
+ <pattern>io.grpc.</pattern>
+
<shadedPattern>org.apache.hudi.gcp.shaded.io.grpc.</shadedPattern>
+ </relocation>
</relocations>
<createDependencyReducedPom>true</createDependencyReducedPom>
<!-- Keep dependencies that are not absorbed into the shaded
jar, so the reduced POM still
@@ -136,6 +222,67 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-compatibility-fixtures</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>2.5.0</version>
+
<outputDirectory>${project.build.directory}/compatibility-libs</outputDirectory>
+ </artifactItem>
+ <artifactItem>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>27.0-jre</version>
+
<outputDirectory>${project.build.directory}/compatibility-libs</outputDirectory>
+ </artifactItem>
+ <!-- Jackson Core is supplied by the host in this deployment
scenario. -->
+ <artifactItem>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>${fasterxml.version}</version>
+
<outputDirectory>${project.build.directory}/compatibility-libs</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <configuration>
+ <skipTests>${skipITs}</skipTests>
+ <includes>
+ <include>**/ITGcpBundleCompatibility.java</include>
+ </includes>
+ <systemPropertyVariables>
+
<gcp.bundle.jar>${project.build.directory}/${project.build.finalName}.jar</gcp.bundle.jar>
+
<gcp.test.classes>${project.build.testOutputDirectory}</gcp.test.classes>
+
<host.protobuf.jar>${project.build.directory}/compatibility-libs/protobuf-java-2.5.0.jar</host.protobuf.jar>
+
<host.guava.jar>${project.build.directory}/compatibility-libs/guava-27.0-jre.jar</host.guava.jar>
+
<host.jackson.core.jar>${project.build.directory}/compatibility-libs/jackson-core-${fasterxml.version}.jar</host.jackson.core.jar>
+ </systemPropertyVariables>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
<resources>
<resource>
@@ -175,6 +322,19 @@
<artifactId>google-cloud-bigquery</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.platform</groupId>
+ <artifactId>junit-platform-launcher</artifactId>
+ <version>${junit.platform.version}</version>
+ <scope>test</scope>
+ </dependency>
+
<!-- Parquet -->
<dependency>
<groupId>org.apache.parquet</groupId>
diff --git
a/packaging/hudi-gcp-bundle/src/test/java/org/apache/hudi/gcp/bundle/ITGcpBundleCompatibility.java
b/packaging/hudi-gcp-bundle/src/test/java/org/apache/hudi/gcp/bundle/ITGcpBundleCompatibility.java
new file mode 100644
index 000000000000..45a2ff18b53b
--- /dev/null
+++
b/packaging/hudi-gcp-bundle/src/test/java/org/apache/hudi/gcp/bundle/ITGcpBundleCompatibility.java
@@ -0,0 +1,155 @@
+/*
+ * 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.hudi.gcp.bundle;
+
+import com.google.cloud.NoCredentials;
+import com.google.cloud.bigquery.BigQueryOptions;
+import com.google.cloud.storage.StorageOptions;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ITGcpBundleCompatibility {
+
+ private enum ClasspathOrder {
+ HOST_FIRST,
+ BUNDLE_FIRST
+ }
+
+ @Test
+ void testHostDependenciesBeforeBundle() throws Exception {
+ runSmokeTest(ClasspathOrder.HOST_FIRST);
+ }
+
+ @Test
+ void testBundleBeforeHostDependencies() throws Exception {
+ runSmokeTest(ClasspathOrder.BUNDLE_FIRST);
+ }
+
+ private static void runSmokeTest(ClasspathOrder order) throws Exception {
+ String bundleJar = System.getProperty("gcp.bundle.jar");
+ String testClasses = System.getProperty("gcp.test.classes");
+ String hostProtobuf = System.getProperty("host.protobuf.jar");
+ String hostGuava = System.getProperty("host.guava.jar");
+ String hostJacksonCore = System.getProperty("host.jackson.core.jar");
+
+ List<String> orderedClasspath = new ArrayList<>();
+ orderedClasspath.add(testClasses);
+ if (order == ClasspathOrder.HOST_FIRST) {
+ orderedClasspath.add(hostProtobuf);
+ orderedClasspath.add(hostGuava);
+ }
+ orderedClasspath.add(bundleJar);
+ if (order == ClasspathOrder.BUNDLE_FIRST) {
+ orderedClasspath.add(hostProtobuf);
+ orderedClasspath.add(hostGuava);
+ }
+ orderedClasspath.add(hostJacksonCore);
+
+ Process process = new ProcessBuilder(
+ new File(System.getProperty("java.home"),
"bin/java").getAbsolutePath(),
+ "-cp",
+ String.join(File.pathSeparator, orderedClasspath),
+ ITGcpBundleCompatibility.class.getName(),
+ "smoke")
+ .inheritIO()
+ .start();
+ assertEquals(0, process.waitFor(), "GCP bundle compatibility smoke test
failed");
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 1 || !"smoke".equals(args[0])) {
+ throw new IllegalArgumentException("Expected smoke mode");
+ }
+
+ verifyShadedProtobuf();
+ verifyShadedGuava();
+ verifyHostGuava();
+ verifyStorageClient();
+ verifyBigQueryClient();
+ verifyThreeTenExtra();
+ }
+
+ private static void verifyShadedProtobuf() throws Exception {
+ Class<?> timestampClass = Class.forName(
+ "org.apache.hudi.gcp.shaded.com.google.protobuf.Timestamp");
+ Object timestampBuilder =
timestampClass.getMethod("newBuilder").invoke(null);
+ Method setSeconds = timestampBuilder.getClass().getMethod("setSeconds",
long.class);
+ Object timestamp = timestampBuilder.getClass().getMethod("build")
+ .invoke(setSeconds.invoke(timestampBuilder, 1L));
+ byte[] timestampBytes = (byte[])
timestampClass.getMethod("toByteArray").invoke(timestamp);
+ if (timestampBytes.length == 0) {
+ throw new AssertionError("Protobuf serialization returned no bytes");
+ }
+ }
+
+ private static void verifyShadedGuava() throws Exception {
+ Class<?> domainNameClass = Class.forName(
+ "org.apache.hudi.com.google.common.net.InternetDomainName");
+ Object domainName = domainNameClass.getMethod("from",
String.class).invoke(null, "example.com");
+ Object publicSuffix =
domainNameClass.getMethod("publicSuffix").invoke(domainName);
+ if (!"com".equals(publicSuffix.toString())) {
+ throw new AssertionError("Unexpected public suffix: " + publicSuffix);
+ }
+ }
+
+ private static void verifyHostGuava() throws Exception {
+ Class<?> hostDomainNameClass =
Class.forName("com.google.common.net.InternetDomainName");
+ Object hostDomainName = hostDomainNameClass.getMethod("from", String.class)
+ .invoke(null, "example.com");
+ Object hostPublicSuffix = hostDomainNameClass.getMethod("publicSuffix")
+ .invoke(hostDomainName);
+ if (!"com".equals(hostPublicSuffix.toString())) {
+ throw new AssertionError("Unexpected host public suffix: " +
hostPublicSuffix);
+ }
+ }
+
+ private static void verifyStorageClient() {
+ StorageOptions options = StorageOptions.newBuilder()
+ .setProjectId("review")
+ .setCredentials(NoCredentials.getInstance())
+ .setRetrySettings(StorageOptions.getDefaultRetrySettings().toBuilder()
+ .setMaxAttempts(1)
+ .build())
+ .build();
+ options.getService();
+ }
+
+ private static void verifyBigQueryClient() {
+ BigQueryOptions.newBuilder()
+ .setProjectId("review")
+ .setCredentials(NoCredentials.getInstance())
+ .build()
+ .getService();
+ }
+
+ private static void verifyThreeTenExtra() throws Exception {
+ Class<?> periodDurationClass =
Class.forName("org.threeten.extra.PeriodDuration");
+ Object periodDuration = periodDurationClass.getMethod("parse",
CharSequence.class)
+ .invoke(null, "P1D");
+ if (!"P1D".equals(periodDuration.toString())) {
+ throw new AssertionError("Unexpected period duration: " +
periodDuration);
+ }
+ }
+}