Skip to content

Commit 450255f

Browse files
committed
feat: extend Spark dependency matrix with Cloudera support and task aliases
- Added Cloudera Spark/Hadoop dependency line to test matrix with resolvable runtime classpaths. - Introduced compatibility task aliases for legacy HMS test tasks. - Updated documentation to reflect matrix structure, task usage, and runtime configuration details. - Enhanced test setups with uncompressed Parquet default and improved HiveMetastoreClient compatibility.
1 parent 9a3bbfa commit 450255f

7 files changed

Lines changed: 253 additions & 44 deletions

File tree

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ Run the full Spark matrix when touching runtime config composition, HMS selectio
134134
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkBigDataMatrixTest
135135
```
136136

137+
The Spark matrix has two kinds of axes. Container/service axes belong in TOML and can be selected with `bigdata.test.config`. Dependency axes belong in Gradle tasks because they require different resolved test JVM classpaths. Add a new dependency line by creating a resolvable runtime classpath in `example/spark/build.gradle.kts`, then pair it with the existing TOML service variants.
138+
137139
The Gradle configuration-cache warning from `net.researchgate.release` is currently expected and unrelated to test behavior.
138140

139141
## Troubleshooting During Development

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,23 @@ Run the Spark smoke test:
7979
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:test
8080
```
8181

82-
Run the Spark HMS/Kerberos matrix:
82+
Run the Spark dependency/HMS/Kerberos matrix:
8383

8484
```bash
8585
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkBigDataMatrixTest
8686
```
8787

88-
Individual matrix cells are also available:
88+
Individual matrix cells are also available. The first axis selects the Spark/Hadoop dependency line, then HMS implementation, then Kerberos:
8989

9090
```bash
91-
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheHmsTest
92-
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheHmsKerberosTest
93-
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaHmsTest
94-
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaHmsKerberosTest
91+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheDepsApacheHmsTest
92+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheDepsApacheHmsKerberosTest
93+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheDepsClouderaHmsTest
94+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheDepsClouderaHmsKerberosTest
95+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaDepsApacheHmsTest
96+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaDepsApacheHmsKerberosTest
97+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaDepsClouderaHmsTest
98+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaDepsClouderaHmsKerberosTest
9599
```
96100

97101
## Documentation

doc/user-guide.adoc

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ When `bigdata.test.config.replace=true`, task config replaces startup config fro
245245

246246
Extension config has the same launcher hook: `bigdata.extensions.config` and `bigdata.extensions.config.replace`.
247247

248+
Container data-driven tests can be TOML-driven because service images, ports, Kerberos flags, and extension inputs are runtime configuration. Dependency data-driven tests must be Gradle-task/classpath-driven because Spark, Hadoop, Iceberg, and vendor distributions are resolved before the test JVM starts. The Spark example uses separate resolvable runtime classpaths for Apache and Cloudera dependency lines, then pairs those classpaths with TOML service variants.
249+
248250
`hiveMetastore` is the open-source HMS image and defaults to `ghcr.io/openprojectx/hive:3.1.3-hadoop-3.4.2-gcs-4.0.4-jdk17-0.1.4`. Hive 4 images can be tested by overriding this image key, but Spark 3.x brings a Hive 2.3 metastore client and should use the Hive 3 image unless that client stack is changed. The open-source HMS path starts an external PostgreSQL support container using `hiveMetastorePostgres`.
249251

250252
`clouderaHms` is the embedded-Postgres Cloudera HMS image. Use the `clouderaHms = true` annotation flag when you want this implementation. A test class must not enable both `hiveMetastore` and `clouderaHms`.
@@ -801,18 +803,74 @@ Run it with:
801803
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:test
802804
----
803805

804-
The Spark example also has Gradle tasks for the HMS/Kerberos matrix:
806+
The Spark example also has Gradle tasks for a dependency/HMS/Kerberos matrix:
807+
808+
[source,bash]
809+
----
810+
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkBigDataMatrixTest
811+
----
812+
813+
The aggregate task runs these cells:
814+
815+
|===
816+
|Task |Dependency line |HMS |Kerberos
817+
818+
|`sparkApacheDepsApacheHmsTest`
819+
|Apache Spark/Hadoop
820+
|Open-source Hive 3 HMS
821+
|No
822+
823+
|`sparkApacheDepsApacheHmsKerberosTest`
824+
|Apache Spark/Hadoop
825+
|Open-source Hive 3 HMS
826+
|Yes
827+
828+
|`sparkApacheDepsClouderaHmsTest`
829+
|Apache Spark/Hadoop
830+
|Cloudera HMS
831+
|No
832+
833+
|`sparkApacheDepsClouderaHmsKerberosTest`
834+
|Apache Spark/Hadoop
835+
|Cloudera HMS
836+
|Yes
837+
838+
|`sparkClouderaDepsApacheHmsTest`
839+
|Cloudera Spark/Hadoop
840+
|Open-source Hive 3 HMS
841+
|No
842+
843+
|`sparkClouderaDepsApacheHmsKerberosTest`
844+
|Cloudera Spark/Hadoop
845+
|Open-source Hive 3 HMS
846+
|Yes
847+
848+
|`sparkClouderaDepsClouderaHmsTest`
849+
|Cloudera Spark/Hadoop
850+
|Cloudera HMS
851+
|No
852+
853+
|`sparkClouderaDepsClouderaHmsKerberosTest`
854+
|Cloudera Spark/Hadoop
855+
|Cloudera HMS
856+
|Yes
857+
|===
858+
859+
The Kerberos axis currently enables the shared KDC and Kafka Kerberos path used by the Spark Kafka source. The HMS axis switches between the open-source Hive 3 HMS image and the Cloudera HMS image. The dependency axis switches the test JVM runtime classpath; it is intentionally implemented in Gradle rather than TOML.
860+
861+
Legacy HMS-only task aliases are still present:
805862

806863
[source,bash]
807864
----
808865
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheHmsTest
809866
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkApacheHmsKerberosTest
810867
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaHmsTest
811868
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkClouderaHmsKerberosTest
812-
GRADLE_USER_HOME=/data/.gradle ./gradlew :example:spark:sparkBigDataMatrixTest
813869
----
814870

815-
The Kerberos axis currently enables the shared KDC and Kafka Kerberos path used by the Spark Kafka source. The HMS axis switches between the open-source Hive 3 HMS image and the Cloudera HMS image.
871+
The `sparkClouderaHmsTest` and `sparkClouderaHmsKerberosTest` aliases select the Cloudera dependency line and Cloudera HMS. Use `sparkApacheDepsClouderaHmsTest` when you specifically want Apache Spark/Hadoop dependencies against Cloudera HMS.
872+
873+
Spark example test tasks always execute instead of reusing previous test outputs, because these are container smoke tests. The shared Spark TOML writes service container logs to `example/spark/build/bigdata-test-container-logs`; Gradle's test output contains the test JVM logs and points to the HTML test report on failure.
816874

817875
== Troubleshooting
818876

example/spark/build.gradle.kts

Lines changed: 153 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
1+
import org.gradle.api.artifacts.Configuration
2+
import org.gradle.api.attributes.Bundling
3+
import org.gradle.api.attributes.Category
4+
import org.gradle.api.attributes.LibraryElements
5+
import org.gradle.api.attributes.Usage
26

37
plugins {
48
id("buildsrc.convention.kotlin-jvm")
@@ -8,7 +12,7 @@ plugins {
812
description = "Spark JUnit 5 example for bigdata-test"
913

1014
sparkPlatform {
11-
line.set("spark3")
15+
line.set("cloudera")
1216
variants.set(listOf("iceberg"))
1317
addons.set(
1418
listOf(
@@ -28,7 +32,6 @@ dependencies {
2832
testImplementation("org.apache.spark:spark-sql-kafka-0-10_2.12")
2933
testImplementation("org.apache.spark:spark-avro_2.12")
3034
testImplementation("org.apache.hadoop:hadoop-aws")
31-
testImplementation("org.apache.iceberg:iceberg-spark-runtime-3.5_2.12")
3235
testImplementation("org.apache.iceberg:iceberg-aws-bundle")
3336
testImplementation("com.google.cloud.bigdataoss:gcs-connector")
3437
testImplementation("com.google.cloud.bigdataoss:gcsio")
@@ -38,8 +41,50 @@ dependencies {
3841
testRuntimeOnly(libs.junitPlatformLauncher)
3942
}
4043

44+
val apacheSparkVersion = libs.versions.spark.get()
45+
val apacheHadoopVersion = libs.versions.hadoop.get()
46+
val icebergVersion = libs.versions.iceberg.get()
47+
val testImplementationConfiguration = configurations.named("testImplementation")
48+
val testRuntimeOnlyConfiguration = configurations.named("testRuntimeOnly")
49+
50+
fun createSparkRuntimeClasspath(name: String, dependencyLine: String) = configurations.create(name) {
51+
isCanBeConsumed = false
52+
isCanBeResolved = true
53+
extendsFrom(testImplementationConfiguration.get(), testRuntimeOnlyConfiguration.get())
54+
55+
attributes {
56+
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class, Usage.JAVA_RUNTIME))
57+
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class, Category.LIBRARY))
58+
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class, LibraryElements.JAR))
59+
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling::class, Bundling.EXTERNAL))
60+
}
61+
62+
if (dependencyLine == "apache") {
63+
resolutionStrategy.eachDependency {
64+
when (requested.group) {
65+
"org.apache.spark" -> useVersion(apacheSparkVersion)
66+
"org.apache.hadoop" -> useVersion(apacheHadoopVersion)
67+
}
68+
}
69+
}
70+
}
71+
72+
val apacheSparkRuntimeClasspath = createSparkRuntimeClasspath("apacheSparkRuntimeClasspath", "apache")
73+
val clouderaSparkRuntimeClasspath = createSparkRuntimeClasspath("clouderaSparkRuntimeClasspath", "cloudera")
74+
75+
dependencies {
76+
add(apacheSparkRuntimeClasspath.name, "org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:$icebergVersion")
77+
add(apacheSparkRuntimeClasspath.name, "org.apache.logging.log4j:log4j-slf4j-impl:2.20.0")
78+
add(clouderaSparkRuntimeClasspath.name, "org.apache.iceberg:iceberg-spark-runtime-3.3_2.12")
79+
}
80+
81+
fun Test.useSparkRuntimeClasspath(runtimeClasspath: Configuration, dependencyLine: String) {
82+
classpath = sourceSets.test.get().output + sourceSets.main.get().output + runtimeClasspath
83+
systemProperty("bigdata.spark.dependency.line", dependencyLine)
84+
}
4185

4286
tasks.withType<Test>().configureEach {
87+
outputs.upToDateWhen { false }
4388
minHeapSize = "2048m"
4489
maxHeapSize = "8192m"
4590
jvmArgs(
@@ -49,64 +94,143 @@ tasks.withType<Test>().configureEach {
4994
)
5095
}
5196

97+
tasks.named<Test>("test") {
98+
useSparkRuntimeClasspath(clouderaSparkRuntimeClasspath, "cloudera")
99+
}
100+
52101
val sparkBigDataTestClass = "org.openprojectx.bigdata.test.example.spark.SparkBigDataTestExample"
53102
val sparkCommonConfig = "classpath:spark-bigdata-test-common.toml"
54103

55104
fun registerSparkMatrixTest(
56105
name: String,
57106
descriptionText: String,
107+
dependencyLine: String,
108+
runtimeClasspath: Configuration,
58109
variantConfig: String,
59110
) = tasks.register<Test>(name) {
60111
description = descriptionText
61112
group = "verification"
62113
testClassesDirs = sourceSets.test.get().output.classesDirs
63-
classpath = sourceSets.test.get().runtimeClasspath
114+
useSparkRuntimeClasspath(runtimeClasspath, dependencyLine)
64115
useJUnitPlatform()
65116
filter.includeTestsMatching(sparkBigDataTestClass)
66117
systemProperty("bigdata.test.config.replace", "true")
67118
systemProperty("bigdata.test.config", "$sparkCommonConfig,$variantConfig")
68119
}
69120

70-
val sparkApacheHmsTest = registerSparkMatrixTest(
71-
name = "sparkApacheHmsTest",
72-
descriptionText = "Runs the Spark example with open-source Hive 3 HMS and plaintext Kafka.",
121+
val sparkApacheDepsApacheHmsTest = registerSparkMatrixTest(
122+
name = "sparkApacheDepsApacheHmsTest",
123+
descriptionText = "Runs the Spark example with Apache Spark/Hadoop deps, open-source Hive 3 HMS, and plaintext Kafka.",
124+
dependencyLine = "apache",
125+
runtimeClasspath = apacheSparkRuntimeClasspath,
73126
variantConfig = "classpath:spark-bigdata-test-apache-hms.toml",
74127
)
75128

76-
val sparkApacheHmsKerberosTest = registerSparkMatrixTest(
77-
name = "sparkApacheHmsKerberosTest",
78-
descriptionText = "Runs the Spark example with open-source Hive 3 HMS and Kafka Kerberos.",
129+
val sparkApacheDepsApacheHmsKerberosTest = registerSparkMatrixTest(
130+
name = "sparkApacheDepsApacheHmsKerberosTest",
131+
descriptionText = "Runs the Spark example with Apache Spark/Hadoop deps, open-source Hive 3 HMS, and Kafka Kerberos.",
132+
dependencyLine = "apache",
133+
runtimeClasspath = apacheSparkRuntimeClasspath,
79134
variantConfig = "classpath:spark-bigdata-test-apache-hms-kerberos.toml",
80135
)
81-
sparkApacheHmsKerberosTest.configure {
82-
mustRunAfter(sparkApacheHmsTest)
83-
}
84136

85-
val sparkClouderaHmsTest = registerSparkMatrixTest(
86-
name = "sparkClouderaHmsTest",
87-
descriptionText = "Runs the Spark example with Cloudera HMS and plaintext Kafka.",
137+
val sparkApacheDepsClouderaHmsTest = registerSparkMatrixTest(
138+
name = "sparkApacheDepsClouderaHmsTest",
139+
descriptionText = "Runs the Spark example with Apache Spark/Hadoop deps, Cloudera HMS, and plaintext Kafka.",
140+
dependencyLine = "apache",
141+
runtimeClasspath = apacheSparkRuntimeClasspath,
88142
variantConfig = "classpath:spark-bigdata-test-cloudera-hms.toml",
89143
)
90-
sparkClouderaHmsTest.configure {
91-
mustRunAfter(sparkApacheHmsKerberosTest)
92-
}
93144

94-
val sparkClouderaHmsKerberosTest = registerSparkMatrixTest(
95-
name = "sparkClouderaHmsKerberosTest",
96-
descriptionText = "Runs the Spark example with Cloudera HMS and Kafka Kerberos.",
145+
val sparkApacheDepsClouderaHmsKerberosTest = registerSparkMatrixTest(
146+
name = "sparkApacheDepsClouderaHmsKerberosTest",
147+
descriptionText = "Runs the Spark example with Apache Spark/Hadoop deps, Cloudera HMS, and Kafka Kerberos.",
148+
dependencyLine = "apache",
149+
runtimeClasspath = apacheSparkRuntimeClasspath,
97150
variantConfig = "classpath:spark-bigdata-test-cloudera-hms-kerberos.toml",
98151
)
99-
sparkClouderaHmsKerberosTest.configure {
100-
mustRunAfter(sparkClouderaHmsTest)
152+
153+
val sparkClouderaDepsApacheHmsTest = registerSparkMatrixTest(
154+
name = "sparkClouderaDepsApacheHmsTest",
155+
descriptionText = "Runs the Spark example with Cloudera Spark/Hadoop deps, open-source Hive 3 HMS, and plaintext Kafka.",
156+
dependencyLine = "cloudera",
157+
runtimeClasspath = clouderaSparkRuntimeClasspath,
158+
variantConfig = "classpath:spark-bigdata-test-apache-hms.toml",
159+
)
160+
161+
val sparkClouderaDepsApacheHmsKerberosTest = registerSparkMatrixTest(
162+
name = "sparkClouderaDepsApacheHmsKerberosTest",
163+
descriptionText = "Runs the Spark example with Cloudera Spark/Hadoop deps, open-source Hive 3 HMS, and Kafka Kerberos.",
164+
dependencyLine = "cloudera",
165+
runtimeClasspath = clouderaSparkRuntimeClasspath,
166+
variantConfig = "classpath:spark-bigdata-test-apache-hms-kerberos.toml",
167+
)
168+
169+
val sparkClouderaDepsClouderaHmsTest = registerSparkMatrixTest(
170+
name = "sparkClouderaDepsClouderaHmsTest",
171+
descriptionText = "Runs the Spark example with Cloudera Spark/Hadoop deps, Cloudera HMS, and plaintext Kafka.",
172+
dependencyLine = "cloudera",
173+
runtimeClasspath = clouderaSparkRuntimeClasspath,
174+
variantConfig = "classpath:spark-bigdata-test-cloudera-hms.toml",
175+
)
176+
177+
val sparkClouderaDepsClouderaHmsKerberosTest = registerSparkMatrixTest(
178+
name = "sparkClouderaDepsClouderaHmsKerberosTest",
179+
descriptionText = "Runs the Spark example with Cloudera Spark/Hadoop deps, Cloudera HMS, and Kafka Kerberos.",
180+
dependencyLine = "cloudera",
181+
runtimeClasspath = clouderaSparkRuntimeClasspath,
182+
variantConfig = "classpath:spark-bigdata-test-cloudera-hms-kerberos.toml",
183+
)
184+
185+
listOf(
186+
sparkApacheDepsApacheHmsKerberosTest to sparkApacheDepsApacheHmsTest,
187+
sparkApacheDepsClouderaHmsTest to sparkApacheDepsApacheHmsKerberosTest,
188+
sparkApacheDepsClouderaHmsKerberosTest to sparkApacheDepsClouderaHmsTest,
189+
sparkClouderaDepsApacheHmsTest to sparkApacheDepsClouderaHmsKerberosTest,
190+
sparkClouderaDepsApacheHmsKerberosTest to sparkClouderaDepsApacheHmsTest,
191+
sparkClouderaDepsClouderaHmsTest to sparkClouderaDepsApacheHmsKerberosTest,
192+
sparkClouderaDepsClouderaHmsKerberosTest to sparkClouderaDepsClouderaHmsTest,
193+
).forEach { (task, predecessor) ->
194+
task.configure {
195+
mustRunAfter(predecessor)
196+
}
101197
}
102198

103199
tasks.register("sparkBigDataMatrixTest") {
104-
description = "Runs all Spark HMS/Kerberos matrix combinations."
200+
description = "Runs all Spark dependency/HMS/Kerberos matrix combinations."
105201
group = "verification"
106202
dependsOn(
107-
sparkApacheHmsTest,
108-
sparkApacheHmsKerberosTest,
109-
sparkClouderaHmsTest,
110-
sparkClouderaHmsKerberosTest,
203+
sparkApacheDepsApacheHmsTest,
204+
sparkApacheDepsApacheHmsKerberosTest,
205+
sparkApacheDepsClouderaHmsTest,
206+
sparkApacheDepsClouderaHmsKerberosTest,
207+
sparkClouderaDepsApacheHmsTest,
208+
sparkClouderaDepsApacheHmsKerberosTest,
209+
sparkClouderaDepsClouderaHmsTest,
210+
sparkClouderaDepsClouderaHmsKerberosTest,
111211
)
112212
}
213+
214+
tasks.register("sparkApacheHmsTest") {
215+
description = "Compatibility alias for sparkApacheDepsApacheHmsTest."
216+
group = "verification"
217+
dependsOn(sparkApacheDepsApacheHmsTest)
218+
}
219+
220+
tasks.register("sparkApacheHmsKerberosTest") {
221+
description = "Compatibility alias for sparkApacheDepsApacheHmsKerberosTest."
222+
group = "verification"
223+
dependsOn(sparkApacheDepsApacheHmsKerberosTest)
224+
}
225+
226+
tasks.register("sparkClouderaHmsTest") {
227+
description = "Compatibility alias for sparkClouderaDepsClouderaHmsTest."
228+
group = "verification"
229+
dependsOn(sparkClouderaDepsClouderaHmsTest)
230+
}
231+
232+
tasks.register("sparkClouderaHmsKerberosTest") {
233+
description = "Compatibility alias for sparkClouderaDepsClouderaHmsKerberosTest."
234+
group = "verification"
235+
dependsOn(sparkClouderaDepsClouderaHmsKerberosTest)
236+
}

0 commit comments

Comments
 (0)