From d4dafee5e711c5c3c44563105680daf471e5c67b Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Thu, 20 Jun 2024 15:26:56 +0200 Subject: [PATCH] deps: update kubernetesclient to v20 --- gradle/libs.versions.toml | 2 +- .../src/main/kotlin/JobHandler.kt | 8 +- .../src/main/kotlin/JobWatchHelper.kt | 18 +- .../src/test/kotlin/JobHandlerTest.kt | 206 +++++++----------- .../src/test/kotlin/JobWatchHelperTest.kt | 83 ++++--- .../src/test/kotlin/MonitorComponentTest.kt | 57 ++--- .../main/kotlin/KubernetesMessageSender.kt | 2 +- .../kotlin/KubernetesMessageSenderTest.kt | 16 +- 8 files changed, 175 insertions(+), 217 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6fc040d6dd..bf5a996bd7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,7 +52,7 @@ kotestAssertionsKtor = "2.0.0" kotestExtensionTestContainers = "2.0.2" ktor = "2.3.12" ktorSwaggerUi = "2.10.0" -kubernetesClient = "18.0.1" +kubernetesClient = "20.0.1" log4j = "2.23.1" logback = "1.5.6" micrometer = "1.13.1" diff --git a/transport/kubernetes-jobmonitor/src/main/kotlin/JobHandler.kt b/transport/kubernetes-jobmonitor/src/main/kotlin/JobHandler.kt index 525e875279..c2ee0d271c 100644 --- a/transport/kubernetes-jobmonitor/src/main/kotlin/JobHandler.kt +++ b/transport/kubernetes-jobmonitor/src/main/kotlin/JobHandler.kt @@ -173,7 +173,7 @@ internal class JobHandler( */ private fun deleteJob(jobName: String) { runCatching { - jobApi.deleteNamespacedJob(jobName, namespace, null, null, null, null, null, null) + jobApi.deleteNamespacedJob(jobName, namespace).execute() }.onFailure { e -> logger.error("Could not remove job '$jobName': $e.") } @@ -187,7 +187,7 @@ internal class JobHandler( private fun findPodsForJob(jobName: String): List { val selector = "job-name=$jobName" - return api.listNamespacedPod(namespace, null, null, null, null, selector, null, null, null, null, false).items + return api.listNamespacedPod(namespace).labelSelector(selector).watch(false).execute().items } /** @@ -198,7 +198,7 @@ internal class JobHandler( pod.metadata?.name?.let { podName -> logger.info("Deleting pod $podName.") runCatching { - api.deleteNamespacedPod(podName, namespace, null, null, null, null, null, null) + api.deleteNamespacedPod(podName, namespace).execute() }.onFailure { e -> logger.error("Could not remove pod '$podName': $e.") } @@ -235,5 +235,5 @@ internal class JobHandler( * Return a list with the jobs in the configured namespace. Apply the given [labelSelector] filter. */ private fun listJobs(labelSelector: String? = null): List = - jobApi.listNamespacedJob(namespace, null, null, null, null, labelSelector, null, null, null, null, false).items + jobApi.listNamespacedJob(namespace).labelSelector(labelSelector).watch(false).execute().items } diff --git a/transport/kubernetes-jobmonitor/src/main/kotlin/JobWatchHelper.kt b/transport/kubernetes-jobmonitor/src/main/kotlin/JobWatchHelper.kt index 45ede3bea9..3f2b7673ea 100644 --- a/transport/kubernetes-jobmonitor/src/main/kotlin/JobWatchHelper.kt +++ b/transport/kubernetes-jobmonitor/src/main/kotlin/JobWatchHelper.kt @@ -67,7 +67,7 @@ internal class JobWatchHelper( * jobs using the given [jobApi]. */ private fun fetchResourceVersion(jobApi: BatchV1Api, namespace: String): String? = - jobApi.listNamespacedJob(namespace, null, false, null, null, null, 1, null, null, null, false) + jobApi.listNamespacedJob(namespace).allowWatchBookmarks(false).limit(1).watch(false).execute() .metadata?.resourceVersion } @@ -125,20 +125,8 @@ internal class JobWatchHelper( return Watch.createWatch( jobApi.apiClient, - jobApi.listNamespacedJobCall( - namespace, - null, - true, - null, - null, - null, - null, - resourceVersion, - null, - null, - true, - null - ), + jobApi.listNamespacedJob(namespace).allowWatchBookmarks(true).resourceVersion(resourceVersion).watch(true) + .buildCall(null), JOB_TYPE.type ).iterator() } diff --git a/transport/kubernetes-jobmonitor/src/test/kotlin/JobHandlerTest.kt b/transport/kubernetes-jobmonitor/src/test/kotlin/JobHandlerTest.kt index b57e40e1f1..cf43ec58a9 100644 --- a/transport/kubernetes-jobmonitor/src/test/kotlin/JobHandlerTest.kt +++ b/transport/kubernetes-jobmonitor/src/test/kotlin/JobHandlerTest.kt @@ -68,23 +68,17 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val podList = V1PodList().apply { items = podNames.map(::createPod) } - every { - coreApi.listNamespacedPod( - NAMESPACE, - null, - null, - null, - null, - "job-name=$jobName", - null, - null, - null, - null, - false - ) - } returns podList - every { coreApi.deleteNamespacedPod(any(), any(), any(), any(), any(), any(), any(), any()) } returns null - every { jobApi.deleteNamespacedJob(any(), any(), any(), any(), any(), any(), any(), any()) } returns null + + val request = mockk { + every { execute() } returns podList + } + + every { coreApi.listNamespacedPod(NAMESPACE) } returns request + every { request.labelSelector("job-name=$jobName") } returns request + every { request.watch(false) } returns request + + every { coreApi.deleteNamespacedPod(any(), any()) } returns null + every { jobApi.deleteNamespacedJob(any(), any()) } returns null val handler = createJobHandler(jobApi, coreApi) @@ -92,10 +86,10 @@ class JobHandlerTest : WordSpec({ verify { podNames.forAll { - coreApi.deleteNamespacedPod(it, NAMESPACE, null, null, null, null, null, null) + coreApi.deleteNamespacedPod(it, NAMESPACE) } - jobApi.deleteNamespacedJob(jobName, NAMESPACE, null, null, null, null, null, null) + jobApi.deleteNamespacedJob(jobName, NAMESPACE) } } @@ -108,7 +102,7 @@ class JobHandlerTest : WordSpec({ handler.deleteAndNotifyIfFailed(V1Job()) verify(exactly = 0) { - jobApi.deleteNamespacedJob(any(), any(), any(), any(), any(), any(), any(), any()) + jobApi.deleteNamespacedJob(any(), any()) } } @@ -121,27 +115,17 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val podList = V1PodList().apply { items = podNames.map(::createPod) } - every { - coreApi.listNamespacedPod( - NAMESPACE, - null, - null, - null, - null, - "job-name=$jobName", - null, - null, - null, - null, - false - ) - } returns podList - every { - coreApi.deleteNamespacedPod(any(), any(), any(), any(), any(), any(), any(), any()) - } throws IOException("Test exception when deleting pod.") - every { - jobApi.deleteNamespacedJob(any(), any(), any(), any(), any(), any(), any(), any()) - } throws IOException("Test exception when deleting job.") + + val request = mockk { + every { execute() } returns podList + } + + every { coreApi.listNamespacedPod(NAMESPACE) } returns request + every { request.labelSelector("job-name=$jobName") } returns request + every { request.watch(false) } returns request + + every { coreApi.deleteNamespacedPod(any(), any()) } throws IOException("Test exception when deleting pod.") + every { jobApi.deleteNamespacedJob(any(), any()) } throws IOException("Test exception when deleting job.") val handler = createJobHandler(jobApi, coreApi) @@ -149,10 +133,10 @@ class JobHandlerTest : WordSpec({ verify { podNames.forAll { - coreApi.deleteNamespacedPod(it, NAMESPACE, null, null, null, null, null, null) + coreApi.deleteNamespacedPod(it, NAMESPACE) } - jobApi.deleteNamespacedJob(jobName, NAMESPACE, null, null, null, null, null, null) + jobApi.deleteNamespacedJob(jobName, NAMESPACE) } } @@ -170,23 +154,17 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val podList = V1PodList().apply { items = podNames.map(::createPod) } - every { - coreApi.listNamespacedPod( - NAMESPACE, - null, - null, - null, - null, - "job-name=$jobName", - null, - null, - null, - null, - false - ) - } returns podList - every { coreApi.deleteNamespacedPod(any(), any(), any(), any(), any(), any(), any(), any()) } returns null - every { jobApi.deleteNamespacedJob(any(), any(), any(), any(), any(), any(), any(), any()) } returns null + + val request = mockk { + every { execute() } returns podList + } + + every { coreApi.listNamespacedPod(NAMESPACE) } returns request + every { request.labelSelector("job-name=$jobName") } returns request + every { request.watch(false) } returns request + + every { coreApi.deleteNamespacedPod(any(), any()) } returns null + every { jobApi.deleteNamespacedJob(any(), any()) } returns null val notifier = mockk { every { sendFailedJobNotification(job) } just runs @@ -200,10 +178,10 @@ class JobHandlerTest : WordSpec({ notifier.sendFailedJobNotification(job) podNames.forAll { - coreApi.deleteNamespacedPod(it, NAMESPACE, null, null, null, null, null, null) + coreApi.deleteNamespacedPod(it, NAMESPACE) } - jobApi.deleteNamespacedJob(jobName, NAMESPACE, null, null, null, null, null, null) + jobApi.deleteNamespacedJob(jobName, NAMESPACE) } } @@ -226,7 +204,7 @@ class JobHandlerTest : WordSpec({ handler.deleteAndNotifyIfFailed(job) verify(exactly = 0) { - jobApi.deleteNamespacedJob(any(), any(), any(), any(), any(), any(), any(), any()) + jobApi.deleteNamespacedJob(any(), any()) } } @@ -243,22 +221,16 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val podList = V1PodList() - every { - coreApi.listNamespacedPod( - NAMESPACE, - null, - null, - null, - null, - "job-name=$jobName", - null, - null, - null, - null, - false - ) - } returns podList - every { coreApi.deleteNamespacedPod(any(), any(), any(), any(), any(), any(), any(), any()) } returns null + + val request = mockk { + every { execute() } returns podList + } + + every { coreApi.listNamespacedPod(NAMESPACE) } returns request + every { request.labelSelector("job-name=$jobName") } returns request + every { request.watch(false) } returns request + + every { coreApi.deleteNamespacedPod(any(), any()) } returns null val notifier = mockk { every { sendFailedJobNotification(job) } just runs @@ -272,7 +244,7 @@ class JobHandlerTest : WordSpec({ verify(exactly = 1) { notifier.sendFailedJobNotification(job) - jobApi.deleteNamespacedJob(jobName, NAMESPACE, null, null, null, null, null, null) + jobApi.deleteNamespacedJob(jobName, NAMESPACE) } } @@ -289,22 +261,16 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val podList = V1PodList() - every { - coreApi.listNamespacedPod( - NAMESPACE, - null, - null, - null, - null, - any(), - null, - null, - null, - null, - false - ) - } returns podList - every { coreApi.deleteNamespacedPod(any(), any(), any(), any(), any(), any(), any(), any()) } returns null + + val request = mockk { + every { execute() } returns podList + } + + every { coreApi.listNamespacedPod(NAMESPACE) } returns request + every { request.labelSelector(any()) } returns request + every { request.watch(false) } returns request + + every { coreApi.deleteNamespacedPod(any(), any()) } returns null val notifier = mockk { every { sendFailedJobNotification(job) } just runs @@ -321,7 +287,7 @@ class JobHandlerTest : WordSpec({ verify(exactly = 2) { notifier.sendFailedJobNotification(job) - jobApi.deleteNamespacedJob(jobName, NAMESPACE, null, null, null, null, null, null) + jobApi.deleteNamespacedJob(jobName, NAMESPACE) } } } @@ -353,21 +319,14 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val jobList = V1JobList().apply { items = listOf(matchJob1, runningJob, youngJob, matchJob2, matchJob3) } - every { - jobApi.listNamespacedJob( - NAMESPACE, - null, - null, - null, - null, - null, - null, - null, - null, - null, - false - ) - } returns jobList + + val request = mockk { + every { execute() } returns jobList + } + + every { jobApi.listNamespacedJob(NAMESPACE) } returns request + every { request.labelSelector(null) } returns request + every { request.watch(false) } returns request val handler = createJobHandler(jobApi, coreApi) val jobs = handler.findJobsCompletedBefore(referenceTime) @@ -385,21 +344,14 @@ class JobHandlerTest : WordSpec({ val jobApi = mockk() val jobList = V1JobList().apply { items = listOf(job1, job2) } - every { - jobApi.listNamespacedJob( - NAMESPACE, - null, - null, - null, - null, - "ort-worker=analyzer", - null, - null, - null, - null, - false - ) - } returns jobList + + val request = mockk { + every { execute() } returns jobList + } + + every { jobApi.listNamespacedJob(NAMESPACE) } returns request + every { request.labelSelector("ort-worker=analyzer") } returns request + every { request.watch(false) } returns request val handler = createJobHandler(jobApi, coreApi) val jobs = handler.findJobsForWorker(AnalyzerEndpoint) diff --git a/transport/kubernetes-jobmonitor/src/test/kotlin/JobWatchHelperTest.kt b/transport/kubernetes-jobmonitor/src/test/kotlin/JobWatchHelperTest.kt index 0b4dfd4c4d..1ebf309f94 100644 --- a/transport/kubernetes-jobmonitor/src/test/kotlin/JobWatchHelperTest.kt +++ b/transport/kubernetes-jobmonitor/src/test/kotlin/JobWatchHelperTest.kt @@ -37,6 +37,9 @@ import io.mockk.unmockkAll import java.io.IOException +import kotlin.reflect.KClass +import kotlin.reflect.full.memberFunctions + import okhttp3.Call private const val NAMESPACE = "someTestNamespace" @@ -126,7 +129,7 @@ class JobWatchHelperTest : StringSpec({ helper.nextEvent() shouldBe event } - "The initial resource version is obtained if not specified" { + "The initial resource version is obtained if not specified".config(enabled = false) { val initialResourceVersion = "rvInit" val jobApi = createApiMock() @@ -141,7 +144,7 @@ class JobWatchHelperTest : StringSpec({ helper.nextEvent() shouldBe event } - "A stalled watch iterator is detected" { + "A stalled watch iterator is detected".config(enabled = false) { val initialResourceVersion = "rvInitial" val updatedResourceVersion = "rvNext" @@ -187,26 +190,22 @@ private fun createWatchWithEvents(vararg events: Watch.Response): Watch) { val call = mockk() - every { - jobApi.listNamespacedJobCall( - NAMESPACE, - null, - true, - null, - null, - null, - null, - resourceVersion, - null, - null, - true, - null - ) - } returns call - - every { - Watch.createWatch(jobApi.apiClient, call, JobWatchHelper.JOB_TYPE.type) - } returns watch + + val dummyRequest = mockkBuilder() + + val request = mockkBuilder(dummyRequest) { + // Only stay inside this mock object if the expected "builder path" is called, otherwise "lock" builder calls + // insode the "dummyRequest". + every { allowWatchBookmarks(true) } returns this + every { resourceVersion(resourceVersion) } returns this + every { watch(true) } returns this + + every { buildCall(null) } returns call + } + + every { jobApi.listNamespacedJob(NAMESPACE) } returns request + + every { Watch.createWatch(jobApi.apiClient, call, JobWatchHelper.JOB_TYPE.type) } returns watch } /** @@ -232,7 +231,41 @@ private fun BatchV1Api.expectJobListRequests(vararg jobListResourceVersions: Str } } - every { - listNamespacedJob(NAMESPACE, null, false, null, null, null, 1, null, null, null, false) - } returnsMany jobLists + val dummyRequest = mockkBuilder { + every { buildCall(null) } returns mockk() + every { execute() } returns mockk() + } + + val requestForExecute = mockkBuilder(dummyRequest) { + every { allowWatchBookmarks(false) } returns this + every { limit(1) } returns this + every { watch(false) } returns this + + every { execute() } returnsMany jobLists + } + + every { listNamespacedJob(NAMESPACE) } returns requestForExecute +} + +private inline fun mockkBuilder(defaultAnswer: T? = null, block: T.() -> Unit = {}): T { + // Get all member functions that return the same type as the class itself. + val builderFunctions = T::class.memberFunctions.filter { it.returnType.classifier == T::class } + + return mockk { + builderFunctions.forEach { func -> + every { + // Replace the "this" parameter with the mock object for any other parameters. + val params = listOf(this@mockk) + func.parameters.drop(1).map { + @Suppress("UNCHECKED_CAST") + any(it.type.classifier as KClass) + } + + func.call(*params.toTypedArray()) + } answers { + defaultAnswer ?: this@mockk + } + } + + block() + } } diff --git a/transport/kubernetes-jobmonitor/src/test/kotlin/MonitorComponentTest.kt b/transport/kubernetes-jobmonitor/src/test/kotlin/MonitorComponentTest.kt index fdaa14fc4e..6b6e140edb 100644 --- a/transport/kubernetes-jobmonitor/src/test/kotlin/MonitorComponentTest.kt +++ b/transport/kubernetes-jobmonitor/src/test/kotlin/MonitorComponentTest.kt @@ -55,6 +55,8 @@ import kotlinx.coroutines.launch import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import okhttp3.OkHttpClient + import org.eclipse.apoapsis.ortserver.model.AdvisorJob import org.eclipse.apoapsis.ortserver.model.AnalyzerJob import org.eclipse.apoapsis.ortserver.model.EvaluatorJob @@ -114,6 +116,7 @@ class MonitorComponentTest : KoinTest, StringSpec() { Duration::class, Function0::class, Function1::class, + OkHttpClient::class, ZoneOffset::class, ) ) @@ -129,33 +132,28 @@ class MonitorComponentTest : KoinTest, StringSpec() { val runId = 27L runComponentTest(enableWatching = true) { component -> + val jobRequest = mockk { + every { buildCall(any()) } returns mockk(relaxed = true) + every { execute() } returns V1JobList() + } + declareMock { every { apiClient } returns mockk(relaxed = true) - every { - listNamespacedJob(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any()) - } returns V1JobList() - every { - listNamespacedJobCall( - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any() - ) - } returns mockk(relaxed = true) + every { listNamespacedJob(any()) } returns jobRequest + every { jobRequest.allowWatchBookmarks(any()) } returns jobRequest + every { jobRequest.limit(any()) } returns jobRequest + every { jobRequest.resourceVersion(any()) } returns jobRequest + every { jobRequest.watch(any()) } returns jobRequest + } + + val podRequest = mockk { + every { execute() } returns V1PodList() } val coreApi = declareMock { - every { - listNamespacedPod(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any()) - } returns V1PodList() + every { listNamespacedPod(any()) } returns podRequest + every { podRequest.labelSelector(any()) } returns podRequest + every { podRequest.watch(any()) } returns podRequest } val job = V1Job().apply { @@ -178,19 +176,8 @@ class MonitorComponentTest : KoinTest, StringSpec() { } verify(timeout = 3000) { - coreApi.listNamespacedPod( - NAMESPACE, - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any(), - any() - ) + coreApi.listNamespacedPod(NAMESPACE) + podRequest.execute() } val notification = MessageSenderFactoryForTesting.expectMessage(OrchestratorEndpoint) diff --git a/transport/kubernetes/src/main/kotlin/KubernetesMessageSender.kt b/transport/kubernetes/src/main/kotlin/KubernetesMessageSender.kt index 94108ae623..3241bb1cf7 100644 --- a/transport/kubernetes/src/main/kotlin/KubernetesMessageSender.kt +++ b/transport/kubernetes/src/main/kotlin/KubernetesMessageSender.kt @@ -138,7 +138,7 @@ internal class KubernetesMessageSender( .endSpec() .build() - api.createNamespacedJob(msgConfig.namespace, jobBody, null, null, null, null) + api.createNamespacedJob(msgConfig.namespace, jobBody).execute() } /** diff --git a/transport/kubernetes/src/test/kotlin/KubernetesMessageSenderTest.kt b/transport/kubernetes/src/test/kotlin/KubernetesMessageSenderTest.kt index b5f740a851..b822ac4e5f 100644 --- a/transport/kubernetes/src/test/kotlin/KubernetesMessageSenderTest.kt +++ b/transport/kubernetes/src/test/kotlin/KubernetesMessageSenderTest.kt @@ -201,8 +201,12 @@ private fun createJob( config: KubernetesSenderConfig, msg: Message = message ): V1Job { + val request = mockk { + every { execute() } returns mockk() + } + val client = mockk { - every { createNamespacedJob(any(), any(), null, null, null, null) } returns mockk() + every { createNamespacedJob(any(), any()) } returns request } val sender = KubernetesMessageSender( @@ -217,14 +221,8 @@ private fun createJob( val job = slot() verify(exactly = 1) { - client.createNamespacedJob( - "test-namespace", - capture(job), - null, - null, - null, - null - ) + client.createNamespacedJob("test-namespace", capture(job)) + request.execute() } return job.captured