Skip to content

Commit 14783b8

Browse files
committed
feat: add configurable Kerberos bootstrap timings and validation
- Introduced new Kerberos configuration options: `startupTimeoutSeconds`, `materialTimeoutSeconds`, `adminAttempts`, and `adminRetryDelaySeconds`. - Updated container setup to use user-defined or default Kerberos timings. - Enhanced validation to ensure positive timing-related values for Kerberos options. - Extended TOML and programmatic configurations to support new Kerberos options. - Updated documentation and examples to reflect added configurations.
1 parent 96e4fe1 commit 14783b8

7 files changed

Lines changed: 90 additions & 2 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ data class KerberosOptions(
2020
val clientPrincipal: String = "app_user@EXAMPLE.COM",
2121
val clientPassword: String = "app-user-secret",
2222
val users: List<KerberosUserOptions> = emptyList(),
23+
val startupTimeoutSeconds: Int = 120,
24+
val materialTimeoutSeconds: Int = 30,
25+
val adminAttempts: Int = 30,
26+
val adminRetryDelaySeconds: Int = 1,
2327
)
2428

2529
data class KerberosUserOptions(

core/src/main/kotlin/org/openprojectx/bigdata/test/core/container/BigDataContainerFactory.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ internal class BigDataContainerFactory(
6868

6969
private fun kerberos(): BigDataServiceContainer {
7070
val kerberos = options.kerberos
71+
validateKerberosTiming(kerberos)
7172
val servicePrincipals = buildList {
7273
addUserKeytab(kerberos.clientPrincipal, "/kerby/keytabs/client.keytab")
7374
kerberos.users.forEach { addUserKeytab(it.principal, it.keytabPath) }
@@ -91,7 +92,12 @@ internal class BigDataContainerFactory(
9192
.withEnv("KERBY_PA_ENC_TIMESTAMP_REQUIRED", "false")
9293
.withEnv("KERBY_CLIENT_PRINCIPAL", kerberos.clientPrincipal)
9394
.withEnv("KERBY_CLIENT_PASSWORD", kerberos.clientPassword)
94-
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(2)))
95+
.withEnv("KERBY_KADMIN_ATTEMPTS", kerberos.adminAttempts.toString())
96+
.withEnv("KERBY_KADMIN_RETRY_DELAY_SECONDS", kerberos.adminRetryDelaySeconds.toString())
97+
.waitingFor(
98+
Wait.forLogMessage(".*Kerby KDC container ready\\..*", 1)
99+
.withStartupTimeout(Duration.ofSeconds(kerberos.startupTimeoutSeconds.toLong())),
100+
)
95101
if (servicePrincipals.isNotEmpty()) {
96102
container.withEnv("KERBY_EXTRA_SERVICE_PRINCIPALS", servicePrincipals.joinToString(","))
97103
}
@@ -916,7 +922,7 @@ internal class BigDataContainerFactory(
916922

917923
private fun waitForKerberosFiles(container: GenericContainer<*>, containerPaths: Set<String>) {
918924
val sources = containerPaths.map { it.replace("/kerby/", "/var/lib/kerby/") }
919-
val deadline = System.nanoTime() + Duration.ofSeconds(30).toNanos()
925+
val deadline = System.nanoTime() + Duration.ofSeconds(options.kerberos.materialTimeoutSeconds.toLong()).toNanos()
920926
while (true) {
921927
if (!container.isRunning) {
922928
error(
@@ -945,6 +951,13 @@ internal class BigDataContainerFactory(
945951
}
946952
}
947953

954+
private fun validateKerberosTiming(kerberos: KerberosOptions) {
955+
require(kerberos.startupTimeoutSeconds > 0) { "Kerberos startupTimeoutSeconds must be positive" }
956+
require(kerberos.materialTimeoutSeconds > 0) { "Kerberos materialTimeoutSeconds must be positive" }
957+
require(kerberos.adminAttempts > 0) { "Kerberos adminAttempts must be positive" }
958+
require(kerberos.adminRetryDelaySeconds > 0) { "Kerberos adminRetryDelaySeconds must be positive" }
959+
}
960+
948961
private fun copyKerberosFileToContainer(container: GenericContainer<*>, containerPath: String) {
949962
val path = hostKerberosMaterialPath(containerPath)
950963
Files.createDirectories(path.parent)

doc/user-guide.adoc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,34 @@ class MyKerberosIntegrationTest
706706

707707
For host-side Kafka Kerberos clients, prefer an explicit fixed `kafkaPort`. Kafka must advertise the same host and port that the client uses during the SASL/GSSAPI exchange; with a random host port the framework cannot know that advertised port before the broker starts. When Schema Registry is enabled with Kafka Kerberos, it uses Kafka's internal plaintext listener while host clients keep using the Kerberos listener.
708708

709+
Kerberos bootstrap timing is configurable from TOML. The defaults are intentionally modest for local runs; CI can use a longer window when KDC admin startup is slower:
710+
711+
[source,toml]
712+
----
713+
[kerberos]
714+
startupTimeoutSeconds = 180
715+
materialTimeoutSeconds = 120
716+
adminAttempts = 120
717+
adminRetryDelaySeconds = 1
718+
----
719+
720+
`startupTimeoutSeconds` controls how long Testcontainers waits for the KDC image to print its ready log. `materialTimeoutSeconds` controls the post-start wait for generated keytabs and client config to be readable. `adminAttempts` and `adminRetryDelaySeconds` are passed to the Kerby image as `KERBY_KADMIN_ATTEMPTS` and `KERBY_KADMIN_RETRY_DELAY_SECONDS`.
721+
722+
Programmatic setup exposes the same options:
723+
724+
[source,kotlin]
725+
----
726+
BigDataTestKit.builder()
727+
.withKerberos(
728+
KerberosOptions(
729+
enabled = true,
730+
startupTimeoutSeconds = 180,
731+
materialTimeoutSeconds = 120,
732+
adminAttempts = 120,
733+
),
734+
)
735+
----
736+
709737
Default principals are defined in `BigDataTestKitOptions`:
710738

711739
[cols="1,2",options="header"]

example/spark/src/test/resources/spark-bigdata-test-apache-hms-kerberos.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ kerberos = true
33
hiveMetastore = true
44
kafkaKerberos = true
55

6+
[kerberos]
7+
startupTimeoutSeconds = 180
8+
materialTimeoutSeconds = 120
9+
adminAttempts = 120
10+
adminRetryDelaySeconds = 1
11+
612
[ports]
713
kafka = 19092
814

example/spark/src/test/resources/spark-bigdata-test-cloudera-hms-kerberos.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ kerberos = true
33
clouderaHms = true
44
kafkaKerberos = true
55

6+
[kerberos]
7+
startupTimeoutSeconds = 180
8+
materialTimeoutSeconds = 120
9+
adminAttempts = 120
10+
adminRetryDelaySeconds = 1
11+
612
[ports]
713
kafka = 19092
814

junit5/src/main/kotlin/org/openprojectx/bigdata/test/junit5/BigDataTestConfig.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.openprojectx.bigdata.test.core.ContainerLogMode
77
internal data class BigDataTestConfig(
88
val images: BigDataTestImageConfig = BigDataTestImageConfig(),
99
val services: BigDataTestServiceConfig = BigDataTestServiceConfig(),
10+
val kerberos: BigDataTestKerberosConfig = BigDataTestKerberosConfig(),
1011
val tls: BigDataTestTlsConfig = BigDataTestTlsConfig(),
1112
val hdfsWebTls: BigDataTestHttpTlsConfig = BigDataTestHttpTlsConfig(),
1213
val hiveMetastoreTls: BigDataTestHttpTlsConfig = BigDataTestHttpTlsConfig(),
@@ -22,6 +23,7 @@ internal data class BigDataTestConfig(
2223
BigDataTestConfig(
2324
images = images.merge(override.images),
2425
services = services.merge(override.services),
26+
kerberos = kerberos.merge(override.kerberos),
2527
tls = tls.merge(override.tls),
2628
hdfsWebTls = hdfsWebTls.merge(override.hdfsWebTls),
2729
hiveMetastoreTls = hiveMetastoreTls.merge(override.hiveMetastoreTls),
@@ -35,6 +37,21 @@ internal data class BigDataTestConfig(
3537
)
3638
}
3739

40+
internal data class BigDataTestKerberosConfig(
41+
val startupTimeoutSeconds: Int? = null,
42+
val materialTimeoutSeconds: Int? = null,
43+
val adminAttempts: Int? = null,
44+
val adminRetryDelaySeconds: Int? = null,
45+
) {
46+
fun merge(override: BigDataTestKerberosConfig): BigDataTestKerberosConfig =
47+
BigDataTestKerberosConfig(
48+
startupTimeoutSeconds = override.startupTimeoutSeconds ?: startupTimeoutSeconds,
49+
materialTimeoutSeconds = override.materialTimeoutSeconds ?: materialTimeoutSeconds,
50+
adminAttempts = override.adminAttempts ?: adminAttempts,
51+
adminRetryDelaySeconds = override.adminRetryDelaySeconds ?: adminRetryDelaySeconds,
52+
)
53+
}
54+
3855
internal data class BigDataTestTlsConfig(
3956
val enabled: Boolean? = null,
4057
val caCertPath: String? = null,
@@ -188,6 +205,7 @@ internal class BigDataTestConfigLoader(
188205
val tables = parseTables(readText(location))
189206
val images = tables["images"].orEmpty()
190207
val services = tables["services"].orEmpty()
208+
val kerberos = tables["kerberos"].orEmpty()
191209
val tls = tables["tls"].orEmpty()
192210
val hdfsWebTls = tables["hdfsWebTls"].orEmpty()
193211
val hiveMetastoreTls = tables["hiveMetastoreTls"].orEmpty()
@@ -226,6 +244,12 @@ internal class BigDataTestConfigLoader(
226244
localStackS3 = services.boolean("localStackS3"),
227245
fakeGcs = services.boolean("fakeGcs"),
228246
),
247+
kerberos = BigDataTestKerberosConfig(
248+
startupTimeoutSeconds = kerberos.int("startupTimeoutSeconds"),
249+
materialTimeoutSeconds = kerberos.int("materialTimeoutSeconds"),
250+
adminAttempts = kerberos.int("adminAttempts"),
251+
adminRetryDelaySeconds = kerberos.int("adminRetryDelaySeconds"),
252+
),
229253
tls = BigDataTestTlsConfig(
230254
enabled = tls.boolean("enabled"),
231255
caCertPath = tls.string("caCertPath"),
@@ -281,6 +305,7 @@ internal class BigDataTestConfigLoader(
281305
val knownTables = setOf(
282306
"images",
283307
"services",
308+
"kerberos",
284309
"tls",
285310
"hdfsWebTls",
286311
"hiveMetastoreTls",

junit5/src/main/kotlin/org/openprojectx/bigdata/test/junit5/BigDataTestExtension.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class BigDataTestExtension : BeforeAllCallback, AfterAllCallback, ParameterResol
5050
val config = BigDataTestConfigLoader(context.requiredTestClass.classLoader).load(configLocations)
5151
val images = config.images
5252
val services = config.services
53+
val kerberosConfig = config.kerberos
5354
val ports = config.ports
5455
val tls = config.tls
5556
val containerLogs = config.containerLogs
@@ -129,12 +130,17 @@ class BigDataTestExtension : BeforeAllCallback, AfterAllCallback, ParameterResol
129130
}
130131

131132
if (kerberos || hdfsKerberos || hiveMetastoreKerberos || kafkaKerberos || kafkaUiKerberos) {
133+
val defaultKerberos = KerberosOptions()
132134
builder.withKerberos(
133135
KerberosOptions(
134136
enabled = true,
135137
image = images.kerberos ?: "ghcr.io/openprojectx/directory-kerby/kerby-kdc:latest",
136138
clientPrincipal = annotation.kerberosClientPrincipal,
137139
clientPassword = annotation.kerberosClientPassword,
140+
startupTimeoutSeconds = kerberosConfig.startupTimeoutSeconds ?: defaultKerberos.startupTimeoutSeconds,
141+
materialTimeoutSeconds = kerberosConfig.materialTimeoutSeconds ?: defaultKerberos.materialTimeoutSeconds,
142+
adminAttempts = kerberosConfig.adminAttempts ?: defaultKerberos.adminAttempts,
143+
adminRetryDelaySeconds = kerberosConfig.adminRetryDelaySeconds ?: defaultKerberos.adminRetryDelaySeconds,
138144
),
139145
)
140146
}

0 commit comments

Comments
 (0)