Skip to content

Commit 43aea5e

Browse files
committed
feat: enhance GCS integration and update HMS image version
- Updated HMS container image to `ghcr.io/openprojectx/cloudera-hms:0.1.16`. - Enhanced GCS integration for Hive external tables in both Spark example and HMS configuration. - Added support for creating and verifying GCS-backed Hive external Parquet tables. - Improved documentation to clarify HMS requirements for GCS connector dependencies.
1 parent b0857fd commit 43aea5e

5 files changed

Lines changed: 28 additions & 9 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class MyFixedPortTest
159159

160160
Available JUnit port fields are `kerberosKdcPort`, `hdfsNameNodePort`, `hdfsWebPort`, `hiveMetastorePort`, `kafkaPort`, `schemaRegistryPort`, `kafkaUiPort`, `localStackS3Port`, and `fakeGcsPort`. Endpoint properties still use the actual mapped host ports returned by Testcontainers.
161161

162-
When Hive Metastore is started with LocalStack S3 or fake GCS, the kit also injects server-side Hadoop filesystem configuration into HMS. S3 external table DDL can validate `s3a://` locations against the internal LocalStack endpoint. GCS config is also injected, but HMS still needs a GCS connector jar in the HMS image before it can validate `gs://` locations.
162+
When Hive Metastore is started with LocalStack S3 or fake GCS, the kit also injects server-side Hadoop filesystem configuration into HMS. External table DDL can validate `s3a://` locations against the internal LocalStack endpoint and `gs://` locations against the internal fake GCS endpoint when the HMS image includes the matching filesystem connector.
163163

164164
## Spring Boot
165165

@@ -204,7 +204,7 @@ GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spring:bootRun --args='--sprin
204204

205205
The JUnit examples in `example/junit` are annotated with `@Disabled`; remove that annotation from an example class to start the configured stack.
206206

207-
The Spark example in `example/spark` creates a `SparkSession` from `BigDataTestKit` endpoints, starts HDFS, Hive Metastore, Kafka, Schema Registry, LocalStack S3, and fake GCS, then uses `extensions` config to create the S3 JCEKS file on HDFS and produce Avro records to Kafka. HDFS is used as the config store for the JCEKS file; S3 and GCS are object-store Iceberg smoke checks through Hadoop catalogs. The example also creates an HMS-backed Iceberg table and a Hive external Parquet table stored on S3, then verifies the HMS database/table location and format metadata through `HiveMetaStoreClient`.
207+
The Spark example in `example/spark` creates a `SparkSession` from `BigDataTestKit` endpoints, starts HDFS, Hive Metastore, Kafka, Schema Registry, LocalStack S3, and fake GCS, then uses `extensions` config to create the S3 JCEKS file on HDFS and produce Avro records to Kafka. HDFS is used as the config store for the JCEKS file; S3 and GCS are object-store Iceberg smoke checks through Hadoop catalogs. The example also creates an HMS-backed Iceberg table and Hive external Parquet tables on S3 and GCS, then verifies the HMS database/table location and format metadata through `HiveMetaStoreClient`.
208208

209209
```bash
210210
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:test

autoconfigure/src/main/kotlin/org/openprojectx/bigdata/test/autoconfigure/BigDataTestProperties.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ data class BigDataTestProperties(
3737

3838
data class HiveMetastore(
3939
var enabled: Boolean = false,
40-
var image: String = "ghcr.io/openprojectx/cloudera-hms:0.1.15",
40+
var image: String = "ghcr.io/openprojectx/cloudera-hms:0.1.16",
4141
var databaseName: String = "metastore_db",
4242
var databaseUser: String = "hive",
4343
var databasePassword: String = "hive-password",

core/src/main/kotlin/org/openprojectx/bigdata/test/core/BigDataTestKitOptions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ data class HdfsOptions(
4747

4848
data class HiveMetastoreOptions(
4949
val enabled: Boolean = false,
50-
val image: String = "ghcr.io/openprojectx/cloudera-hms:0.1.15",
50+
val image: String = "ghcr.io/openprojectx/cloudera-hms:0.1.16",
5151
val apacheHiveImage: String = "apache/hive:3.1.3",
5252
val databaseName: String = "metastore_db",
5353
val databaseUser: String = "hive",

doc/user-guide.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ When Hive Metastore starts with fake GCS enabled, `bigdata-test` injects HMS-sid
449449
* `fs.gs.implicit.dir.repair.enable=false`
450450
* `fs.gs.hierarchical.namespace.folders.enable=false`
451451

452-
This configuration does not fail HMS startup by itself. HMS can start with these keys even if the GCS connector jar is not present. Creating or validating a `gs://` Hive external table still requires the HMS image to include the GCS connector and its runtime dependencies.
452+
This configuration does not fail HMS startup by itself. HMS can start with these keys even if the GCS connector jar is not present. Creating or validating a `gs://` Hive external table requires the HMS image to include the GCS connector and its runtime dependencies. The default `ghcr.io/openprojectx/cloudera-hms` image includes those dependencies.
453453

454454
== Spring Boot
455455

@@ -506,7 +506,7 @@ The test demonstrates:
506506
* Reading Kafka data from Spark.
507507
* Writing Iceberg tables to S3 and GCS through Hadoop catalogs.
508508
* Creating an HMS-backed Iceberg table and asserting HMS metadata with `HiveMetaStoreClient`.
509-
* Creating a Hive external Parquet table stored on S3 and asserting HMS location and Parquet metadata.
509+
* Creating Hive external Parquet tables stored on S3 and GCS and asserting HMS location and Parquet metadata.
510510

511511
Run it with:
512512

@@ -532,7 +532,7 @@ The important distinction is where the path is resolved:
532532

533533
=== GCS Hive External Table Fails with `ClassNotFoundException`
534534

535-
If HMS returns `ClassNotFoundException: com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem`, the HMS image does not include the GCS connector jar. The config can be present and HMS can still start, but the jar is required when HMS validates a `gs://` path.
535+
If HMS returns `ClassNotFoundException: com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem`, the HMS image does not include the GCS connector jar. The config can be present and HMS can still start, but the jar is required when HMS validates a `gs://` path. Use the default `ghcr.io/openprojectx/cloudera-hms` image or another HMS image that includes the GCS connector and runtime dependencies.
536536

537537
=== Container Startup Failures
538538

example/spark/src/test/kotlin/org/openprojectx/bigdata/test/example/spark/SparkBigDataTestExample.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class SparkBigDataTestExample {
4242
val topic = "spark-avro-events"
4343
val s3Bucket = "spark-iceberg-s3-$runId"
4444
val gcsBucket = "spark-iceberg-gcs-$runId"
45+
val gcsIcebergDataPath = "gs://$gcsBucket/data/demo_$runId/events_gcs"
4546
val s3CredentialProviderPath = extensions.required("s3-jceks.credential-provider.path")
4647
val s3CredentialProviderHdfsPath = extensions.required("s3-jceks.hdfs.path")
4748
createS3Bucket(s3.property("aws.endpoint-url.s3"), s3Bucket)
@@ -65,7 +66,7 @@ class SparkBigDataTestExample {
6566
namespace = "demo_$runId",
6667
table = "events_gcs",
6768
storageName = "gcs",
68-
dataPath = "gs://$gcsBucket/data/demo_$runId/events_gcs",
69+
dataPath = gcsIcebergDataPath,
6970
)
7071
assertIcebergTable(spark, catalog = "hms", namespace = "hms_demo_$runId", table = "events_hms", storageName = "hms")
7172
assertHiveMetastoreTable(
@@ -81,6 +82,15 @@ class SparkBigDataTestExample {
8182
location = "s3a://$s3Bucket/hive-parquet/events_parquet_s3",
8283
storageName = "hive-s3-parquet",
8384
)
85+
assertHiveExternalParquetTable(
86+
spark = spark,
87+
hiveMetastoreUri = hiveMetastore.property("hive.metastore.uris"),
88+
database = "hive_gcs_demo_$runId",
89+
table = "events_parquet_gcs",
90+
location = gcsIcebergDataPath,
91+
storageName = "gcs",
92+
seedData = false,
93+
)
8494
}
8595
}
8696

@@ -203,9 +213,19 @@ class SparkBigDataTestExample {
203213
table: String,
204214
location: String,
205215
storageName: String,
216+
seedData: Boolean = true,
206217
) {
207218
val identifier = "$database.$table"
208219
spark.sql("CREATE DATABASE IF NOT EXISTS $database")
220+
if (seedData) {
221+
spark.sql(
222+
"""
223+
SELECT 1 AS id, 'alpha' AS name, '$storageName' AS storage
224+
UNION ALL
225+
SELECT 2 AS id, 'beta' AS name, '$storageName' AS storage
226+
""".trimIndent(),
227+
).write().mode("overwrite").parquet(location)
228+
}
209229
spark.sql(
210230
"""
211231
CREATE EXTERNAL TABLE $identifier (
@@ -216,7 +236,6 @@ class SparkBigDataTestExample {
216236
LOCATION '$location'
217237
""".trimIndent(),
218238
)
219-
spark.sql("INSERT INTO $identifier VALUES (1, 'alpha', '$storageName'), (2, 'beta', '$storageName')")
220239
val count = spark.table(identifier).where("storage = '$storageName'").count()
221240
check(count == 2L) { "Expected two Hive external Parquet rows in $identifier" }
222241
assertParquetFiles(spark, location)

0 commit comments

Comments
 (0)