loadBalancerClientFactory;
+
+ @BeforeAll
+ static void beforeAll() {
+
+ wireMockServer = new WireMockServer(options().dynamicPort());
+ wireMockServer.start();
+ WireMock.configureFor("localhost", wireMockServer.port());
+ mockWatchers();
+
+ serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
+ serviceAMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_A_PORT);
+
+ serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
+ serviceBMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_B_PORT);
+
+ serviceCMockServer = new WireMockServer(SERVICE_C_PORT);
+ serviceCMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_C_PORT);
+
+ // we mock host creation so that it becomes something like : localhost:8888
+ // then wiremock can catch this request, and we can assert for the result
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "c", "cluster.local"))
+ .thenReturn("localhost");
+
+ ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
+ clientUtils = mockStatic(KubernetesClientUtils.class);
+ clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ wireMockServer.stop();
+ serviceAMockServer.stop();
+ serviceBMockServer.stop();
+ serviceCMockServer.stop();
+ MOCKED_STATIC.close();
+ clientUtils.close();
+ }
+
+ /**
+ *
+ * - my-service is present in 'a' namespace
+ * - my-service is present in 'b' namespace
+ * - my-service is present in 'c' namespace
+ * - we enable search in selective namespaces [a, b]
+ * - load balancer mode is 'POD'
+ *
+ * - as such, only service in namespace a and b are load balanced
+ * - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
+ *
+ */
+ @Test
+ void test() {
+
+ serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
+
+ serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
+
+ serviceCMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-c-reached").withStatus(200)));
+
+ String firstCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+
+ String secondCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+
+ // since selective namespaces is a Set, we need to be careful with assertion order
+ if (firstCallResult.equals("service-a-reached")) {
+ Assertions.assertThat(secondCallResult).isEqualTo("service-b-reached");
+ }
+ else {
+ Assertions.assertThat(firstCallResult).isEqualTo("service-b-reached");
+ Assertions.assertThat(secondCallResult).isEqualTo("service-a-reached");
+ }
+
+ CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
+ .getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
+ Assertions.assertThat(supplier.getDelegate().getClass())
+ .isSameAs(DiscoveryClientServiceInstanceListSupplier.class);
+ }
+
+ private static void mockWatchers() {
+ V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
+ V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
+ V1Service serviceC = Util.service("c", "my-service", SERVICE_C_PORT);
+
+ V1ServiceList serviceListA = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceA).build();
+ V1ServiceList serviceListB = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceB).build();
+ V1ServiceList serviceListC = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceC).build();
+
+ Util.servicesInNamespacePodMode(wireMockServer, serviceListA, "a");
+ Util.servicesInNamespacePodMode(wireMockServer, serviceListB, "b");
+ Util.servicesInNamespacePodMode(wireMockServer, serviceListC, "c");
+
+ V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
+ V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
+ V1Endpoints endpointsC = Util.endpoints("c", "my-service", SERVICE_C_PORT, "127.0.0.1");
+
+ V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsA).build();
+ V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsB).build();
+ V1EndpointsList endpointsListC = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsC).build();
+
+ Util.endpointsInNamespacePodMode(wireMockServer, endpointsListA, "a");
+ Util.endpointsInNamespacePodMode(wireMockServer, endpointsListB, "b");
+ Util.endpointsInNamespacePodMode(wireMockServer, endpointsListC, "c");
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SpecificNamespaceTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SpecificNamespaceTest.java
new file mode 100644
index 00000000..c24400c6
--- /dev/null
+++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SpecificNamespaceTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.pod;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsList;
+import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
+import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1ServiceList;
+import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
+import io.kubernetes.client.util.ClientBuilder;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
+import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
+import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
+import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static org.mockito.Mockito.mockStatic;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES",
+ "spring.cloud.kubernetes.discovery.all-namespaces=false", "spring.cloud.kubernetes.client.namespace=a" },
+ classes = { LoadBalancerConfiguration.class, Configuration.class })
+class SpecificNamespaceTest {
+
+ private static final String SERVICE_A_URL = "http://my-service";
+
+ private static final int SERVICE_A_PORT = 8888;
+
+ private static final int SERVICE_B_PORT = 8889;
+
+ private static WireMockServer wireMockServer;
+
+ private static WireMockServer serviceAMockServer;
+
+ private static WireMockServer serviceBMockServer;
+
+ private static final MockedStatic MOCKED_STATIC = Mockito
+ .mockStatic(KubernetesServiceInstanceMapper.class);
+
+ private static MockedStatic clientUtils;
+
+ @Autowired
+ private WebClient.Builder builder;
+
+ @Autowired
+ private ObjectProvider loadBalancerClientFactory;
+
+ @BeforeAll
+ static void beforeAll() {
+
+ wireMockServer = new WireMockServer(options().dynamicPort());
+ wireMockServer.start();
+ WireMock.configureFor("localhost", wireMockServer.port());
+ mockWatchers();
+
+ serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
+ serviceAMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_A_PORT);
+
+ serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
+ serviceBMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_B_PORT);
+
+ // we mock host creation so that it becomes something like : localhost:8888
+ // then wiremock can catch this request, and we can assert for the result
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
+ .thenReturn("localhost");
+
+ ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
+ // we need to not mock 'getApplicationNamespace'
+ clientUtils = mockStatic(KubernetesClientUtils.class, Mockito.CALLS_REAL_METHODS);
+ clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ wireMockServer.stop();
+ serviceAMockServer.stop();
+ serviceBMockServer.stop();
+ MOCKED_STATIC.close();
+ clientUtils.close();
+ }
+
+ /**
+ *
+ * - my-service is present in 'a' namespace
+ * - my-service is present in 'b' namespace
+ * - we enable search in namespace 'a'
+ * - load balancer mode is 'POD'
+ *
+ * - as such, only my-service in namespace a is load balanced
+ * - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
+ *
+ */
+ @Test
+ void test() {
+
+ serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
+
+ serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
+
+ String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+ Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
+
+ CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
+ .getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
+ Assertions.assertThat(supplier.getDelegate().getClass())
+ .isSameAs(DiscoveryClientServiceInstanceListSupplier.class);
+ }
+
+ private static void mockWatchers() {
+ V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
+ V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
+
+ V1ServiceList serviceListA = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceA).build();
+ V1ServiceList serviceListB = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceB).build();
+
+ Util.servicesInNamespacePodMode(wireMockServer, serviceListA, "a");
+ Util.servicesInNamespacePodMode(wireMockServer, serviceListB, "b");
+
+ V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
+ V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
+
+ V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsA).build();
+ V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsB).build();
+
+ Util.endpointsInNamespacePodMode(wireMockServer, endpointsListA, "a");
+ Util.endpointsInNamespacePodMode(wireMockServer, endpointsListB, "b");
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/AllNamespacesTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/AllNamespacesTest.java
new file mode 100644
index 00000000..27ec8b26
--- /dev/null
+++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/AllNamespacesTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.service;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsList;
+import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
+import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1ServiceList;
+import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
+import io.kubernetes.client.util.ClientBuilder;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
+import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
+import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
+import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
+import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static org.mockito.Mockito.mockStatic;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(
+ properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE", "spring.main.cloud-platform=KUBERNETES",
+ "spring.cloud.kubernetes.discovery.all-namespaces=true" },
+ classes = { LoadBalancerConfiguration.class, Configuration.class })
+class AllNamespacesTest {
+
+ private static final String SERVICE_A_URL = "http://service-a";
+
+ private static final String SERVICE_B_URL = "http://service-b";
+
+ private static final int SERVICE_A_PORT = 8888;
+
+ private static final int SERVICE_B_PORT = 8889;
+
+ private static WireMockServer wireMockServer;
+
+ private static WireMockServer serviceAMockServer;
+
+ private static WireMockServer serviceBMockServer;
+
+ private static MockedStatic clientUtils;
+
+ private static final MockedStatic MOCKED_STATIC = Mockito
+ .mockStatic(KubernetesServiceInstanceMapper.class);
+
+ @Autowired
+ private WebClient.Builder builder;
+
+ @Autowired
+ private ObjectProvider loadBalancerClientFactory;
+
+ @BeforeAll
+ static void beforeAll() {
+
+ wireMockServer = new WireMockServer(options().dynamicPort().notifier(new ConsoleNotifier(true)));
+ wireMockServer.start();
+ WireMock.configureFor("localhost", wireMockServer.port());
+ mockWatchers();
+
+ serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
+ serviceAMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_A_PORT);
+
+ serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
+ serviceBMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_B_PORT);
+
+ // we mock host creation so that it becomes something like : localhost:8888
+ // then wiremock can catch this request, and we can assert for the result
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("service-a", "a", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("service-b", "b", "cluster.local"))
+ .thenReturn("localhost");
+
+ ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
+ clientUtils = mockStatic(KubernetesClientUtils.class);
+ clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
+
+ }
+
+ @AfterAll
+ static void afterAll() {
+ wireMockServer.stop();
+ serviceAMockServer.stop();
+ serviceBMockServer.stop();
+ MOCKED_STATIC.close();
+ clientUtils.close();
+ }
+
+ /**
+ *
+ * - service-a is present in namespace a with exposed port 8888
+ * - service-b is present in namespace b with exposed port 8889
+ * - we make two calls to them via the load balancer
+ *
+ */
+ @Test
+ void test() {
+
+ serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
+
+ serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
+
+ String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+ Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
+
+ String serviceBResult = builder.baseUrl(SERVICE_B_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+ Assertions.assertThat(serviceBResult).isEqualTo("service-b-reached");
+
+ CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
+ .getIfAvailable().getProvider("service-a", ServiceInstanceListSupplier.class).getIfAvailable();
+ Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class);
+
+ }
+
+ private static void mockWatchers() {
+ V1Service serviceA = Util.service("a", "service-a", SERVICE_A_PORT);
+ V1ServiceList serviceListA = new V1ServiceListBuilder().withKind("V1ServiceList")
+ .withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build())
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceA).build();
+ Util.servicesServiceMode(wireMockServer, serviceListA, "service-a");
+
+ V1Service serviceB = Util.service("b", "service-b", SERVICE_B_PORT);
+ V1ServiceList serviceListB = new V1ServiceListBuilder().withKind("V1ServiceList")
+ .withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build())
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceB).build();
+ Util.servicesServiceMode(wireMockServer, serviceListB, "service-b");
+
+ V1Endpoints endpointsA = Util.endpoints("a", "service-a", SERVICE_A_PORT, "127.0.0.1");
+ V1EndpointsList endpointsListA = new V1EndpointsListBuilder().withKind("V1EndpointsList")
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsA).build();
+ Util.endpointsServiceMode(wireMockServer, endpointsListA, "service-a");
+
+ V1Endpoints endpointsB = Util.endpoints("b", "service-b", SERVICE_B_PORT, "127.0.0.1");
+ V1EndpointsList endpointsListB = new V1EndpointsListBuilder().withKind("V1EndpointsList")
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsB).build();
+ Util.endpointsServiceMode(wireMockServer, endpointsListB, "service-b");
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/SelectiveNamespacesTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/SelectiveNamespacesTest.java
new file mode 100644
index 00000000..cc23253b
--- /dev/null
+++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/SelectiveNamespacesTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.service;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsList;
+import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
+import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1ServiceList;
+import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
+import io.kubernetes.client.util.ClientBuilder;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
+import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
+import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
+import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
+import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static org.mockito.Mockito.mockStatic;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE",
+ "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.discovery.all-namespaces=false",
+ "spring.cloud.kubernetes.discovery.namespaces.[0]=a", "spring.cloud.kubernetes.discovery.namespaces.[1]=b" },
+ classes = { LoadBalancerConfiguration.class, Configuration.class })
+class SelectiveNamespacesTest {
+
+ private static final String MY_SERVICE_URL = "http://my-service";
+
+ private static final int SERVICE_A_PORT = 8887;
+
+ private static final int SERVICE_B_PORT = 8888;
+
+ private static final int SERVICE_C_PORT = 8889;
+
+ private static WireMockServer wireMockServer;
+
+ private static WireMockServer serviceAMockServer;
+
+ private static WireMockServer serviceBMockServer;
+
+ private static WireMockServer serviceCMockServer;
+
+ private static final MockedStatic MOCKED_STATIC = Mockito
+ .mockStatic(KubernetesServiceInstanceMapper.class);
+
+ private static MockedStatic clientUtils;
+
+ @Autowired
+ private WebClient.Builder builder;
+
+ @Autowired
+ private ObjectProvider loadBalancerClientFactory;
+
+ @BeforeAll
+ static void beforeAll() {
+
+ wireMockServer = new WireMockServer(options().dynamicPort());
+ wireMockServer.start();
+ WireMock.configureFor("localhost", wireMockServer.port());
+ mockWatchers();
+
+ serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
+ serviceAMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_A_PORT);
+
+ serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
+ serviceBMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_B_PORT);
+
+ serviceCMockServer = new WireMockServer(SERVICE_C_PORT);
+ serviceCMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_C_PORT);
+
+ // we mock host creation so that it becomes something like : localhost:8888
+ // then wiremock can catch this request, and we can assert for the result
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "c", "cluster.local"))
+ .thenReturn("localhost");
+
+ ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
+ clientUtils = mockStatic(KubernetesClientUtils.class);
+ clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ wireMockServer.stop();
+ serviceAMockServer.stop();
+ serviceBMockServer.stop();
+ serviceCMockServer.stop();
+ MOCKED_STATIC.close();
+ clientUtils.close();
+ }
+
+ /**
+ *
+ * - my-service is present in 'a' namespace
+ * - my-service is present in 'b' namespace
+ * - my-service is present in 'c' namespace
+ * - we enable search in selective namespaces [a, b]
+ * - load balancer mode is 'POD'
+ *
+ * - as such, only service in namespace a and b are load balanced
+ * - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
+ *
+ */
+ @Test
+ void test() {
+
+ serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
+
+ serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
+
+ serviceCMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-c-reached").withStatus(200)));
+
+ String firstCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+
+ String secondCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+
+ // since selective namespaces is a Set, we need to be careful with assertion order
+ if (firstCallResult.equals("service-a-reached")) {
+ Assertions.assertThat(secondCallResult).isEqualTo("service-b-reached");
+ }
+ else {
+ Assertions.assertThat(firstCallResult).isEqualTo("service-b-reached");
+ Assertions.assertThat(secondCallResult).isEqualTo("service-a-reached");
+ }
+
+ CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
+ .getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
+ Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class);
+ }
+
+ private static void mockWatchers() {
+ V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
+ V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
+ V1Service serviceC = Util.service("c", "my-service", SERVICE_C_PORT);
+
+ V1ServiceList serviceListA = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceA).build();
+ V1ServiceList serviceListB = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceB).build();
+ V1ServiceList serviceListC = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceC).build();
+
+ Util.servicesInNamespaceServiceMode(wireMockServer, serviceListA, "a", "my-service");
+ Util.servicesInNamespaceServiceMode(wireMockServer, serviceListB, "b", "my-service");
+ Util.servicesInNamespaceServiceMode(wireMockServer, serviceListC, "c", "my-service");
+
+ V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
+ V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
+ V1Endpoints endpointsC = Util.endpoints("c", "my-service", SERVICE_C_PORT, "127.0.0.1");
+
+ V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsA).build();
+ V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsB).build();
+ V1EndpointsList endpointsListC = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsC).build();
+
+ Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListA, "a", "my-service");
+ Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListB, "b", "my-service");
+ Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListC, "c", "my-service");
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/SpecificNamespaceTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/SpecificNamespaceTest.java
new file mode 100644
index 00000000..1d0675bf
--- /dev/null
+++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/service/SpecificNamespaceTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.service;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsList;
+import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
+import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1ServiceList;
+import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
+import io.kubernetes.client.util.ClientBuilder;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
+import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
+import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
+import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
+import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static org.mockito.Mockito.mockStatic;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
+import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
+
+/**
+ * @author wind57
+ */
+@SpringBootTest(
+ properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE", "spring.main.cloud-platform=KUBERNETES",
+ "spring.cloud.kubernetes.discovery.all-namespaces=false",
+ "spring.cloud.kubernetes.client.namespace=a" },
+ classes = { LoadBalancerConfiguration.class, Configuration.class })
+class SpecificNamespaceTest {
+
+ private static final String SERVICE_A_URL = "http://my-service";
+
+ private static final int SERVICE_A_PORT = 8888;
+
+ private static final int SERVICE_B_PORT = 8889;
+
+ private static WireMockServer wireMockServer;
+
+ private static WireMockServer serviceAMockServer;
+
+ private static WireMockServer serviceBMockServer;
+
+ private static final MockedStatic MOCKED_STATIC = Mockito
+ .mockStatic(KubernetesServiceInstanceMapper.class);
+
+ private static MockedStatic clientUtils;
+
+ @Autowired
+ private WebClient.Builder builder;
+
+ @Autowired
+ private ObjectProvider loadBalancerClientFactory;
+
+ @BeforeAll
+ static void beforeAll() {
+
+ wireMockServer = new WireMockServer(options().dynamicPort());
+ wireMockServer.start();
+ WireMock.configureFor("localhost", wireMockServer.port());
+ mockWatchers();
+
+ serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
+ serviceAMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_A_PORT);
+
+ serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
+ serviceBMockServer.start();
+ WireMock.configureFor("localhost", SERVICE_B_PORT);
+
+ // we mock host creation so that it becomes something like : localhost:8888
+ // then wiremock can catch this request, and we can assert for the result
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
+ .thenReturn("localhost");
+
+ MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
+ .thenReturn("localhost");
+
+ ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
+ // we need to not mock 'getApplicationNamespace'
+ clientUtils = mockStatic(KubernetesClientUtils.class, Mockito.CALLS_REAL_METHODS);
+ clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ wireMockServer.stop();
+ serviceAMockServer.stop();
+ serviceBMockServer.stop();
+ MOCKED_STATIC.close();
+ clientUtils.close();
+ }
+
+ /**
+ *
+ * - my-service is present in 'a' namespace
+ * - my-service is present in 'b' namespace
+ * - we enable search in namespace 'a'
+ * - load balancer mode is 'POD'
+ *
+ * - as such, only my-service in namespace a is load balanced
+ * - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
+ *
+ */
+ @Test
+ void test() {
+
+ serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
+
+ serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
+ .willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
+
+ String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
+ .bodyToMono(String.class).block();
+ Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
+
+ CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
+ .getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
+ Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class);
+ }
+
+ private static void mockWatchers() {
+ V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
+ V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
+
+ V1ServiceList serviceListA = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceA).build();
+ V1ServiceList serviceListB = new V1ServiceListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(serviceB).build();
+
+ Util.servicesInNamespaceServiceMode(wireMockServer, serviceListA, "a", "my-service");
+ Util.servicesInNamespaceServiceMode(wireMockServer, serviceListB, "b", "my-service");
+
+ V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
+ V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
+
+ V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsA).build();
+ V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
+ .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
+ .withItems(endpointsB).build();
+
+ Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListA, "a", "my-service");
+ Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListB, "b", "my-service");
+ }
+
+}
diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-client-loadbalancer/src/test/resources/logback-test.xml
index 7c31c5ab..7b3a49d3 100644
--- a/spring-cloud-kubernetes-client-loadbalancer/src/test/resources/logback-test.xml
+++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/resources/logback-test.xml
@@ -3,4 +3,6 @@
+
+
diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml
index b6fb4499..d4dd4696 100644
--- a/spring-cloud-kubernetes-integration-tests/pom.xml
+++ b/spring-cloud-kubernetes-integration-tests/pom.xml
@@ -128,9 +128,6 @@
spring-cloud-kubernetes-k8s-client-configuration-watcher
-
- spring-cloud-kubernetes-k8s-client-loadbalancer
-
spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/KubernetesClientLoadBalancerApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/KubernetesClientLoadBalancerApplication.java
deleted file mode 100644
index 9b1f6144..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/KubernetesClientLoadBalancerApplication.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2013-2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.cloud.kubernetes.k8s.client.loadbalancer;
-
-import java.util.Map;
-
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
-import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
-import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
-import org.springframework.http.HttpMethod;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.reactive.function.client.WebClient;
-
-/**
- * @author Ryan Baxter
- */
-
-@SpringBootApplication
-@RestController
-class KubernetesClientLoadBalancerApplication {
-
- private static final String URL = "http://service-wiremock/__admin/mappings";
-
- private final WebClient.Builder client;
-
- private final ObjectProvider loadBalancerClientFactory;
-
- KubernetesClientLoadBalancerApplication(WebClient.Builder client,
- ObjectProvider loadBalancerClientFactory) {
- this.client = client;
- this.loadBalancerClientFactory = loadBalancerClientFactory;
- }
-
- public static void main(String[] args) {
- SpringApplication.run(KubernetesClientLoadBalancerApplication.class, args);
- }
-
- @GetMapping("/loadbalancer-it/service")
- @SuppressWarnings("unchecked")
- Map greeting() {
- return (Map) client.baseUrl(URL).build().method(HttpMethod.GET).retrieve().bodyToMono(Map.class)
- .block();
- }
-
- @GetMapping("/loadbalancer-it/supplier")
- String supplier() {
- ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getIfAvailable()
- .getProvider("service-wiremock", ServiceInstanceListSupplier.class).getIfAvailable();
- if (supplier instanceof CachingServiceInstanceListSupplier cachingSupplier) {
- return cachingSupplier.getDelegate().getClass().getSimpleName();
- }
- return supplier.getClass().getSimpleName();
- }
-
-}
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/KubernetesClientLoadBalancerConfiguration.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/KubernetesClientLoadBalancerConfiguration.java
deleted file mode 100644
index 2c029aa3..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/KubernetesClientLoadBalancerConfiguration.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2013-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.cloud.kubernetes.k8s.client.loadbalancer;
-
-import org.springframework.cloud.client.loadbalancer.LoadBalanced;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.reactive.function.client.WebClient;
-
-/**
- * @author wind57
- */
-@Configuration
-class KubernetesClientLoadBalancerConfiguration {
-
- @Bean
- @LoadBalanced
- WebClient.Builder client() {
- return WebClient.builder();
- }
-
-}
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/resources/application.yaml
deleted file mode 100644
index 98b2266e..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/main/resources/application.yaml
+++ /dev/null
@@ -1 +0,0 @@
-debug: true
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/LoadBalancerIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/LoadBalancerIT.java
deleted file mode 100644
index 2911bfe4..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/k8s/client/loadbalancer/LoadBalancerIT.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2013-2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.cloud.kubernetes.k8s.client.loadbalancer;
-
-import java.util.Map;
-
-import io.kubernetes.client.openapi.models.V1Deployment;
-import io.kubernetes.client.openapi.models.V1Ingress;
-import io.kubernetes.client.openapi.models.V1Service;
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-import org.testcontainers.k3s.K3sContainer;
-import reactor.netty.http.client.HttpClient;
-
-import org.springframework.boot.test.json.BasicJsonTester;
-import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
-import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
-import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
-import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;
-import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.client.reactive.ReactorClientHttpConnector;
-import org.springframework.web.reactive.function.client.WebClient;
-
-import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithMerge;
-
-/**
- * @author Ryan Baxter
- */
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-class LoadBalancerIT {
-
- private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(LoadBalancerIT.class);
-
- private static final String BODY_FOR_MERGE = """
- {
- "spec": {
- "template": {
- "spec": {
- "containers": [{
- "name": "spring-cloud-kubernetes-k8s-client-loadbalancer",
- "env": [
- {
- "name": "SPRING_CLOUD_KUBERNETES_LOADBALANCER_MODE",
- "value": "SERVICE"
- }
- ]
- }]
- }
- }
- }
- }
- """;
-
- private static final Map POD_LABELS = Map.of("app",
- "spring-cloud-kubernetes-k8s-client-loadbalancer");
-
- private static final String SERVICE_URL = "http://localhost:80/loadbalancer-it/service";
-
- private static final String SERVICE_INSTANCE_LIST_SUPPLIER_URL = "http://localhost:80/loadbalancer-it/supplier";
-
- private static final String SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME = "spring-cloud-kubernetes-k8s-client-loadbalancer";
-
- private static final String NAMESPACE = "default";
-
- private static final K3sContainer K3S = Commons.container();
-
- private static Util util;
-
- @BeforeAll
- static void beforeAll() throws Exception {
- K3S.start();
- Commons.validateImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
- Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
- util = new Util(K3S);
- util.setUp(NAMESPACE);
- loadbalancerIt(Phase.CREATE);
- }
-
- @AfterAll
- static void afterAll() throws Exception {
- loadbalancerIt(Phase.DELETE);
- Commons.cleanUp(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
- Commons.systemPrune();
- }
-
- @BeforeEach
- void setup() {
- util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE, false);
- }
-
- @AfterEach
- void afterEach() {
- util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE, false);
- }
-
- @Test
- @Order(1)
- void testLoadBalancerPodMode() {
- testLoadBalancer(true);
- }
-
- @Test
- @Order(2)
- void testLoadBalancerServiceMode() {
- patchForServiceMode();
- testLoadBalancer(false);
- }
-
- private void testLoadBalancer(boolean podMode) {
-
- WebClient.Builder builder = builder();
- WebClient serviceClient = builder.baseUrl(SERVICE_URL).build();
-
- String result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block();
- Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathArrayValue("$.mappings").isEmpty();
- Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathNumberValue("$.meta.total")
- .isEqualTo(0);
-
- WebClient.Builder supplierBuilder = builder();
- WebClient supplierServiceClient = supplierBuilder.baseUrl(SERVICE_INSTANCE_LIST_SUPPLIER_URL).build();
- String supplierResult = supplierServiceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
- .block();
- if (podMode) {
- Assertions.assertThat(supplierResult)
- .isEqualTo(DiscoveryClientServiceInstanceListSupplier.class.getSimpleName());
- }
- else {
- Assertions.assertThat(supplierResult).isEqualTo(KubernetesClientServicesListSupplier.class.getSimpleName());
- }
- }
-
- private static void loadbalancerIt(Phase phase) {
- V1Deployment deployment = (V1Deployment) util
- .yaml("spring-cloud-kubernetes-k8s-client-loadbalancer-deployment.yaml");
- V1Service service = (V1Service) util.yaml("spring-cloud-kubernetes-k8s-client-loadbalancer-service.yaml");
- V1Ingress ingress = (V1Ingress) util.yaml("spring-cloud-kubernetes-k8s-client-loadbalancer-ingress.yaml");
-
- if (phase.equals(Phase.CREATE)) {
- util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
- }
- else if (phase.equals(Phase.DELETE)) {
- util.deleteAndWait(NAMESPACE, deployment, service, ingress);
- }
- }
-
- private WebClient.Builder builder() {
- return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
- }
-
- private static void patchForServiceMode() {
- patchWithMerge("spring-cloud-kubernetes-k8s-client-loadbalancer", LoadBalancerIT.NAMESPACE, BODY_FOR_MERGE,
- POD_LABELS);
- }
-
-}
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/logback-test.xml
deleted file mode 100644
index 9e284876..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
-
-
-
-
-
-
-
-
-
-
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-deployment.yaml
deleted file mode 100644
index a0aab26b..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-deployment.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: spring-cloud-kubernetes-k8s-client-loadbalancer
-spec:
- selector:
- matchLabels:
- app: spring-cloud-kubernetes-k8s-client-loadbalancer
- template:
- metadata:
- labels:
- app: spring-cloud-kubernetes-k8s-client-loadbalancer
- spec:
- serviceAccountName: spring-cloud-kubernetes-serviceaccount
- containers:
- - name: spring-cloud-kubernetes-k8s-client-loadbalancer
- env:
- - name: SPRING_CLOUD_KUBERNETES_LOADBALANCER_MODE
- value: POD
- image: docker.io/springcloud/spring-cloud-kubernetes-k8s-client-loadbalancer
- imagePullPolicy: IfNotPresent
- readinessProbe:
- httpGet:
- port: 8080
- path: /actuator/health/readiness
- livenessProbe:
- httpGet:
- port: 8080
- path: /actuator/health/liveness
- ports:
- - containerPort: 8080
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-ingress.yaml
deleted file mode 100644
index 35c5d8da..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-ingress.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: it-ingress
- namespace: default
-spec:
- rules:
- - http:
- paths:
- - path: /loadbalancer-it/
- pathType: Prefix
- backend:
- service:
- name: spring-cloud-kubernetes-k8s-client-loadbalancer
- port:
- number: 8080
-
-
-
diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-service.yaml
deleted file mode 100644
index 6fe86a0a..00000000
--- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-k8s-client-loadbalancer-service.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
- labels:
- app: spring-cloud-kubernetes-k8s-client-loadbalancer
- name: spring-cloud-kubernetes-k8s-client-loadbalancer
-spec:
- ports:
- - name: http
- port: 8080
- targetPort: 8080
- selector:
- app: spring-cloud-kubernetes-k8s-client-loadbalancer
- type: ClusterIP