From 8794572031363054d20207785708924505473489 Mon Sep 17 00:00:00 2001 From: wind57 Date: Tue, 26 Aug 2025 23:08:37 +0300 Subject: [PATCH 1/5] started work Signed-off-by: wind57 --- .../KubernetesClientServicesListSupplier.java | 50 ++++++------ .../Fabric8ServicesListSupplier.java | 76 ++++++++++--------- .../fabric8/loadbalancer/it/Util.java | 27 ------- .../fabric8/loadbalancer/it/mode/App.java | 29 +++++++ .../it/mode/LoadBalancerConfiguration.java | 33 ++++++++ .../it/mode/pod/AllNamespacesTest.java | 13 ++-- .../it/mode/pod/SelectiveNamespacesTest.java | 13 ++-- .../it/mode/pod/SpecificNamespaceTest.java | 13 ++-- .../it/mode/service/AllNamespacesTest.java | 14 ++-- .../mode/service/SelectiveNamespacesTest.java | 13 ++-- .../mode/service/SpecificNamespaceTest.java | 13 ++-- 11 files changed, 163 insertions(+), 131 deletions(-) create mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java create mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplier.java b/spring-cloud-kubernetes-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplier.java index 1375081301..9955876c67 100644 --- a/spring-cloud-kubernetes-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplier.java +++ b/spring-cloud-kubernetes-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplier.java @@ -57,32 +57,34 @@ public KubernetesClientServicesListSupplier(Environment environment, @Override public Flux> get() { - List result = new ArrayList<>(); - String serviceName = getServiceId(); - LOG.debug(() -> "serviceID : " + serviceName); - - if (discoveryProperties.allNamespaces()) { - LOG.debug(() -> "discovering services in all namespaces"); - List services = services(null, serviceName); - services.forEach(service -> addMappedService(mapper, result, service)); - } - else if (!discoveryProperties.namespaces().isEmpty()) { - List selectiveNamespaces = discoveryProperties.namespaces().stream().sorted().toList(); - LOG.debug(() -> "discovering services in selective namespaces : " + selectiveNamespaces); - selectiveNamespaces.forEach(selectiveNamespace -> { - List services = services(selectiveNamespace, serviceName); + return Flux.defer(() -> { + List result = new ArrayList<>(); + String serviceName = getServiceId(); + LOG.debug(() -> "serviceID : " + serviceName); + + if (discoveryProperties.allNamespaces()) { + LOG.debug(() -> "discovering services in all namespaces"); + List services = services(null, serviceName); services.forEach(service -> addMappedService(mapper, result, service)); - }); - } - else { - String namespace = getApplicationNamespace(null, "loadbalancer-service", kubernetesNamespaceProvider); - LOG.debug(() -> "discovering services in namespace : " + namespace); - List services = services(namespace, serviceName); - services.forEach(service -> addMappedService(mapper, result, service)); - } + } + else if (!discoveryProperties.namespaces().isEmpty()) { + List selectiveNamespaces = discoveryProperties.namespaces().stream().sorted().toList(); + LOG.debug(() -> "discovering services in selective namespaces : " + selectiveNamespaces); + selectiveNamespaces.forEach(selectiveNamespace -> { + List services = services(selectiveNamespace, serviceName); + services.forEach(service -> addMappedService(mapper, result, service)); + }); + } + else { + String namespace = getApplicationNamespace(null, "loadbalancer-service", kubernetesNamespaceProvider); + LOG.debug(() -> "discovering services in namespace : " + namespace); + List services = services(namespace, serviceName); + services.forEach(service -> addMappedService(mapper, result, service)); + } - LOG.debug(() -> "found services : " + result); - return Flux.just(result); + LOG.debug(() -> "found services : " + result); + return Flux.just(result); + }); } private void addMappedService(KubernetesServiceInstanceMapper mapper, List services, diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java index 3ee549e101..764b1f552c 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java @@ -57,51 +57,53 @@ public class Fabric8ServicesListSupplier extends KubernetesServicesListSupplier< @Override public Flux> get() { - List result = new ArrayList<>(); - String serviceName = getServiceId(); - LOG.debug(() -> "serviceID : " + serviceName); + return Flux.defer(() -> { + List result = new ArrayList<>(); + String serviceName = getServiceId(); + LOG.debug(() -> "serviceID : " + serviceName); - if (discoveryProperties.allNamespaces()) { - LOG.debug(() -> "discovering services in all namespaces"); - List services = kubernetesClient.services() - .inAnyNamespace() - .withField("metadata.name", serviceName) - .list() - .getItems(); - services.forEach(service -> addMappedService(mapper, result, service)); - } - else if (!discoveryProperties.namespaces().isEmpty()) { - List selectiveNamespaces = discoveryProperties.namespaces().stream().sorted().toList(); - LOG.debug(() -> "discovering services in selective namespaces : " + selectiveNamespaces); - selectiveNamespaces.forEach(selectiveNamespace -> { - Service service = kubernetesClient.services() - .inNamespace(selectiveNamespace) - .withName(serviceName) - .get(); + if (discoveryProperties.allNamespaces()) { + LOG.debug(() -> "discovering services in all namespaces"); + List services = kubernetesClient.services() + .inAnyNamespace() + .withField("metadata.name", serviceName) + .list() + .getItems(); + services.forEach(service -> addMappedService(mapper, result, service)); + } + else if (!discoveryProperties.namespaces().isEmpty()) { + List selectiveNamespaces = discoveryProperties.namespaces().stream().sorted().toList(); + LOG.debug(() -> "discovering services in selective namespaces : " + selectiveNamespaces); + selectiveNamespaces.forEach(selectiveNamespace -> { + Service service = kubernetesClient.services() + .inNamespace(selectiveNamespace) + .withName(serviceName) + .get(); + if (service != null) { + addMappedService(mapper, result, service); + } + else { + LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " + + selectiveNamespace); + } + }); + } + else { + String namespace = Fabric8Utils.getApplicationNamespace(kubernetesClient, null, "loadbalancer-service", + namespaceProvider); + LOG.debug(() -> "discovering services in namespace : " + namespace); + Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get(); if (service != null) { addMappedService(mapper, result, service); } else { - LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " - + selectiveNamespace); + LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " + namespace); } - }); - } - else { - String namespace = Fabric8Utils.getApplicationNamespace(kubernetesClient, null, "loadbalancer-service", - namespaceProvider); - LOG.debug(() -> "discovering services in namespace : " + namespace); - Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get(); - if (service != null) { - addMappedService(mapper, result, service); - } - else { - LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " + namespace); } - } - LOG.debug(() -> "found services : " + result); - return Flux.just(result); + LOG.debug(() -> "found services : " + result); + return Flux.just(result); + }); } private void addMappedService(KubernetesServiceInstanceMapper mapper, List services, diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/Util.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/Util.java index 03f73c14f5..3a81f8809b 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/Util.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/Util.java @@ -27,13 +27,6 @@ import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.web.reactive.function.client.WebClient; - /** * @author wind57 */ @@ -63,24 +56,4 @@ public static Endpoints endpoints(int port, String host, String namespace) { .build(); } - @TestConfiguration - public static class LoadBalancerConfiguration { - - @Bean - @LoadBalanced - WebClient.Builder client() { - return WebClient.builder(); - } - - } - - @SpringBootApplication - public static class Configuration { - - public static void main(String[] args) { - SpringApplication.run(Configuration.class); - } - - } - } diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java new file mode 100644 index 0000000000..16404a7708 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2025 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.fabric8.loadbalancer.it.mode; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java new file mode 100644 index 0000000000..942903e4b5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2025 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.fabric8.loadbalancer.it.mode; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.client.WebClient; + +@TestConfiguration +public class LoadBalancerConfiguration { + + @Bean + @LoadBalanced + WebClient.Builder client() { + return WebClient.builder(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/AllNamespacesTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/AllNamespacesTest.java index 864a467b3d..2d83b73402 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/AllNamespacesTest.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/AllNamespacesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 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. @@ -30,11 +30,12 @@ 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.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; @@ -43,8 +44,6 @@ import org.springframework.web.reactive.function.client.WebClient; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.LoadBalancerConfiguration; /** * @author wind57 @@ -52,7 +51,7 @@ @SpringBootTest( properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.discovery.all-namespaces=true" }, - classes = { LoadBalancerConfiguration.class, Configuration.class }) + classes = { LoadBalancerConfiguration.class, App.class }) class AllNamespacesTest { private static final String SERVICE_A_URL = "http://service-a"; @@ -69,6 +68,7 @@ class AllNamespacesTest { private static WireMockServer serviceBMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -76,7 +76,7 @@ class AllNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -177,7 +177,6 @@ void test() { Assertions.assertThat(serviceBResult).isEqualTo("service-b-reached"); CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("service-a", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()) diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java index 8576c264ff..14c49f5237 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 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. @@ -30,11 +30,12 @@ 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.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; @@ -43,8 +44,6 @@ import org.springframework.web.reactive.function.client.WebClient; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.LoadBalancerConfiguration; /** * @author wind57 @@ -52,7 +51,7 @@ @SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class SelectiveNamespacesTest { private static final String MY_SERVICE_URL = "http://my-service"; @@ -71,6 +70,7 @@ class SelectiveNamespacesTest { private static WireMockServer serviceCMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -78,7 +78,7 @@ class SelectiveNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -213,7 +213,6 @@ void test() { } CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()) diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SpecificNamespaceTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SpecificNamespaceTest.java index e005d04546..8c02ec452e 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SpecificNamespaceTest.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/SpecificNamespaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 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. @@ -30,11 +30,12 @@ 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.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; @@ -43,15 +44,13 @@ import org.springframework.web.reactive.function.client.WebClient; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration; -import static org.springframework.cloud.kubernetes.fabric8.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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class SpecificNamespaceTest { private static final String SERVICE_A_URL = "http://my-service"; @@ -66,6 +65,7 @@ class SpecificNamespaceTest { private static WireMockServer serviceBMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -73,7 +73,7 @@ class SpecificNamespaceTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -170,7 +170,6 @@ void test() { Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached"); CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()) diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/AllNamespacesTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/AllNamespacesTest.java index 7bb9f0f98a..1653ff12ee 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/AllNamespacesTest.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/AllNamespacesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 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. @@ -30,7 +30,6 @@ 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.boot.test.system.CapturedOutput; @@ -38,6 +37,8 @@ import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.Fabric8ServicesListSupplier; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; @@ -45,8 +46,6 @@ import org.springframework.web.reactive.function.client.WebClient; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.LoadBalancerConfiguration; /** * @author wind57 @@ -54,7 +53,7 @@ @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 }) + classes = { LoadBalancerConfiguration.class, App.class }) @ExtendWith(OutputCaptureExtension.class) class AllNamespacesTest { @@ -72,6 +71,7 @@ class AllNamespacesTest { private static WireMockServer serviceBMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -79,7 +79,7 @@ class AllNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -168,13 +168,11 @@ void test(CapturedOutput output) { Assertions.assertThat(serviceBResult).isEqualTo("service-b-reached"); CachingServiceInstanceListSupplier supplierA = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("service-a", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplierA.getDelegate().getClass()).isSameAs(Fabric8ServicesListSupplier.class); CachingServiceInstanceListSupplier supplierB = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("service-b", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplierB.getDelegate().getClass()).isSameAs(Fabric8ServicesListSupplier.class); diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SelectiveNamespacesTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SelectiveNamespacesTest.java index e0efbc2cbb..491e4b50f9 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SelectiveNamespacesTest.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SelectiveNamespacesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 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. @@ -29,7 +29,6 @@ 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.boot.test.system.CapturedOutput; @@ -37,6 +36,8 @@ import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.Fabric8ServicesListSupplier; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; @@ -44,8 +45,6 @@ import org.springframework.web.reactive.function.client.WebClient; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.LoadBalancerConfiguration; /** * @author wind57 @@ -53,7 +52,7 @@ @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 }) + classes = { LoadBalancerConfiguration.class, App.class }) @ExtendWith(OutputCaptureExtension.class) class SelectiveNamespacesTest { @@ -73,6 +72,7 @@ class SelectiveNamespacesTest { private static WireMockServer serviceCMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -80,7 +80,7 @@ class SelectiveNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -195,7 +195,6 @@ void test(CapturedOutput output) { } CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(Fabric8ServicesListSupplier.class); diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SpecificNamespaceTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SpecificNamespaceTest.java index 4046a159ce..7d96564304 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SpecificNamespaceTest.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/service/SpecificNamespaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * Copyright 2013-2025 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. @@ -29,7 +29,6 @@ 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.boot.test.system.CapturedOutput; @@ -37,6 +36,8 @@ import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.Fabric8ServicesListSupplier; import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; @@ -44,8 +45,6 @@ import org.springframework.web.reactive.function.client.WebClient; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration; -import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.LoadBalancerConfiguration; /** * @author wind57 @@ -54,7 +53,7 @@ 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 }) + classes = { LoadBalancerConfiguration.class, App.class }) @ExtendWith(OutputCaptureExtension.class) class SpecificNamespaceTest { @@ -70,6 +69,7 @@ class SpecificNamespaceTest { private static WireMockServer serviceBMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -77,7 +77,7 @@ class SpecificNamespaceTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -160,7 +160,6 @@ void test(CapturedOutput output) { Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached"); CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(Fabric8ServicesListSupplier.class); From e3af38c56334217475bd958d6ffcd8edc8b0ca9e Mon Sep 17 00:00:00 2001 From: wind57 Date: Fri, 29 Aug 2025 19:17:40 +0300 Subject: [PATCH 2/5] checkstyle Signed-off-by: wind57 --- .../fabric8/loadbalancer/Fabric8ServicesListSupplier.java | 4 ++-- .../cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java | 2 +- .../loadbalancer/it/mode/LoadBalancerConfiguration.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java index 46a6609101..57462019a6 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java @@ -84,13 +84,13 @@ else if (!discoveryProperties.namespaces().isEmpty()) { } else { LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " - + selectiveNamespace); + + selectiveNamespace); } }); } else { String namespace = Fabric8Utils.getApplicationNamespace(kubernetesClient, null, "loadbalancer-service", - namespaceProvider); + namespaceProvider); LOG.debug(() -> "discovering services in namespace : " + namespace); Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get(); if (service != null) { diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java index 16404a7708..f319abc8f1 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/App.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2025 the original author or authors. + * Copyright 2013-present 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. diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java index 942903e4b5..34ec022c3f 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/LoadBalancerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2025 the original author or authors. + * Copyright 2013-present 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. From 58ea29882e31543f60c20979e31de496f01c0a47 Mon Sep 17 00:00:00 2001 From: wind57 Date: Thu, 4 Sep 2025 21:37:14 +0300 Subject: [PATCH 3/5] remove un-used method Signed-off-by: wind57 --- .../integration/tests/commons/Commons.java | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java index 6be976466e..7fcb1c33df 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.TimeUnit; import com.github.dockerjava.api.command.ListImagesCmd; import com.github.dockerjava.api.command.PullImageCmd; @@ -36,7 +35,6 @@ import com.github.dockerjava.api.model.Image; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.assertj.core.api.Assertions; import org.testcontainers.containers.Container; import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; @@ -79,54 +77,6 @@ public static void loadSpringCloudKubernetesImage(String project, K3sContainer c loadImage("springcloud/" + project, pomVersion(), project, container); } - /** - * assert that "left" is present and if so, "right" is not. - */ - public static void assertReloadLogStatements(String left, String right, String appLabel) { - - try { - String appPodName = CONTAINER - .execInContainer("sh", "-c", - "kubectl get pods -l app=" + appLabel + " -o=name --no-headers | tr -d '\n'") - .getStdout(); - LOG.info("appPodName : ->" + appPodName + "<-"); - // we issue a pollDelay to let the logs sync in, otherwise the results are not - // going to be correctly asserted - await().pollDelay(20, TimeUnit.SECONDS) - .pollInterval(Duration.ofSeconds(5)) - .atMost(Duration.ofSeconds(120)) - .until(() -> { - - Container.ExecResult result = CONTAINER.execInContainer("sh", "-c", - "kubectl logs " + appPodName.trim() + "| grep " + "'" + left + "'"); - String error = result.getStderr(); - String ok = result.getStdout(); - - LOG.info("error is : -->" + error + "<--"); - - if (ok != null && !ok.isBlank()) { - - if (!right.isBlank()) { - String notPresent = CONTAINER - .execInContainer("sh", "-c", - "kubectl logs " + appPodName.trim() + "| grep " + "'" + right + "'") - .getStdout(); - - Assertions.assertThat(notPresent).isNullOrEmpty(); - } - - return true; - } - LOG.info("log statement not yet present"); - return false; - }); - } - catch (Exception e) { - throw new RuntimeException(e); - } - - } - /** * create a tar, copy it in the running k3s and load this tar as an image. */ From dd52a859e06ff2fddf1ed20b718ce322dad03053 Mon Sep 17 00:00:00 2001 From: wind57 Date: Tue, 9 Sep 2025 09:37:51 +0300 Subject: [PATCH 4/5] started tests Signed-off-by: wind57 --- .../it/mode/cached/CachedServicesTest.java | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java new file mode 100644 index 0000000000..67bdfe924d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java @@ -0,0 +1,160 @@ +package org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.cached; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.utils.Serialization; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.Fabric8ServicesListSupplier; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; +import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; +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; + +/** + * @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, App.class }) +@ExtendWith(OutputCaptureExtension.class) +class CachedServicesTest { + + private static final String MY_SERVICE_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; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private WebClient.Builder builder; + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + serviceAMockServer = new WireMockServer(SERVICE_A_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_A_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"); + + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, "http://localhost:" + wireMockServer.port()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + } + + @AfterAll + static void afterAll() { + wireMockServer.stop(); + serviceAMockServer.stop(); + MOCKED_STATIC.close(); + } + + /** + *
+	 *     TODO
+	 *      - my-service is present in 'a' namespace
+	 *      - my-service is present in 'b' namespace
+	 *      - we enable search in namespace 'a'
+	 *      - load balancer mode is 'SERVICE'
+	 *
+	 *      - as such, only my-service in namespace a is load balanced
+	 *      - we also assert the type of ServiceInstanceListSupplier corresponding to the SERVICE mode.
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + + Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT); + Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT); + + String serviceAJson = Serialization.asJson(serviceA); + String serviceBJson = Serialization.asJson(serviceB); + + wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/a/services/my-service")) + .willReturn(WireMock.aResponse().withBody(serviceAJson).withStatus(200))); + + wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/b/services/my-service")) + .willReturn(WireMock.aResponse().withBody(serviceBJson).withStatus(200))); + + serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/")) + .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200))); + + String serviceAResult = builder.baseUrl(MY_SERVICE_URL) + .build() + .method(HttpMethod.GET) + .retrieve() + .bodyToMono(String.class) + .block(); + Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached"); + + ReactiveLoadBalancer reactiveLoadBalancer = loadBalancerClientFactory.getInstance("service-a"); + + CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory + .getProvider("my-service", ServiceInstanceListSupplier.class) + .getIfAvailable(); + Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(Fabric8ServicesListSupplier.class); + + Assertions.assertThat(output.getOut()).contains("serviceID : my-service"); + Assertions.assertThat(output.getOut()).contains("discovering services in namespace : a"); + + // was called in namespace 'a' + wireMockServer.verify(WireMock.exactly(1), + WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/a/services/my-service"))); + + // was not called in namespace 'b' + wireMockServer.verify(WireMock.exactly(0), + WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/b/services/my-service"))); + + } + +} From ced717b9e42f02010034d883d717ada5caf8b0d0 Mon Sep 17 00:00:00 2001 From: wind57 Date: Tue, 9 Sep 2025 21:38:32 +0300 Subject: [PATCH 5/5] add tests Signed-off-by: wind57 --- .../client/loadbalancer/it/App.java | 29 ++++ .../it/LoadBalancerConfiguration.java | 33 ++++ .../client/loadbalancer/it/Util.java | 38 ++--- .../it/mode/cache/CacheDisabledTest.java | 126 ++++++++++++++ .../cache/CacheEnabledOutsideTTLTest.java | 130 ++++++++++++++ .../mode/cache/CacheEnabledWithinTTLTest.java | 128 ++++++++++++++ .../it/mode/pod/AllNamespacesTest.java | 11 +- .../it/mode/pod/SelectiveNamespacesTest.java | 11 +- .../it/mode/pod/SpecificNamespaceTest.java | 11 +- .../it/mode/service/AllNamespacesTest.java | 11 +- .../mode/service/SelectiveNamespacesTest.java | 11 +- .../mode/service/SpecificNamespaceTest.java | 12 +- .../it/mode/cache/CacheDisabledTest.java | 129 ++++++++++++++ .../cache/CacheEnabledOutsideTTLTest.java | 133 +++++++++++++++ .../mode/cache/CacheEnabledWithinTTLTest.java | 131 ++++++++++++++ .../it/mode/cached/CachedServicesTest.java | 160 ------------------ 16 files changed, 888 insertions(+), 216 deletions(-) create mode 100644 spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/App.java create mode 100644 spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerConfiguration.java create mode 100644 spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheDisabledTest.java create mode 100644 spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java create mode 100644 spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java create mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheDisabledTest.java create mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java create mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java delete mode 100644 spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/App.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/App.java new file mode 100644 index 0000000000..e4ddd5930a --- /dev/null +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/App.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-present 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; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class); + } + +} diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerConfiguration.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerConfiguration.java new file mode 100644 index 0000000000..443972df88 --- /dev/null +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerConfiguration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-present 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; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.client.WebClient; + +@TestConfiguration +public class LoadBalancerConfiguration { + + @Bean + @LoadBalanced + WebClient.Builder client() { + return WebClient.builder(); + } + +} diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Util.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Util.java index 609b92c936..fa36747b37 100644 --- a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Util.java +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Util.java @@ -25,20 +25,16 @@ import io.kubernetes.client.openapi.models.V1Endpoints; import io.kubernetes.client.openapi.models.V1EndpointsBuilder; 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.V1ObjectMetaBuilder; import io.kubernetes.client.openapi.models.V1Service; import io.kubernetes.client.openapi.models.V1ServiceBuilder; import io.kubernetes.client.openapi.models.V1ServiceList; +import io.kubernetes.client.openapi.models.V1ServiceListBuilder; import io.kubernetes.client.openapi.models.V1ServicePortBuilder; import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.web.reactive.function.client.WebClient; - /** * @author wind57 */ @@ -142,24 +138,26 @@ public static void endpointsInNamespaceServiceMode(WireMockServer server, V1Endp .willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200))); } - @TestConfiguration - public static class LoadBalancerConfiguration { + public static void mockWatchers(WireMockServer wireMockServer) { + V1Service serviceA = Util.service("a", "service-a", 8888); - @Bean - @LoadBalanced - WebClient.Builder client() { - return WebClient.builder(); - } + V1ServiceList serviceListA = new V1ServiceListBuilder() + .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()) + .endMetadata() + .withItems(serviceA) + .build(); - } + servicesInNamespaceServiceMode(wireMockServer, serviceListA, "a", "service-a"); - @SpringBootApplication - public static class Configuration { + V1Endpoints endpointsA = Util.endpoints("a", "service-a", 8888, "127.0.0.1"); - public static void main(String[] args) { - SpringApplication.run(Configuration.class); - } + V1EndpointsList endpointsListA = new V1EndpointsListBuilder() + .withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()) + .endMetadata() + .withItems(endpointsA) + .build(); + Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListA, "a", "service-a"); } } diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheDisabledTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheDisabledTest.java new file mode 100644 index 0000000000..f6f85a47c0 --- /dev/null +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheDisabledTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2013-present 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.cache; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.util.ClientBuilder; +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 reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.test.annotation.DirtiesContext; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; + +/** + * @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", "spring.cloud.loadbalancer.cache.enabled=false" }, + classes = App.class) +@DirtiesContext +class CacheDisabledTest { + + private static final int SERVICE_PORT = 8888; + + private static WireMockServer wireMockServer; + + private static WireMockServer serviceAMockServer; + + private static MockedStatic clientUtils; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + Util.mockWatchers(wireMockServer); + + serviceAMockServer = new WireMockServer(SERVICE_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_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"); + + 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(); + MOCKED_STATIC.close(); + clientUtils.close(); + } + + /** + *
+	 *      - we disable caching via 'spring.cloud.loadbalancer.cache.enabled=false'
+	 *      - as such, two calls to : loadBalancer.choose() will both execute
+	 *        on the delegate itself, which we assert via 'wireMockServer.verify'
+	 * 
+ */ + @Test + void test() { + + ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance("service-a"); + Response firstResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(firstResponse.hasServer()).isTrue(); + + Response secondResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(secondResponse.hasServer()).isTrue(); + + // called two times + wireMockServer.verify(WireMock.exactly(2), WireMock.getRequestedFor( + WireMock.urlEqualTo("/api/v1/namespaces/a/services?fieldSelector=metadata.name%3D" + "service-a"))); + + } + +} diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java new file mode 100644 index 0000000000..8e0884f060 --- /dev/null +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013-present 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.cache; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.util.ClientBuilder; +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 reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.test.annotation.DirtiesContext; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; + +/** + * @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", + "spring.cloud.loadbalancer.cache.enabled=true", "spring.cloud.loadbalancer.cache.ttl=2s" }, + classes = App.class) +@DirtiesContext +class CacheEnabledOutsideTTLTest { + + private static final int SERVICE_PORT = 8888; + + private static WireMockServer wireMockServer; + + private static WireMockServer serviceAMockServer; + + private static MockedStatic clientUtils; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + Util.mockWatchers(wireMockServer); + + serviceAMockServer = new WireMockServer(SERVICE_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_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"); + + 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(); + MOCKED_STATIC.close(); + clientUtils.close(); + } + + /** + *
+	 *      - caching is enabled and : 'spring.cloud.loadbalancer.cache.ttl=2s'
+	 *      - we make one call now, and one after 2s.
+	 *      - as such, first loadBalancer.choose() will execute on the delegate,
+	 *        it will be cached. And the second call, since it got TTL-ed, will happen
+	 *        on the delegate again.
+	 *
+	 * 
+ */ + @Test + void test() throws Exception { + + ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance("service-a"); + Mono.from(loadBalancer.choose()).block(); + // ttl will expire the first flux + Thread.sleep(2500); + Response response = Mono.from(loadBalancer.choose()).block(); + assertThat(response.hasServer()).as("there should be one server").isTrue(); + + // called two times + wireMockServer.verify(WireMock.exactly(2), WireMock.getRequestedFor( + WireMock.urlEqualTo("/api/v1/namespaces/a/services?fieldSelector=metadata.name%3D" + "service-a"))); + + } + +} diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java new file mode 100644 index 0000000000..df0c565e24 --- /dev/null +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013-present 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.cache; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.util.ClientBuilder; +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 reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.test.annotation.DirtiesContext; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; + +/** + * @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", + "spring.cloud.loadbalancer.cache.enabled=true", "spring.cloud.loadbalancer.cache.ttl=2s" }, + classes = App.class) +@DirtiesContext +class CacheEnabledWithinTTLTest { + + private static final int SERVICE_PORT = 8888; + + private static WireMockServer wireMockServer; + + private static WireMockServer serviceAMockServer; + + private static MockedStatic clientUtils; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + Util.mockWatchers(wireMockServer); + + serviceAMockServer = new WireMockServer(SERVICE_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_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"); + + 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(); + MOCKED_STATIC.close(); + clientUtils.close(); + } + + /** + *
+	 *      - caching is enabled and : 'spring.cloud.loadbalancer.cache.ttl=2s'
+	 *      - we make two calls within those two seconds
+	 *      - as such, first loadBalancer.choose() will execute on the delegate,
+	 *        while the second one will be cached.
+	 *
+	 * 
+ */ + @Test + void test() { + + ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance("service-a"); + Response firstResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(firstResponse.hasServer()).isTrue(); + Response secondResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(secondResponse.hasServer()).isTrue(); + + // called two times + wireMockServer.verify(WireMock.exactly(1), WireMock.getRequestedFor( + WireMock.urlEqualTo("/api/v1/namespaces/a/services?fieldSelector=metadata.name%3D" + "service-a"))); + + } + +} diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/AllNamespacesTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/AllNamespacesTest.java index 8ee77ef681..3b5710ac91 100644 --- a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/AllNamespacesTest.java +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/AllNamespacesTest.java @@ -34,10 +34,11 @@ 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.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.LoadBalancerConfiguration; import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; @@ -49,8 +50,6 @@ 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 @@ -58,7 +57,7 @@ @SpringBootTest( properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.discovery.all-namespaces=true" }, - classes = { LoadBalancerConfiguration.class, Configuration.class }) + classes = { LoadBalancerConfiguration.class, App.class }) class AllNamespacesTest { private static final String SERVICE_A_URL = "http://service-a"; @@ -77,6 +76,7 @@ class AllNamespacesTest { private static MockedStatic clientUtils; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -84,7 +84,7 @@ class AllNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -158,7 +158,6 @@ void test() { Assertions.assertThat(serviceBResult).isEqualTo("service-b-reached"); CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("service-a", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()) diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java index 245733aeeb..f5195faa41 100644 --- a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/mode/pod/SelectiveNamespacesTest.java @@ -34,10 +34,11 @@ 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.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.LoadBalancerConfiguration; import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; @@ -49,8 +50,6 @@ 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 @@ -58,7 +57,7 @@ @SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class SelectiveNamespacesTest { private static final String MY_SERVICE_URL = "http://my-service"; @@ -77,6 +76,7 @@ class SelectiveNamespacesTest { private static WireMockServer serviceCMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -86,7 +86,7 @@ class SelectiveNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -182,7 +182,6 @@ void test() { } CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()) 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 index a1aa42053d..d67d7039dc 100644 --- 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 @@ -34,10 +34,11 @@ 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.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.LoadBalancerConfiguration; import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; @@ -49,15 +50,13 @@ 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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class SpecificNamespaceTest { private static final String SERVICE_A_URL = "http://my-service"; @@ -72,6 +71,7 @@ class SpecificNamespaceTest { private static WireMockServer serviceBMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -81,7 +81,7 @@ class SpecificNamespaceTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -151,7 +151,6 @@ void test() { Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached"); CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()) 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 index 6876b71a68..33995f701b 100644 --- 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 @@ -34,11 +34,12 @@ 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.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.LoadBalancerConfiguration; import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; @@ -49,8 +50,6 @@ 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 @@ -58,7 +57,7 @@ @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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class AllNamespacesTest { private static final String SERVICE_A_URL = "http://service-a"; @@ -77,6 +76,7 @@ class AllNamespacesTest { private static MockedStatic clientUtils; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -84,7 +84,7 @@ class AllNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -158,7 +158,6 @@ void test() { 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); 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 index 6c30805da3..17760b0109 100644 --- 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 @@ -34,11 +34,12 @@ 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.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.LoadBalancerConfiguration; import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; @@ -49,8 +50,6 @@ 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 @@ -58,7 +57,7 @@ @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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class SelectiveNamespacesTest { private static final String MY_SERVICE_URL = "http://my-service"; @@ -77,6 +76,7 @@ class SelectiveNamespacesTest { private static WireMockServer serviceCMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -86,7 +86,7 @@ class SelectiveNamespacesTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -182,7 +182,6 @@ void test() { } CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getIfAvailable() .getProvider("my-service", ServiceInstanceListSupplier.class) .getIfAvailable(); Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class); 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 index 03502d9b88..0530ac29f3 100644 --- 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 @@ -34,11 +34,12 @@ 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.App; +import org.springframework.cloud.kubernetes.client.loadbalancer.it.LoadBalancerConfiguration; import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util; import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; @@ -49,8 +50,6 @@ 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 @@ -59,7 +58,7 @@ 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 }) + classes = { LoadBalancerConfiguration.class, App.class }) class SpecificNamespaceTest { private static final String SERVICE_A_URL = "http://my-service"; @@ -74,6 +73,7 @@ class SpecificNamespaceTest { private static WireMockServer serviceBMockServer; + @SuppressWarnings("rawtypes") private static final MockedStatic MOCKED_STATIC = Mockito .mockStatic(KubernetesServiceInstanceMapper.class); @@ -83,7 +83,7 @@ class SpecificNamespaceTest { private WebClient.Builder builder; @Autowired - private ObjectProvider loadBalancerClientFactory; + private LoadBalancerClientFactory loadBalancerClientFactory; @BeforeAll static void beforeAll() { @@ -91,6 +91,7 @@ static void beforeAll() { wireMockServer = new WireMockServer(options().dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); + mockWatchers(); serviceAMockServer = new WireMockServer(SERVICE_A_PORT); @@ -153,7 +154,6 @@ void test() { 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); diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheDisabledTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheDisabledTest.java new file mode 100644 index 0000000000..d7470edcaa --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheDisabledTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013-present 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.fabric8.loadbalancer.it.mode.cache; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.utils.Serialization; +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 reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.test.annotation.DirtiesContext; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @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", "spring.cloud.loadbalancer.cache.enabled=false" }, + classes = App.class) +@DirtiesContext +class CacheDisabledTest { + + private static final int SERVICE_PORT = 8888; + + private static WireMockServer wireMockServer; + + private static WireMockServer serviceAMockServer; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + serviceAMockServer = new WireMockServer(SERVICE_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_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"); + + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, "http://localhost:" + wireMockServer.port()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + } + + @AfterAll + static void afterAll() { + wireMockServer.stop(); + serviceAMockServer.stop(); + MOCKED_STATIC.close(); + } + + /** + *
+	 *      - we disable caching via 'spring.cloud.loadbalancer.cache.enabled=false'
+	 *      - as such, two calls to : loadBalancer.choose() will both execute
+	 *        on the delegate itself, which we assert via 'wireMockServer.verify'
+	 * 
+ */ + @Test + void test() { + + Service serviceA = Util.service("a", "my-service", SERVICE_PORT); + String serviceAJson = Serialization.asJson(serviceA); + + wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a")) + .willReturn(WireMock.aResponse().withBody(serviceAJson).withStatus(200))); + + ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance("service-a"); + Response firstResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(firstResponse.hasServer()).isTrue(); + + Response secondResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(secondResponse.hasServer()).isTrue(); + + // called two times, since caching is disabled. + wireMockServer.verify(WireMock.exactly(2), + WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a"))); + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java new file mode 100644 index 0000000000..fe484bb87b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledOutsideTTLTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2013-present 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.fabric8.loadbalancer.it.mode.cache; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.utils.Serialization; +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 reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.test.annotation.DirtiesContext; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @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", + "spring.cloud.loadbalancer.cache.enabled=true", "spring.cloud.loadbalancer.cache.ttl=2s" }, + classes = App.class) +@DirtiesContext +class CacheEnabledOutsideTTLTest { + + private static final int SERVICE_PORT = 8888; + + private static WireMockServer wireMockServer; + + private static WireMockServer serviceAMockServer; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + serviceAMockServer = new WireMockServer(SERVICE_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_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"); + + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, "http://localhost:" + wireMockServer.port()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + } + + @AfterAll + static void afterAll() { + wireMockServer.stop(); + serviceAMockServer.stop(); + MOCKED_STATIC.close(); + + } + + /** + *
+	 *      - caching is enabled and : 'spring.cloud.loadbalancer.cache.ttl=2s'
+	 *      - we make one call now, and one after 2s.
+	 *      - as such, first loadBalancer.choose() will execute on the delegate,
+	 *        it will be cached. And the second call, since it got TTL-ed, will happen
+	 *        on the delegate again.
+	 *
+	 * 
+ */ + @Test + void testCallsOutsideTTL() throws InterruptedException { + + Service serviceA = Util.service("a", "my-service", SERVICE_PORT); + String serviceAJson = Serialization.asJson(serviceA); + + wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a")) + .willReturn(WireMock.aResponse().withBody(serviceAJson).withStatus(200))); + + ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance("service-a"); + Mono.from(loadBalancer.choose()).block(); + // ttl will expire the first flux + Thread.sleep(2500); + Response response = Mono.from(loadBalancer.choose()).block(); + assertThat(response.hasServer()).as("there should be one server").isTrue(); + + // called two times, since it got expired via the ttl + wireMockServer.verify(WireMock.exactly(2), + WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a"))); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java new file mode 100644 index 0000000000..975ced4e61 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cache/CacheEnabledWithinTTLTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2013-present 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.fabric8.loadbalancer.it.mode.cache; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.utils.Serialization; +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 reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; +import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.test.annotation.DirtiesContext; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @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", + "spring.cloud.loadbalancer.cache.enabled=true", "spring.cloud.loadbalancer.cache.ttl=2s" }, + classes = App.class) +@DirtiesContext +class CacheEnabledWithinTTLTest { + + private static final int SERVICE_PORT = 8888; + + private static WireMockServer wireMockServer; + + private static WireMockServer serviceAMockServer; + + @SuppressWarnings("rawtypes") + private static final MockedStatic MOCKED_STATIC = Mockito + .mockStatic(KubernetesServiceInstanceMapper.class); + + @Autowired + private LoadBalancerClientFactory loadBalancerClientFactory; + + @BeforeAll + static void beforeAll() { + + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + serviceAMockServer = new WireMockServer(SERVICE_PORT); + serviceAMockServer.start(); + WireMock.configureFor("localhost", SERVICE_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"); + + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, "http://localhost:" + wireMockServer.port()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + } + + @AfterAll + static void afterAll() { + wireMockServer.stop(); + serviceAMockServer.stop(); + MOCKED_STATIC.close(); + } + + /** + *
+	 *      - caching is enabled and : 'spring.cloud.loadbalancer.cache.ttl=2s'
+	 *      - we make two calls within those two seconds
+	 *      - as such, first loadBalancer.choose() will execute on the delegate,
+	 *        while the second one will be cached.
+	 *
+	 * 
+ */ + @Test + void testCallsWithinTTL() { + + Service serviceA = Util.service("a", "my-service", SERVICE_PORT); + String serviceAJson = Serialization.asJson(serviceA); + + wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a")) + .willReturn(WireMock.aResponse().withBody(serviceAJson).withStatus(200))); + + ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance("service-a"); + Response firstResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(firstResponse.hasServer()).isTrue(); + Response secondResponse = Mono.from(loadBalancer.choose()).block(); + assertThat(secondResponse.hasServer()).isTrue(); + + // called once only + wireMockServer.verify(WireMock.exactly(1), + WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a"))); + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java deleted file mode 100644 index 67bdfe924d..0000000000 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/cached/CachedServicesTest.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.cached; - -import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.client.WireMock; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.utils.Serialization; -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.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockedStatic; -import org.mockito.Mockito; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; -import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper; -import org.springframework.cloud.kubernetes.fabric8.loadbalancer.Fabric8ServicesListSupplier; -import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util; -import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.App; -import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.LoadBalancerConfiguration; -import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; -import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; -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; - -/** - * @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, App.class }) -@ExtendWith(OutputCaptureExtension.class) -class CachedServicesTest { - - private static final String MY_SERVICE_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; - - @SuppressWarnings("rawtypes") - private static final MockedStatic MOCKED_STATIC = Mockito - .mockStatic(KubernetesServiceInstanceMapper.class); - - @Autowired - private WebClient.Builder builder; - - @Autowired - private LoadBalancerClientFactory loadBalancerClientFactory; - - @BeforeAll - static void beforeAll() { - - wireMockServer = new WireMockServer(options().dynamicPort()); - wireMockServer.start(); - WireMock.configureFor("localhost", wireMockServer.port()); - - serviceAMockServer = new WireMockServer(SERVICE_A_PORT); - serviceAMockServer.start(); - WireMock.configureFor("localhost", SERVICE_A_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"); - - // Configure the kubernetes master url to point to the mock server - System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, "http://localhost:" + wireMockServer.port()); - System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); - System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); - System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); - System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); - System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); - } - - @AfterAll - static void afterAll() { - wireMockServer.stop(); - serviceAMockServer.stop(); - MOCKED_STATIC.close(); - } - - /** - *
-	 *     TODO
-	 *      - my-service is present in 'a' namespace
-	 *      - my-service is present in 'b' namespace
-	 *      - we enable search in namespace 'a'
-	 *      - load balancer mode is 'SERVICE'
-	 *
-	 *      - as such, only my-service in namespace a is load balanced
-	 *      - we also assert the type of ServiceInstanceListSupplier corresponding to the SERVICE mode.
-	 * 
- */ - @Test - void test(CapturedOutput output) { - - Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT); - Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT); - - String serviceAJson = Serialization.asJson(serviceA); - String serviceBJson = Serialization.asJson(serviceB); - - wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/a/services/my-service")) - .willReturn(WireMock.aResponse().withBody(serviceAJson).withStatus(200))); - - wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/api/v1/namespaces/b/services/my-service")) - .willReturn(WireMock.aResponse().withBody(serviceBJson).withStatus(200))); - - serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/")) - .willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200))); - - String serviceAResult = builder.baseUrl(MY_SERVICE_URL) - .build() - .method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .block(); - Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached"); - - ReactiveLoadBalancer reactiveLoadBalancer = loadBalancerClientFactory.getInstance("service-a"); - - CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory - .getProvider("my-service", ServiceInstanceListSupplier.class) - .getIfAvailable(); - Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(Fabric8ServicesListSupplier.class); - - Assertions.assertThat(output.getOut()).contains("serviceID : my-service"); - Assertions.assertThat(output.getOut()).contains("discovering services in namespace : a"); - - // was called in namespace 'a' - wireMockServer.verify(WireMock.exactly(1), - WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/a/services/my-service"))); - - // was not called in namespace 'b' - wireMockServer.verify(WireMock.exactly(0), - WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/b/services/my-service"))); - - } - -}