From 876708adc60f0189918a068a42ace7b21218cb37 Mon Sep 17 00:00:00 2001 From: wind57 Date: Fri, 10 Oct 2025 22:31:46 +0300 Subject: [PATCH 1/2] first Signed-off-by: wind57 --- ...rnetesClientInformerAutoConfiguration.java | 4 +- ...oConfigurationApplicationContextTests.java | 779 ++++++++++-------- ...DiscoveryClientAutoConfigurationTests.java | 4 +- .../kubernetes/client/discovery/Sandbox.java | 212 +++++ 4 files changed, 631 insertions(+), 368 deletions(-) create mode 100644 spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerAutoConfiguration.java index a7c1ba959f..cd28e03862 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerAutoConfiguration.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerAutoConfiguration.java @@ -113,10 +113,10 @@ SharedIndexInformer servicesSharedIndexInformer(SharedInformerFactory SharedIndexInformer endpointsSharedIndexInformer(SharedInformerFactory sharedInformerFactory, ApiClient apiClient, String kubernetesClientNamespace) { - GenericKubernetesApi servicesApi = new GenericKubernetesApi<>(V1Endpoints.class, + GenericKubernetesApi endpointsApi = new GenericKubernetesApi<>(V1Endpoints.class, V1EndpointsList.class, "", "v1", "endpoints", apiClient); - return sharedInformerFactory.sharedIndexInformerFor(servicesApi, V1Endpoints.class, 0L, + return sharedInformerFactory.sharedIndexInformerFor(endpointsApi, V1Endpoints.class, 0L, kubernetesClientNamespace); } diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java index 4a3ce1920e..d47f755ea6 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -18,10 +18,23 @@ import java.io.StringReader; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1Endpoints; +import io.kubernetes.client.openapi.models.V1EndpointsList; +import io.kubernetes.client.openapi.models.V1ListMeta; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceList; +import io.kubernetes.client.util.ClientBuilder; import io.kubernetes.client.util.Config; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.k3s.K3sContainer; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -39,6 +52,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertNonSelectiveNamespacesBeansMissing; import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertNonSelectiveNamespacesBeansPresent; @@ -53,271 +69,67 @@ */ class KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests { - private ApplicationContextRunner applicationContextRunner; - - private static K3sContainer container; - - @AfterAll - static void afterAll() { - container.stop(); - } - - @Test - void discoveryEnabledDefault() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void discoveryEnabledDefaultWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.kubernetes.discovery.namespaces=a,b,c"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 3); - }); - } - - @Test - void discoveryEnabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.enabled=true"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void discoveryEnabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b,c"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 3); - }); - } - - @Test - void discoveryDisabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.enabled=false"); - applicationContextRunner.run(context -> { - assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void discoveryDisabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b,c"); - applicationContextRunner.run(context -> { - assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void kubernetesDiscoveryEnabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.kubernetes.discovery.enabled=true"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void kubernetesDiscoveryEnabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.kubernetes.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 2); - }); - } - - @Test - void kubernetesDiscoveryDisabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.kubernetes.discovery.enabled=false"); - applicationContextRunner.run(context -> { - assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - // only "simple" one from commons, as ours is not picked up - assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void kubernetesDiscoveryDisabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.kubernetes.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - // only "simple" one from commons, as ours is not picked up - assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void kubernetesReactiveDiscoveryEnabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.reactive.enabled=true"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } + @RegisterExtension + static WireMockExtension apiServer = + WireMockExtension.newInstance() + .options(options().dynamicPort()) + .build(); - @Test - void kubernetesReactiveDiscoveryEnabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.reactive.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); + @Configuration + static class ApiClientConfig { - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); + @Bean + @Primary + ApiClient apiClient() throws Exception { + ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + apiServer.getPort()).build(); + return client; + } - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 2); - }); } - @Test - void kubernetesReactiveDiscoveryDisabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.reactive.enabled=false"); - applicationContextRunner.run(context -> { - assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); + @BeforeEach + void beforeAll() { + apiServer.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) + .withQueryParam("watch", WireMock.equalTo("false")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1EndpointsList() + .metadata(new V1ListMeta().resourceVersion("0")) + .addItemsItem(new V1Endpoints().metadata(new V1ObjectMeta().namespace("default"))) + )))); + + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) + .withQueryParam("watch", WireMock.equalTo("true")) + .willReturn(aResponse().withStatus(200).withBody("{}")); + + apiServer.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/services.*")) + .withQueryParam("watch", equalTo("false")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1ServiceList() + .metadata(new V1ListMeta().resourceVersion("0")) + .addItemsItem(new V1Service().metadata(new V1ObjectMeta().namespace("default"))) + )))); + + apiServer.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/services.*")) + .withQueryParam("watch", equalTo("true")) + .willReturn(aResponse().withStatus(200).withBody("{}"))); } - @Test - void kubernetesReactiveDiscoveryDisabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.reactive.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 2); - }); - } + private ApplicationContextRunner applicationContextRunner; - /** - * blocking is disabled, and it should not impact reactive in any way. - */ @Test - void blockingDisabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.blocking.enabled=false"); + void discoveryEnabledDefault() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false"); applicationContextRunner.run(context -> { assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); @@ -333,99 +145,352 @@ void blockingDisabled() { }); } - /** - * blocking is disabled, and it should not impact reactive in any way. - */ - @Test - void blockingDisabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.blocking.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - // simple from commons and ours - assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - assertThat(context).hasBean("reactiveIndicatorInitializer"); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 2); - }); - } - - @Test - void healthDisabled() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.client.health-indicator.enabled=false"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void healthDisabledWithSelectiveNamespaces() { - setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.client.health-indicator.enabled=false", - "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 2); - }); - } - - @Test - void healthEnabledClassNotPresent() { - setupWithFilteredClassLoader("org.springframework.boot.health.contributor.ReactiveHealthIndicator", - "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.client.health-indicator.enabled=false"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - - assertNonSelectiveNamespacesBeansPresent(context); - assertSelectiveNamespacesBeansMissing(context); - }); - } - - @Test - void healthEnabledClassNotPresentWithSelectiveNamespaces() { - setupWithFilteredClassLoader("org.springframework.boot.health.contributor.ReactiveHealthIndicator", - "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", - "spring.cloud.discovery.client.health-indicator.enabled=false", - "spring.cloud.kubernetes.discovery.namespaces=a,b"); - applicationContextRunner.run(context -> { - assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); - assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); - assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); - - assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); - assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); - - assertNonSelectiveNamespacesBeansMissing(context); - assertSelectiveNamespacesBeansPresent(context, 2); - }); - } +// @Test +// void discoveryEnabledDefaultWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.kubernetes.discovery.namespaces=a,b,c"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 3); +// }); +// } +// +// @Test +// void discoveryEnabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.enabled=true"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void discoveryEnabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b,c"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 3); +// }); +// } +// +// @Test +// void discoveryDisabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.enabled=false"); +// applicationContextRunner.run(context -> { +// assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void discoveryDisabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b,c"); +// applicationContextRunner.run(context -> { +// assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void kubernetesDiscoveryEnabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.kubernetes.discovery.enabled=true"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void kubernetesDiscoveryEnabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.kubernetes.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 2); +// }); +// } +// +// @Test +// void kubernetesDiscoveryDisabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.kubernetes.discovery.enabled=false"); +// applicationContextRunner.run(context -> { +// assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// // only "simple" one from commons, as ours is not picked up +// assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void kubernetesDiscoveryDisabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.kubernetes.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// // only "simple" one from commons, as ours is not picked up +// assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void kubernetesReactiveDiscoveryEnabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.reactive.enabled=true"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void kubernetesReactiveDiscoveryEnabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.reactive.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 2); +// }); +// } +// +// @Test +// void kubernetesReactiveDiscoveryDisabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.reactive.enabled=false"); +// applicationContextRunner.run(context -> { +// assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void kubernetesReactiveDiscoveryDisabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.reactive.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).doesNotHaveBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 2); +// }); +// } +// +// /** +// * blocking is disabled, and it should not impact reactive in any way. +// */ +// @Test +// void blockingDisabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.blocking.enabled=false"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// /** +// * blocking is disabled, and it should not impact reactive in any way. +// */ +// @Test +// void blockingDisabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.blocking.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// // simple from commons and ours +// assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); +// assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// assertThat(context).hasBean("reactiveIndicatorInitializer"); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 2); +// }); +// } +// +// @Test +// void healthDisabled() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.client.health-indicator.enabled=false"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void healthDisabledWithSelectiveNamespaces() { +// setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.client.health-indicator.enabled=false", +// "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 2); +// }); +// } +// +// @Test +// void healthEnabledClassNotPresent() { +// setupWithFilteredClassLoader("org.springframework.boot.health.contributor.ReactiveHealthIndicator", +// "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.client.health-indicator.enabled=false"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// +// assertNonSelectiveNamespacesBeansPresent(context); +// assertSelectiveNamespacesBeansMissing(context); +// }); +// } +// +// @Test +// void healthEnabledClassNotPresentWithSelectiveNamespaces() { +// setupWithFilteredClassLoader("org.springframework.boot.health.contributor.ReactiveHealthIndicator", +// "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", +// "spring.cloud.discovery.client.health-indicator.enabled=false", +// "spring.cloud.kubernetes.discovery.namespaces=a,b"); +// applicationContextRunner.run(context -> { +// assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); +// assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient"); +// assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); +// +// assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); +// assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); +// +// assertNonSelectiveNamespacesBeansMissing(context); +// assertSelectiveNamespacesBeansPresent(context, 2); +// }); +// } private void setup(String... properties) { applicationContextRunner = new ApplicationContextRunner() @@ -449,23 +514,9 @@ private void setupWithFilteredClassLoader(String name, String... properties) { KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class, KubernetesClientDiscoveryClientSpelAutoConfiguration.class)) - .withUserConfiguration(ApiClientConfig.class) + //.withUserConfiguration(ApiClientConfig.class) .withClassLoader(new FilteredClassLoader(name)) .withPropertyValues(properties); } - @Configuration - static class ApiClientConfig { - - @Bean - @Primary - ApiClient apiClient() throws Exception { - container = Commons.container(); - container.start(); - - return Config.fromConfig(new StringReader(container.getKubeConfigYaml())); - } - - } - } diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationTests.java index b54bde292c..0b32360050 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationTests.java @@ -88,12 +88,12 @@ ApiClient apiClient() { WireMock.configureFor(wireMockServer.port()); stubFor(get("/api/v1/namespaces/test/endpoints?resourceVersion=0&watch=false") .willReturn(aResponse().withStatus(200) - .withBody(new JSON().serialize(new V1EndpointsListBuilder() + .withBody(JSON.serialize(new V1EndpointsListBuilder() .withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build()) .build())))); stubFor(get("/api/v1/namespaces/test/services?resourceVersion=0&watch=false") .willReturn(aResponse().withStatus(200) - .withBody(new JSON().serialize(new V1ServiceListBuilder() + .withBody(JSON.serialize(new V1ServiceListBuilder() .withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build()) .build())))); return new ClientBuilder().setBasePath(wireMockServer.baseUrl()).build(); diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java new file mode 100644 index 0000000000..6126963194 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java @@ -0,0 +1,212 @@ +package org.springframework.cloud.kubernetes.client.discovery; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.PostServeAction; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import io.kubernetes.client.informer.SharedIndexInformer; +import io.kubernetes.client.informer.SharedInformerFactory; +import io.kubernetes.client.informer.cache.Lister; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapList; +import io.kubernetes.client.openapi.models.V1Endpoints; +import io.kubernetes.client.openapi.models.V1EndpointsList; +import io.kubernetes.client.openapi.models.V1ListMeta; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1Pod; +import io.kubernetes.client.openapi.models.V1PodList; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import java.util.Collections; +import java.util.concurrent.Semaphore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; + +@SpringBootTest(properties = "spring.cloud.config.import-check.enabled=false", webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Sandbox { + + public static class CountRequestAction extends PostServeAction { + @Override + public String getName() { + return "semaphore"; + } + + @Override + public void doAction(ServeEvent serveEvent, Admin admin, Parameters parameters) { + Semaphore count = (Semaphore) parameters.get("semaphore"); + count.release(); + } + } + + @RegisterExtension + static WireMockExtension apiServer = + WireMockExtension.newInstance() + .options(options().dynamicPort().extensions(new CountRequestAction())) + .build(); + + @SpringBootApplication + static class App { + + @Bean + public ApiClient testingApiClient() { + return new ClientBuilder().setBasePath("http://localhost:" + apiServer.getPort()).build(); + } + + @Bean + public SharedIndexInformer podInformer( + ApiClient apiClient, SharedInformerFactory sharedInformerFactory) { + GenericKubernetesApi genericApi = + new GenericKubernetesApi<>(V1Pod.class, V1PodList.class, "", "v1", "pods", apiClient); + return sharedInformerFactory.sharedIndexInformerFor(genericApi, V1Pod.class, 0); + } + + @Bean + public SharedIndexInformer configMapInformer( + ApiClient apiClient, SharedInformerFactory sharedInformerFactory) { + GenericKubernetesApi genericApi = + new GenericKubernetesApi<>( + V1ConfigMap.class, V1ConfigMapList.class, "", "v1", "configmaps", apiClient); + return sharedInformerFactory.sharedIndexInformerFor( + genericApi, V1ConfigMap.class, 0, "default"); + } + + @Bean + SharedIndexInformer endpointsSharedIndexInformer(SharedInformerFactory sharedInformerFactory, + ApiClient apiClient) { + + GenericKubernetesApi endpointsApi = new GenericKubernetesApi<>(V1Endpoints.class, + V1EndpointsList.class, "", "v1", "endpoints", apiClient); + + return sharedInformerFactory.sharedIndexInformerFor(endpointsApi, V1Endpoints.class, 0L, + "default"); + } + + @Bean + @ConditionalOnMissingBean + SharedInformerFactory sharedInformerFactory(ApiClient client) { + return new SharedInformerFactory(client); + } + } + + @Autowired private SharedInformerFactory informerFactory; + + @Autowired private SharedIndexInformer podInformer; + + @Autowired private SharedIndexInformer configMapInformer; + + @Test + void informerInjection() throws InterruptedException { + assertThat(podInformer).isNotNull(); + assertThat(configMapInformer).isNotNull(); + + Semaphore getCount = new Semaphore(2); + Semaphore watchCount = new Semaphore(2); + Parameters getParams = new Parameters(); + Parameters watchParams = new Parameters(); + getParams.put("semaphore", getCount); + watchParams.put("semaphore", watchCount); + + V1Pod foo1 = + new V1Pod().kind("Pod").metadata(new V1ObjectMeta().namespace("default").name("foo1")); + V1ConfigMap bar1 = + new V1ConfigMap() + .kind("ConfigMap") + .metadata(new V1ObjectMeta().namespace("default").name("bar1")); + + apiServer.stubFor( + get(urlMatching("^/api/v1/pods.*")) + .withPostServeAction("semaphore", getParams) + .withQueryParam("watch", equalTo("false")) + .willReturn( + aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1PodList() + .metadata(new V1ListMeta().resourceVersion("0")) + .items(Collections.singletonList(foo1)))))); + apiServer.stubFor( + get(urlMatching("^/api/v1/pods.*")) + .withPostServeAction("semaphore", watchParams) + .withQueryParam("watch", equalTo("true")) + .willReturn(aResponse().withStatus(200).withBody("{}"))); + + apiServer.stubFor( + get(urlMatching("^/api/v1/namespaces/default/configmaps.*")) + .withPostServeAction("semaphore", getParams) + .withQueryParam("watch", equalTo("false")) + .willReturn( + aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1ConfigMapList() + .metadata(new V1ListMeta().resourceVersion("0")) + .items(Collections.singletonList(bar1)))))); + apiServer.stubFor( + get(urlMatching("^/api/v1/namespaces/default/configmaps.*")) + .withPostServeAction("semaphore", watchParams) + .withQueryParam("watch", equalTo("true")) + .willReturn(aResponse().withStatus(200).withBody("{}"))); + + + apiServer.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) + //.withQueryParam("watch", WireMock.equalTo("false")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1EndpointsList().addItemsItem(new V1Endpoints()) + )))); + + // These will be released for each web call above. + getCount.acquire(2); + watchCount.acquire(2); + + informerFactory.startAllRegisteredInformers(); + + // Wait for the GETs to complete and the watches to start. + getCount.acquire(2); + watchCount.acquire(2); + + apiServer.verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v1/pods")).withQueryParam("watch", equalTo("false"))); + + apiServer.verify( + getRequestedFor(urlPathEqualTo("/api/v1/pods")).withQueryParam("watch", equalTo("true"))); + + apiServer.verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps")) + .withQueryParam("watch", equalTo("false"))); + + apiServer.verify( + getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps")) + .withQueryParam("watch", equalTo("true"))); + + assertThat(new Lister<>(podInformer.getIndexer()).list()).hasSize(1); + assertThat(new Lister<>(configMapInformer.getIndexer()).list()).hasSize(1); + } + +} From defbb8fbf6b3296e82728a16ee93fc0c82f22ea1 Mon Sep 17 00:00:00 2001 From: wind57 Date: Sat, 11 Oct 2025 17:49:14 +0300 Subject: [PATCH 2/2] wip Signed-off-by: wind57 --- ...oConfigurationApplicationContextTests.java | 144 ++++++++---- .../kubernetes/client/discovery/Sandbox.java | 212 ------------------ 2 files changed, 95 insertions(+), 261 deletions(-) delete mode 100644 spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java index d47f755ea6..f616a0592b 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -17,15 +17,23 @@ package org.springframework.cloud.kubernetes.client.discovery; import java.io.StringReader; +import java.util.Collections; +import java.util.concurrent.Semaphore; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.PostServeAction; +import com.github.tomakehurst.wiremock.extension.ServeEventListener; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.JSON; import io.kubernetes.client.openapi.models.V1Endpoints; import io.kubernetes.client.openapi.models.V1EndpointsList; import io.kubernetes.client.openapi.models.V1ListMeta; import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1Pod; +import io.kubernetes.client.openapi.models.V1PodList; import io.kubernetes.client.openapi.models.V1Service; import io.kubernetes.client.openapi.models.V1ServiceList; import io.kubernetes.client.util.ClientBuilder; @@ -69,60 +77,22 @@ */ class KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests { - @RegisterExtension - static WireMockExtension apiServer = - WireMockExtension.newInstance() - .options(options().dynamicPort()) - .build(); + private static final Parameters GET_ENDPOINTS_PARAMETERS = new Parameters(); - @Configuration - static class ApiClientConfig { + private static final Semaphore GET_ENDPOINTS_SEMAPHORE = new Semaphore(1); - @Bean - @Primary - ApiClient apiClient() throws Exception { - ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + apiServer.getPort()).build(); - return client; - } + private static final String GET_ENDPOINTS_PARAMETER_NAME = "get-endpoints"; - } + @RegisterExtension + private static final WireMockExtension API_SERVER = + WireMockExtension.newInstance() + .options(options().dynamicPort().extensions(new PostServeExtension())) + .build(); @BeforeEach - void beforeAll() { - apiServer.stubFor( - WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) - .withQueryParam("watch", WireMock.equalTo("false")) - .willReturn( - WireMock.aResponse() - .withStatus(200) - .withBody( - JSON.serialize( - new V1EndpointsList() - .metadata(new V1ListMeta().resourceVersion("0")) - .addItemsItem(new V1Endpoints().metadata(new V1ObjectMeta().namespace("default"))) - )))); - - WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) - .withQueryParam("watch", WireMock.equalTo("true")) - .willReturn(aResponse().withStatus(200).withBody("{}")); - - apiServer.stubFor( - WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/services.*")) - .withQueryParam("watch", equalTo("false")) - .willReturn( - WireMock.aResponse() - .withStatus(200) - .withBody( - JSON.serialize( - new V1ServiceList() - .metadata(new V1ListMeta().resourceVersion("0")) - .addItemsItem(new V1Service().metadata(new V1ObjectMeta().namespace("default"))) - )))); - - apiServer.stubFor( - WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/services.*")) - .withQueryParam("watch", equalTo("true")) - .willReturn(aResponse().withStatus(200).withBody("{}"))); + void beforeAll() throws InterruptedException { + mockEndpointsCall(); + mockServicesCall(); } private ApplicationContextRunner applicationContextRunner; @@ -519,4 +489,80 @@ private void setupWithFilteredClassLoader(String name, String... properties) { .withPropertyValues(properties); } + @Configuration + static class ApiClientConfig { + + @Bean + @Primary + ApiClient apiClient() throws Exception { + return new ClientBuilder().setBasePath("http://localhost:" + API_SERVER.getPort()).build(); + } + + } + + private static void mockEndpointsCall() { + + // watch=false, first call to populate watcher cache + API_SERVER.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) + .withQueryParam("watch", WireMock.equalTo("false")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1EndpointsList() + .metadata(new V1ListMeta().resourceVersion("0")) + .addItemsItem(new V1Endpoints().metadata(new V1ObjectMeta().namespace("default"))) + )))); + + // watch=true, call to re-sync + API_SERVER.stubFor(WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) + .withQueryParam("watch", WireMock.equalTo("true")) + .willReturn(aResponse().withStatus(200).withBody("{}"))); + } + + private static void mockServicesCall() throws InterruptedException { + + // watch=false, first call to populate watcher cache + API_SERVER.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/services.*")) + .withQueryParam("watch", equalTo("false")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody( + JSON.serialize( + new V1ServiceList() + .metadata(new V1ListMeta().resourceVersion("0")) + .addItemsItem(new V1Service().metadata(new V1ObjectMeta().namespace("default"))) + )))); + + GET_ENDPOINTS_SEMAPHORE.acquire(1); + GET_ENDPOINTS_PARAMETERS.put(GET_ENDPOINTS_PARAMETER_NAME, GET_ENDPOINTS_SEMAPHORE); + // watch=true, call to re-sync + API_SERVER.stubFor( + WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/services.*")) + .withPostServeAction("PostServeExtension", GET_ENDPOINTS_PARAMETERS) + .withQueryParam("watch", equalTo("true")) + .willReturn(aResponse().withStatus(200).withBody("{}"))); + } + + private static final class PostServeExtension implements ServeEventListener { + + @Override + public String getName() { + return "PostServeExtension"; + } + + @Override + public void afterMatch(ServeEvent serveEvent, Parameters parameters) { + Object getEndpointsSemaphore = parameters.get(GET_ENDPOINTS_PARAMETER_NAME); + if (getEndpointsSemaphore != null) { + Semaphore semaphore = (Semaphore) getEndpointsSemaphore; + semaphore.release(); + } + } + } + } diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java deleted file mode 100644 index 6126963194..0000000000 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/Sandbox.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.springframework.cloud.kubernetes.client.discovery; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.assertj.core.api.Assertions.assertThat; - -import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.PostServeAction; -import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import io.kubernetes.client.informer.SharedIndexInformer; -import io.kubernetes.client.informer.SharedInformerFactory; -import io.kubernetes.client.informer.cache.Lister; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.JSON; -import io.kubernetes.client.openapi.models.V1ConfigMap; -import io.kubernetes.client.openapi.models.V1ConfigMapList; -import io.kubernetes.client.openapi.models.V1Endpoints; -import io.kubernetes.client.openapi.models.V1EndpointsList; -import io.kubernetes.client.openapi.models.V1ListMeta; -import io.kubernetes.client.openapi.models.V1ObjectMeta; -import io.kubernetes.client.openapi.models.V1Pod; -import io.kubernetes.client.openapi.models.V1PodList; -import io.kubernetes.client.util.ClientBuilder; -import io.kubernetes.client.util.generic.GenericKubernetesApi; -import java.util.Collections; -import java.util.concurrent.Semaphore; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; - -@SpringBootTest(properties = "spring.cloud.config.import-check.enabled=false", webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class Sandbox { - - public static class CountRequestAction extends PostServeAction { - @Override - public String getName() { - return "semaphore"; - } - - @Override - public void doAction(ServeEvent serveEvent, Admin admin, Parameters parameters) { - Semaphore count = (Semaphore) parameters.get("semaphore"); - count.release(); - } - } - - @RegisterExtension - static WireMockExtension apiServer = - WireMockExtension.newInstance() - .options(options().dynamicPort().extensions(new CountRequestAction())) - .build(); - - @SpringBootApplication - static class App { - - @Bean - public ApiClient testingApiClient() { - return new ClientBuilder().setBasePath("http://localhost:" + apiServer.getPort()).build(); - } - - @Bean - public SharedIndexInformer podInformer( - ApiClient apiClient, SharedInformerFactory sharedInformerFactory) { - GenericKubernetesApi genericApi = - new GenericKubernetesApi<>(V1Pod.class, V1PodList.class, "", "v1", "pods", apiClient); - return sharedInformerFactory.sharedIndexInformerFor(genericApi, V1Pod.class, 0); - } - - @Bean - public SharedIndexInformer configMapInformer( - ApiClient apiClient, SharedInformerFactory sharedInformerFactory) { - GenericKubernetesApi genericApi = - new GenericKubernetesApi<>( - V1ConfigMap.class, V1ConfigMapList.class, "", "v1", "configmaps", apiClient); - return sharedInformerFactory.sharedIndexInformerFor( - genericApi, V1ConfigMap.class, 0, "default"); - } - - @Bean - SharedIndexInformer endpointsSharedIndexInformer(SharedInformerFactory sharedInformerFactory, - ApiClient apiClient) { - - GenericKubernetesApi endpointsApi = new GenericKubernetesApi<>(V1Endpoints.class, - V1EndpointsList.class, "", "v1", "endpoints", apiClient); - - return sharedInformerFactory.sharedIndexInformerFor(endpointsApi, V1Endpoints.class, 0L, - "default"); - } - - @Bean - @ConditionalOnMissingBean - SharedInformerFactory sharedInformerFactory(ApiClient client) { - return new SharedInformerFactory(client); - } - } - - @Autowired private SharedInformerFactory informerFactory; - - @Autowired private SharedIndexInformer podInformer; - - @Autowired private SharedIndexInformer configMapInformer; - - @Test - void informerInjection() throws InterruptedException { - assertThat(podInformer).isNotNull(); - assertThat(configMapInformer).isNotNull(); - - Semaphore getCount = new Semaphore(2); - Semaphore watchCount = new Semaphore(2); - Parameters getParams = new Parameters(); - Parameters watchParams = new Parameters(); - getParams.put("semaphore", getCount); - watchParams.put("semaphore", watchCount); - - V1Pod foo1 = - new V1Pod().kind("Pod").metadata(new V1ObjectMeta().namespace("default").name("foo1")); - V1ConfigMap bar1 = - new V1ConfigMap() - .kind("ConfigMap") - .metadata(new V1ObjectMeta().namespace("default").name("bar1")); - - apiServer.stubFor( - get(urlMatching("^/api/v1/pods.*")) - .withPostServeAction("semaphore", getParams) - .withQueryParam("watch", equalTo("false")) - .willReturn( - aResponse() - .withStatus(200) - .withBody( - JSON.serialize( - new V1PodList() - .metadata(new V1ListMeta().resourceVersion("0")) - .items(Collections.singletonList(foo1)))))); - apiServer.stubFor( - get(urlMatching("^/api/v1/pods.*")) - .withPostServeAction("semaphore", watchParams) - .withQueryParam("watch", equalTo("true")) - .willReturn(aResponse().withStatus(200).withBody("{}"))); - - apiServer.stubFor( - get(urlMatching("^/api/v1/namespaces/default/configmaps.*")) - .withPostServeAction("semaphore", getParams) - .withQueryParam("watch", equalTo("false")) - .willReturn( - aResponse() - .withStatus(200) - .withBody( - JSON.serialize( - new V1ConfigMapList() - .metadata(new V1ListMeta().resourceVersion("0")) - .items(Collections.singletonList(bar1)))))); - apiServer.stubFor( - get(urlMatching("^/api/v1/namespaces/default/configmaps.*")) - .withPostServeAction("semaphore", watchParams) - .withQueryParam("watch", equalTo("true")) - .willReturn(aResponse().withStatus(200).withBody("{}"))); - - - apiServer.stubFor( - WireMock.get(WireMock.urlMatching("^/api/v1/namespaces/default/endpoints.*")) - //.withQueryParam("watch", WireMock.equalTo("false")) - .willReturn( - WireMock.aResponse() - .withStatus(200) - .withBody( - JSON.serialize( - new V1EndpointsList().addItemsItem(new V1Endpoints()) - )))); - - // These will be released for each web call above. - getCount.acquire(2); - watchCount.acquire(2); - - informerFactory.startAllRegisteredInformers(); - - // Wait for the GETs to complete and the watches to start. - getCount.acquire(2); - watchCount.acquire(2); - - apiServer.verify( - 1, - getRequestedFor(urlPathEqualTo("/api/v1/pods")).withQueryParam("watch", equalTo("false"))); - - apiServer.verify( - getRequestedFor(urlPathEqualTo("/api/v1/pods")).withQueryParam("watch", equalTo("true"))); - - apiServer.verify( - 1, - getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps")) - .withQueryParam("watch", equalTo("false"))); - - apiServer.verify( - getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps")) - .withQueryParam("watch", equalTo("true"))); - - assertThat(new Lister<>(podInformer.getIndexer()).list()).hasSize(1); - assertThat(new Lister<>(configMapInformer.getIndexer()).list()).hasSize(1); - } - -}