Skip to content

Commit 6db604d

Browse files
committed
feat(extensions): support multiple HDFS JCEKS paths in S3JceksExtension
- Added `additionalHdfsPaths` to configure multiple HDFS locations for JCEKS files. - Updated `BigDataExtensionsConfigLoader` and `BigDataExtensionsBuilder` to handle new paths via TOML and API. - Refactored `S3JceksExtension` to generate and manage multiple HDFS JCEKS providers. - Expanded outputs with plural paths and indexed paths for better client usage. - Updated user documentation with examples and usage details for `additionalHdfsPaths`. - Added unit tests to verify multi-path configurations and provider path logic.
1 parent 583271d commit 6db604d

5 files changed

Lines changed: 63 additions & 13 deletions

File tree

doc/user-guide.adoc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,10 @@ hdfsDir = "/bigdata-test/spark"
15081508
fileName = "s3.jceks"
15091509
accessKeyAlias = "fs.s3a.access.key"
15101510
secretKeyAlias = "fs.s3a.secret.key"
1511+
additionalHdfsPaths = [
1512+
"/bigdata-test/app/s3.jceks",
1513+
"/bigdata-test/worker/s3.jceks"
1514+
]
15111515
15121516
[s3Jceks.aliases]
15131517
"fs.s3a.encryption.algorithm" = ""
@@ -1518,12 +1522,18 @@ secretKeyAlias = "fs.s3a.secret.key"
15181522

15191523
`aliases` writes additional entries into the same JCEKS file. The extension writes empty defaults for Hadoop 3.4 S3A encryption aliases because `S3AUtils.lookupPassword()` reads those options through the credential provider even when encryption is disabled. The JCEKS provider cannot store a zero-length secret, so empty alias values are stored as a single space; S3A trims the value back to blank while reading. Override them only when your test needs S3A encryption options.
15201524

1525+
`additionalHdfsPaths` writes the same generated credentials to more HDFS JCEKS files. The primary file remains `hdfsDir + fileName` for backward compatibility. Use the plural output when the client should search all generated providers.
1526+
15211527
Outputs:
15221528

15231529
* `s3-jceks.hdfs.path`
1530+
* `s3-jceks.hdfs.paths`
15241531
* `s3-jceks.credential-provider.path`
1532+
* `s3-jceks.credential-provider.paths`
1533+
* `s3-jceks.credential-provider.path.0`
1534+
* `s3-jceks.credential-provider.path.1`
15251535

1526-
Use `s3-jceks.credential-provider.path` as `hadoop.security.credential.provider.path` or `spark.hadoop.hadoop.security.credential.provider.path`.
1536+
Use `s3-jceks.credential-provider.path` as `hadoop.security.credential.provider.path` or `spark.hadoop.hadoop.security.credential.provider.path` for the primary generated JCEKS. Use `s3-jceks.credential-provider.paths` when `additionalHdfsPaths` is configured; it is a comma-separated Hadoop credential provider path.
15271537

15281538
=== Kafka Avro Seed Extension
15291539

extensions/src/main/kotlin/org/openprojectx/bigdata/test/extensions/config/BigDataExtensionsBuilder.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class S3JceksBuilder {
173173
var accessKeyAlias: String = "fs.s3a.access.key"
174174
var secretKeyAlias: String = "fs.s3a.secret.key"
175175
private val aliases = linkedMapOf<String, String>()
176+
private val additionalHdfsPaths = mutableListOf<String>()
176177

177178
fun alias(name: String, value: String) {
178179
aliases[name] = value
@@ -182,6 +183,14 @@ class S3JceksBuilder {
182183
aliases += values
183184
}
184185

186+
fun additionalHdfsPath(path: String) {
187+
additionalHdfsPaths += path
188+
}
189+
190+
fun additionalHdfsPaths(paths: Iterable<String>) {
191+
additionalHdfsPaths += paths
192+
}
193+
185194
internal fun build(): S3JceksExtension =
186195
S3JceksExtension(
187196
id = id,
@@ -190,6 +199,7 @@ class S3JceksBuilder {
190199
accessKeyAlias = accessKeyAlias,
191200
secretKeyAlias = secretKeyAlias,
192201
aliases = S3JceksExtension.defaultAliases + aliases,
202+
additionalHdfsPaths = additionalHdfsPaths.toList(),
193203
)
194204
}
195205

extensions/src/main/kotlin/org/openprojectx/bigdata/test/extensions/config/BigDataExtensionsConfigLoader.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class BigDataExtensionsConfigLoader(
5656
accessKeyAlias = config.string("accessKeyAlias", "fs.s3a.access.key"),
5757
secretKeyAlias = config.string("secretKeyAlias", "fs.s3a.secret.key"),
5858
aliases = S3JceksExtension.defaultAliases + config.stringMap("aliases"),
59+
additionalHdfsPaths = config.stringList("additionalHdfsPaths"),
5960
)
6061
}
6162
}

extensions/src/main/kotlin/org/openprojectx/bigdata/test/extensions/hadoop/S3JceksExtension.kt

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ data class S3JceksExtension(
1212
val accessKeyAlias: String = "fs.s3a.access.key",
1313
val secretKeyAlias: String = "fs.s3a.secret.key",
1414
val aliases: Map<String, String> = defaultAliases,
15+
val additionalHdfsPaths: List<String> = emptyList(),
1516
) : BigDataExtension {
1617
override val requiredServices: Set<BigDataService> = setOf(BigDataService.HDFS, BigDataService.LOCALSTACK_S3)
1718
override val events: Set<BigDataExtensionEvent> = setOf(BigDataExtensionEvent.AFTER_KIT_START)
@@ -20,21 +21,36 @@ data class S3JceksExtension(
2021
val hdfs = context.endpoint(BigDataService.HDFS)
2122
val s3 = context.endpoint(BigDataService.LOCALSTACK_S3)
2223
val kerberosProperties = context.kit.endpoints()[BigDataService.KERBEROS]?.properties.orEmpty()
23-
val providerPath = HadoopCredentialProviders.hdfsJceksPath(hdfsDir, fileName)
24-
HadoopCredentialProviders.createHdfsJceks(
25-
hdfsProperties = hdfs.properties + kerberosProperties,
26-
configDir = hdfsDir,
27-
providerPath = providerPath,
28-
credentials = mapOf(
29-
accessKeyAlias to s3.property("aws.accessKeyId"),
30-
secretKeyAlias to s3.property("aws.secretAccessKey"),
31-
) + aliases,
32-
)
33-
context.putOutput("$id.credential-provider.path", providerPath)
34-
context.putOutput("$id.hdfs.path", "${hdfsDir.trimEnd('/')}/$fileName")
24+
val credentials = mapOf(
25+
accessKeyAlias to s3.property("aws.accessKeyId"),
26+
secretKeyAlias to s3.property("aws.secretAccessKey"),
27+
) + aliases
28+
val hdfsPaths = (listOf("${hdfsDir.trimEnd('/')}/$fileName") + additionalHdfsPaths).distinct()
29+
val providerPaths = hdfsPaths.map { hdfsJceksProviderPath(it) }
30+
hdfsPaths.zip(providerPaths).forEach { (hdfsPath, providerPath) ->
31+
HadoopCredentialProviders.createHdfsJceks(
32+
hdfsProperties = hdfs.properties + kerberosProperties,
33+
configDir = hdfsPath.substringBeforeLast('/', missingDelimiterValue = "/").ifBlank { "/" },
34+
providerPath = providerPath,
35+
credentials = credentials,
36+
)
37+
}
38+
context.putOutput("$id.credential-provider.path", providerPaths.first())
39+
context.putOutput("$id.credential-provider.paths", providerPaths.joinToString(","))
40+
context.putOutput("$id.hdfs.path", hdfsPaths.first())
41+
context.putOutput("$id.hdfs.paths", hdfsPaths.joinToString(","))
42+
providerPaths.forEachIndexed { index, providerPath ->
43+
context.putOutput("$id.credential-provider.path.$index", providerPath)
44+
}
45+
hdfsPaths.forEachIndexed { index, hdfsPath ->
46+
context.putOutput("$id.hdfs.path.$index", hdfsPath)
47+
}
3548
}
3649

3750
companion object {
51+
fun hdfsJceksProviderPath(hdfsPath: String): String =
52+
"jceks://hdfs/${hdfsPath.trimStart('/')}"
53+
3854
val defaultAliases: Map<String, String> = mapOf(
3955
// Hadoop 3.4 S3A loads these through S3AUtils.lookupPassword().
4056
// Empty aliases keep credential-provider-only configurations from

extensions/src/test/kotlin/org/openprojectx/bigdata/test/extensions/hadoop/S3JceksExtensionTest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class S3JceksExtensionTest {
1313
[s3Jceks]
1414
hdfsDir = "/config"
1515
fileName = "s3.jceks"
16+
additionalHdfsPaths = [
17+
"/config/app/s3.jceks",
18+
"/config/worker/s3.jceks"
19+
]
1620
1721
[s3Jceks.aliases]
1822
"fs.s3a.encryption.algorithm" = "SSE-S3"
@@ -23,6 +27,7 @@ class S3JceksExtensionTest {
2327

2428
assertEquals("/config", extension.hdfsDir)
2529
assertEquals("s3.jceks", extension.fileName)
30+
assertEquals(listOf("/config/app/s3.jceks", "/config/worker/s3.jceks"), extension.additionalHdfsPaths)
2631
assertEquals("SSE-S3", extension.aliases["fs.s3a.encryption.algorithm"])
2732
assertEquals("", extension.aliases["fs.s3a.server-side-encryption-algorithm"])
2833
assertEquals("", extension.aliases["fs.s3a.server-side-encryption.key"])
@@ -50,4 +55,12 @@ class S3JceksExtensionTest {
5055
assertTrue(defaults.containsKey("fs.s3a.encryption.key"))
5156
assertTrue(defaults.containsKey("fs.s3a.server-side-encryption.key"))
5257
}
58+
59+
@Test
60+
fun `builds hdfs jceks provider path from hdfs path`() {
61+
assertEquals(
62+
"jceks://hdfs/bigdata-test/config/s3.jceks",
63+
S3JceksExtension.hdfsJceksProviderPath("/bigdata-test/config/s3.jceks"),
64+
)
65+
}
5366
}

0 commit comments

Comments
 (0)