Cleanup k8s client loadbalancer part 2 (#1613)
* test * fix @Nested tests not running * trigger again * Add renovate.json * Delete renovate.json * Delete delme.sh * dirty * first * checkstyle * dirty * dirty * add tests * fix test * fix tests * checkstyle --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
@@ -35,7 +35,20 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.mokito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.wiremock</groupId>
|
||||
<artifactId>wiremock-standalone</artifactId>
|
||||
@@ -51,6 +64,11 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.JSON;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
|
||||
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.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.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
|
||||
*/
|
||||
public final class Util {
|
||||
|
||||
private Util() {
|
||||
|
||||
}
|
||||
|
||||
public static V1Service service(String namespace, String name, int port) {
|
||||
return new V1ServiceBuilder().withNewMetadata().withNamespace(namespace).withName(name).endMetadata()
|
||||
.withSpec(new V1ServiceSpecBuilder()
|
||||
.withPorts(new V1ServicePortBuilder().withName("http").withPort(port).build()).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static V1Endpoints endpoints(String namespace, String name, int port, String host) {
|
||||
return new V1EndpointsBuilder()
|
||||
.withSubsets(
|
||||
new V1EndpointSubsetBuilder().withPorts(new CoreV1EndpointPortBuilder().withPort(port).build())
|
||||
.withAddresses(new V1EndpointAddressBuilder().withIp(host).build()).build())
|
||||
.withMetadata(new V1ObjectMetaBuilder().withName(name).withNamespace(namespace).build()).build();
|
||||
}
|
||||
|
||||
public static void servicesPodMode(WireMockServer server, V1ServiceList serviceList) {
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/services*"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(serviceList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void servicesServiceMode(WireMockServer server, V1ServiceList serviceList, String serviceName) {
|
||||
// resourceVersion=0 is passed only from the watcher, so this mocks the
|
||||
// 'postConstruct' in the KubernetesInformerDiscoveryClient
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/services*"))
|
||||
.withQueryParam("resourceVersion", WireMock.equalTo("0"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(serviceList)).withStatus(200)));
|
||||
|
||||
server.stubFor(
|
||||
WireMock.get(WireMock.urlEqualTo("/api/v1/services?fieldSelector=metadata.name%3D" + serviceName))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(serviceList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void endpointsPodMode(WireMockServer server, V1EndpointsList endpointsList) {
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/endpoints*"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void endpointsServiceMode(WireMockServer server, V1EndpointsList endpointsList,
|
||||
String endpointsName) {
|
||||
// resourceVersion=0 is passed only from the watcher, so this mocks the
|
||||
// 'postConstruct' in the KubernetesInformerDiscoveryClient
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/endpoints*"))
|
||||
.withQueryParam("resourceVersion", WireMock.equalTo("0"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
|
||||
|
||||
server.stubFor(WireMock
|
||||
.get(WireMock.urlEqualTo("/api/v1/endpoints?fieldSelector=metadata.name%3D" + endpointsName))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void servicesInNamespacePodMode(WireMockServer server, V1ServiceList serviceList, String namespace) {
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/namespaces/" + namespace + "/services*"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(serviceList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void servicesInNamespaceServiceMode(WireMockServer server, V1ServiceList serviceList,
|
||||
String namespace, String serviceName) {
|
||||
// resourceVersion=0 is passed only from the watcher, so this mocks the
|
||||
// 'postConstruct' in the KubernetesInformerDiscoveryClient
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/namespaces/" + namespace + "/services*"))
|
||||
.withQueryParam("resourceVersion", WireMock.equalTo("0"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(serviceList)).withStatus(200)));
|
||||
|
||||
server.stubFor(WireMock
|
||||
.get(WireMock.urlEqualTo(
|
||||
"/api/v1/namespaces/" + namespace + "/services?fieldSelector=metadata.name%3D" + serviceName))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(serviceList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void endpointsInNamespacePodMode(WireMockServer server, V1EndpointsList endpointsList,
|
||||
String namespace) {
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/namespaces/" + namespace + "/endpoints*"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
|
||||
}
|
||||
|
||||
public static void endpointsInNamespaceServiceMode(WireMockServer server, V1EndpointsList endpointsList,
|
||||
String namespace, String endpointsName) {
|
||||
// resourceVersion=0 is passed only from the watcher, so this mocks the
|
||||
// 'postConstruct' in the KubernetesInformerDiscoveryClient
|
||||
server.stubFor(WireMock.get(WireMock.urlPathMatching("^/api/v1/namespaces/" + namespace + "/endpoints*"))
|
||||
.withQueryParam("resourceVersion", WireMock.equalTo("0"))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
|
||||
|
||||
server.stubFor(WireMock
|
||||
.get(WireMock.urlEqualTo("/api/v1/namespaces/" + namespace + "/endpoints?fieldSelector=metadata.name%3D"
|
||||
+ endpointsName))
|
||||
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.pod;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsList;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceList;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
|
||||
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootTest(
|
||||
properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.cloud.kubernetes.discovery.all-namespaces=true" },
|
||||
classes = { LoadBalancerConfiguration.class, Configuration.class })
|
||||
class AllNamespacesTest {
|
||||
|
||||
private static final String SERVICE_A_URL = "http://service-a";
|
||||
|
||||
private static final String SERVICE_B_URL = "http://service-b";
|
||||
|
||||
private static final int SERVICE_A_PORT = 8888;
|
||||
|
||||
private static final int SERVICE_B_PORT = 8889;
|
||||
|
||||
private static WireMockServer wireMockServer;
|
||||
|
||||
private static WireMockServer serviceAMockServer;
|
||||
|
||||
private static WireMockServer serviceBMockServer;
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtils;
|
||||
|
||||
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
|
||||
.mockStatic(KubernetesServiceInstanceMapper.class);
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
||||
wireMockServer = new WireMockServer(options().dynamicPort());
|
||||
wireMockServer.start();
|
||||
WireMock.configureFor("localhost", wireMockServer.port());
|
||||
mockWatchers();
|
||||
|
||||
serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
|
||||
serviceAMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_A_PORT);
|
||||
|
||||
serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
|
||||
serviceBMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_B_PORT);
|
||||
|
||||
// we mock host creation so that it becomes something like : localhost:8888
|
||||
// then wiremock can catch this request, and we can assert for the result
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("service-a", "a", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("service-b", "b", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
|
||||
clientUtils = mockStatic(KubernetesClientUtils.class);
|
||||
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
|
||||
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
wireMockServer.stop();
|
||||
serviceAMockServer.stop();
|
||||
serviceBMockServer.stop();
|
||||
MOCKED_STATIC.close();
|
||||
clientUtils.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - service-a is present in namespace a with exposed port 8888
|
||||
* - service-b is present in namespace b with exposed port 8889
|
||||
* - we make two calls to them via the load balancer
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
|
||||
|
||||
serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
|
||||
|
||||
String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
|
||||
|
||||
String serviceBResult = builder.baseUrl(SERVICE_B_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
Assertions.assertThat(serviceBResult).isEqualTo("service-b-reached");
|
||||
|
||||
CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
|
||||
.getIfAvailable().getProvider("service-a", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
Assertions.assertThat(supplier.getDelegate().getClass())
|
||||
.isSameAs(DiscoveryClientServiceInstanceListSupplier.class);
|
||||
|
||||
}
|
||||
|
||||
private static void mockWatchers() {
|
||||
V1Service serviceA = Util.service("a", "service-a", SERVICE_A_PORT);
|
||||
V1Service serviceB = Util.service("b", "service-b", SERVICE_B_PORT);
|
||||
V1ServiceList serviceList = new V1ServiceListBuilder().withKind("V1ServiceList")
|
||||
.withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build())
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceA, serviceB).build();
|
||||
Util.servicesPodMode(wireMockServer, serviceList);
|
||||
|
||||
V1Endpoints endpointsA = Util.endpoints("a", "service-a", SERVICE_A_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsB = Util.endpoints("b", "service-b", SERVICE_B_PORT, "127.0.0.1");
|
||||
V1EndpointsList endpointsList = new V1EndpointsListBuilder().withKind("V1EndpointsList")
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsA, endpointsB).build();
|
||||
Util.endpointsPodMode(wireMockServer, endpointsList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.pod;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsList;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceList;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
|
||||
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.cloud.kubernetes.discovery.all-namespaces=false", "spring.cloud.kubernetes.discovery.namespaces.[0]=a",
|
||||
"spring.cloud.kubernetes.discovery.namespaces.[1]=b" },
|
||||
classes = { LoadBalancerConfiguration.class, Configuration.class })
|
||||
class SelectiveNamespacesTest {
|
||||
|
||||
private static final String MY_SERVICE_URL = "http://my-service";
|
||||
|
||||
private static final int SERVICE_A_PORT = 8887;
|
||||
|
||||
private static final int SERVICE_B_PORT = 8888;
|
||||
|
||||
private static final int SERVICE_C_PORT = 8889;
|
||||
|
||||
private static WireMockServer wireMockServer;
|
||||
|
||||
private static WireMockServer serviceAMockServer;
|
||||
|
||||
private static WireMockServer serviceBMockServer;
|
||||
|
||||
private static WireMockServer serviceCMockServer;
|
||||
|
||||
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
|
||||
.mockStatic(KubernetesServiceInstanceMapper.class);
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtils;
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
||||
wireMockServer = new WireMockServer(options().dynamicPort());
|
||||
wireMockServer.start();
|
||||
WireMock.configureFor("localhost", wireMockServer.port());
|
||||
mockWatchers();
|
||||
|
||||
serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
|
||||
serviceAMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_A_PORT);
|
||||
|
||||
serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
|
||||
serviceBMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_B_PORT);
|
||||
|
||||
serviceCMockServer = new WireMockServer(SERVICE_C_PORT);
|
||||
serviceCMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_C_PORT);
|
||||
|
||||
// we mock host creation so that it becomes something like : localhost:8888
|
||||
// then wiremock can catch this request, and we can assert for the result
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "c", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
|
||||
clientUtils = mockStatic(KubernetesClientUtils.class);
|
||||
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
wireMockServer.stop();
|
||||
serviceAMockServer.stop();
|
||||
serviceBMockServer.stop();
|
||||
serviceCMockServer.stop();
|
||||
MOCKED_STATIC.close();
|
||||
clientUtils.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - my-service is present in 'a' namespace
|
||||
* - my-service is present in 'b' namespace
|
||||
* - my-service is present in 'c' namespace
|
||||
* - we enable search in selective namespaces [a, b]
|
||||
* - load balancer mode is 'POD'
|
||||
*
|
||||
* - as such, only service in namespace a and b are load balanced
|
||||
* - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
|
||||
|
||||
serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
|
||||
|
||||
serviceCMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-c-reached").withStatus(200)));
|
||||
|
||||
String firstCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
|
||||
String secondCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
|
||||
// since selective namespaces is a Set, we need to be careful with assertion order
|
||||
if (firstCallResult.equals("service-a-reached")) {
|
||||
Assertions.assertThat(secondCallResult).isEqualTo("service-b-reached");
|
||||
}
|
||||
else {
|
||||
Assertions.assertThat(firstCallResult).isEqualTo("service-b-reached");
|
||||
Assertions.assertThat(secondCallResult).isEqualTo("service-a-reached");
|
||||
}
|
||||
|
||||
CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
|
||||
.getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
Assertions.assertThat(supplier.getDelegate().getClass())
|
||||
.isSameAs(DiscoveryClientServiceInstanceListSupplier.class);
|
||||
}
|
||||
|
||||
private static void mockWatchers() {
|
||||
V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
|
||||
V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
|
||||
V1Service serviceC = Util.service("c", "my-service", SERVICE_C_PORT);
|
||||
|
||||
V1ServiceList serviceListA = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceA).build();
|
||||
V1ServiceList serviceListB = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceB).build();
|
||||
V1ServiceList serviceListC = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceC).build();
|
||||
|
||||
Util.servicesInNamespacePodMode(wireMockServer, serviceListA, "a");
|
||||
Util.servicesInNamespacePodMode(wireMockServer, serviceListB, "b");
|
||||
Util.servicesInNamespacePodMode(wireMockServer, serviceListC, "c");
|
||||
|
||||
V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsC = Util.endpoints("c", "my-service", SERVICE_C_PORT, "127.0.0.1");
|
||||
|
||||
V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsA).build();
|
||||
V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsB).build();
|
||||
V1EndpointsList endpointsListC = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsC).build();
|
||||
|
||||
Util.endpointsInNamespacePodMode(wireMockServer, endpointsListA, "a");
|
||||
Util.endpointsInNamespacePodMode(wireMockServer, endpointsListB, "b");
|
||||
Util.endpointsInNamespacePodMode(wireMockServer, endpointsListC, "c");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.pod;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsList;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceList;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
|
||||
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.cloud.kubernetes.discovery.all-namespaces=false", "spring.cloud.kubernetes.client.namespace=a" },
|
||||
classes = { LoadBalancerConfiguration.class, Configuration.class })
|
||||
class SpecificNamespaceTest {
|
||||
|
||||
private static final String SERVICE_A_URL = "http://my-service";
|
||||
|
||||
private static final int SERVICE_A_PORT = 8888;
|
||||
|
||||
private static final int SERVICE_B_PORT = 8889;
|
||||
|
||||
private static WireMockServer wireMockServer;
|
||||
|
||||
private static WireMockServer serviceAMockServer;
|
||||
|
||||
private static WireMockServer serviceBMockServer;
|
||||
|
||||
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
|
||||
.mockStatic(KubernetesServiceInstanceMapper.class);
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtils;
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
||||
wireMockServer = new WireMockServer(options().dynamicPort());
|
||||
wireMockServer.start();
|
||||
WireMock.configureFor("localhost", wireMockServer.port());
|
||||
mockWatchers();
|
||||
|
||||
serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
|
||||
serviceAMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_A_PORT);
|
||||
|
||||
serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
|
||||
serviceBMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_B_PORT);
|
||||
|
||||
// we mock host creation so that it becomes something like : localhost:8888
|
||||
// then wiremock can catch this request, and we can assert for the result
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
|
||||
// we need to not mock 'getApplicationNamespace'
|
||||
clientUtils = mockStatic(KubernetesClientUtils.class, Mockito.CALLS_REAL_METHODS);
|
||||
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
wireMockServer.stop();
|
||||
serviceAMockServer.stop();
|
||||
serviceBMockServer.stop();
|
||||
MOCKED_STATIC.close();
|
||||
clientUtils.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - my-service is present in 'a' namespace
|
||||
* - my-service is present in 'b' namespace
|
||||
* - we enable search in namespace 'a'
|
||||
* - load balancer mode is 'POD'
|
||||
*
|
||||
* - as such, only my-service in namespace a is load balanced
|
||||
* - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
|
||||
|
||||
serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
|
||||
|
||||
String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
|
||||
|
||||
CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
|
||||
.getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
Assertions.assertThat(supplier.getDelegate().getClass())
|
||||
.isSameAs(DiscoveryClientServiceInstanceListSupplier.class);
|
||||
}
|
||||
|
||||
private static void mockWatchers() {
|
||||
V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
|
||||
V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
|
||||
|
||||
V1ServiceList serviceListA = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceA).build();
|
||||
V1ServiceList serviceListB = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceB).build();
|
||||
|
||||
Util.servicesInNamespacePodMode(wireMockServer, serviceListA, "a");
|
||||
Util.servicesInNamespacePodMode(wireMockServer, serviceListB, "b");
|
||||
|
||||
V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
|
||||
|
||||
V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsA).build();
|
||||
V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsB).build();
|
||||
|
||||
Util.endpointsInNamespacePodMode(wireMockServer, endpointsListA, "a");
|
||||
Util.endpointsInNamespacePodMode(wireMockServer, endpointsListB, "b");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.service;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsList;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceList;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
|
||||
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootTest(
|
||||
properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE", "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.cloud.kubernetes.discovery.all-namespaces=true" },
|
||||
classes = { LoadBalancerConfiguration.class, Configuration.class })
|
||||
class AllNamespacesTest {
|
||||
|
||||
private static final String SERVICE_A_URL = "http://service-a";
|
||||
|
||||
private static final String SERVICE_B_URL = "http://service-b";
|
||||
|
||||
private static final int SERVICE_A_PORT = 8888;
|
||||
|
||||
private static final int SERVICE_B_PORT = 8889;
|
||||
|
||||
private static WireMockServer wireMockServer;
|
||||
|
||||
private static WireMockServer serviceAMockServer;
|
||||
|
||||
private static WireMockServer serviceBMockServer;
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtils;
|
||||
|
||||
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
|
||||
.mockStatic(KubernetesServiceInstanceMapper.class);
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
||||
wireMockServer = new WireMockServer(options().dynamicPort().notifier(new ConsoleNotifier(true)));
|
||||
wireMockServer.start();
|
||||
WireMock.configureFor("localhost", wireMockServer.port());
|
||||
mockWatchers();
|
||||
|
||||
serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
|
||||
serviceAMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_A_PORT);
|
||||
|
||||
serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
|
||||
serviceBMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_B_PORT);
|
||||
|
||||
// we mock host creation so that it becomes something like : localhost:8888
|
||||
// then wiremock can catch this request, and we can assert for the result
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("service-a", "a", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("service-b", "b", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
|
||||
clientUtils = mockStatic(KubernetesClientUtils.class);
|
||||
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
|
||||
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
wireMockServer.stop();
|
||||
serviceAMockServer.stop();
|
||||
serviceBMockServer.stop();
|
||||
MOCKED_STATIC.close();
|
||||
clientUtils.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - service-a is present in namespace a with exposed port 8888
|
||||
* - service-b is present in namespace b with exposed port 8889
|
||||
* - we make two calls to them via the load balancer
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
|
||||
|
||||
serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
|
||||
|
||||
String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
|
||||
|
||||
String serviceBResult = builder.baseUrl(SERVICE_B_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
Assertions.assertThat(serviceBResult).isEqualTo("service-b-reached");
|
||||
|
||||
CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
|
||||
.getIfAvailable().getProvider("service-a", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class);
|
||||
|
||||
}
|
||||
|
||||
private static void mockWatchers() {
|
||||
V1Service serviceA = Util.service("a", "service-a", SERVICE_A_PORT);
|
||||
V1ServiceList serviceListA = new V1ServiceListBuilder().withKind("V1ServiceList")
|
||||
.withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build())
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceA).build();
|
||||
Util.servicesServiceMode(wireMockServer, serviceListA, "service-a");
|
||||
|
||||
V1Service serviceB = Util.service("b", "service-b", SERVICE_B_PORT);
|
||||
V1ServiceList serviceListB = new V1ServiceListBuilder().withKind("V1ServiceList")
|
||||
.withMetadata(new V1ListMetaBuilder().withResourceVersion("0").build())
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceB).build();
|
||||
Util.servicesServiceMode(wireMockServer, serviceListB, "service-b");
|
||||
|
||||
V1Endpoints endpointsA = Util.endpoints("a", "service-a", SERVICE_A_PORT, "127.0.0.1");
|
||||
V1EndpointsList endpointsListA = new V1EndpointsListBuilder().withKind("V1EndpointsList")
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsA).build();
|
||||
Util.endpointsServiceMode(wireMockServer, endpointsListA, "service-a");
|
||||
|
||||
V1Endpoints endpointsB = Util.endpoints("b", "service-b", SERVICE_B_PORT, "127.0.0.1");
|
||||
V1EndpointsList endpointsListB = new V1EndpointsListBuilder().withKind("V1EndpointsList")
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsB).build();
|
||||
Util.endpointsServiceMode(wireMockServer, endpointsListB, "service-b");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.service;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsList;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceList;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
|
||||
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE",
|
||||
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.discovery.all-namespaces=false",
|
||||
"spring.cloud.kubernetes.discovery.namespaces.[0]=a", "spring.cloud.kubernetes.discovery.namespaces.[1]=b" },
|
||||
classes = { LoadBalancerConfiguration.class, Configuration.class })
|
||||
class SelectiveNamespacesTest {
|
||||
|
||||
private static final String MY_SERVICE_URL = "http://my-service";
|
||||
|
||||
private static final int SERVICE_A_PORT = 8887;
|
||||
|
||||
private static final int SERVICE_B_PORT = 8888;
|
||||
|
||||
private static final int SERVICE_C_PORT = 8889;
|
||||
|
||||
private static WireMockServer wireMockServer;
|
||||
|
||||
private static WireMockServer serviceAMockServer;
|
||||
|
||||
private static WireMockServer serviceBMockServer;
|
||||
|
||||
private static WireMockServer serviceCMockServer;
|
||||
|
||||
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
|
||||
.mockStatic(KubernetesServiceInstanceMapper.class);
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtils;
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
||||
wireMockServer = new WireMockServer(options().dynamicPort());
|
||||
wireMockServer.start();
|
||||
WireMock.configureFor("localhost", wireMockServer.port());
|
||||
mockWatchers();
|
||||
|
||||
serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
|
||||
serviceAMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_A_PORT);
|
||||
|
||||
serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
|
||||
serviceBMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_B_PORT);
|
||||
|
||||
serviceCMockServer = new WireMockServer(SERVICE_C_PORT);
|
||||
serviceCMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_C_PORT);
|
||||
|
||||
// we mock host creation so that it becomes something like : localhost:8888
|
||||
// then wiremock can catch this request, and we can assert for the result
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "c", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
|
||||
clientUtils = mockStatic(KubernetesClientUtils.class);
|
||||
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
wireMockServer.stop();
|
||||
serviceAMockServer.stop();
|
||||
serviceBMockServer.stop();
|
||||
serviceCMockServer.stop();
|
||||
MOCKED_STATIC.close();
|
||||
clientUtils.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - my-service is present in 'a' namespace
|
||||
* - my-service is present in 'b' namespace
|
||||
* - my-service is present in 'c' namespace
|
||||
* - we enable search in selective namespaces [a, b]
|
||||
* - load balancer mode is 'POD'
|
||||
*
|
||||
* - as such, only service in namespace a and b are load balanced
|
||||
* - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
|
||||
|
||||
serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
|
||||
|
||||
serviceCMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-c-reached").withStatus(200)));
|
||||
|
||||
String firstCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
|
||||
String secondCallResult = builder.baseUrl(MY_SERVICE_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
|
||||
// since selective namespaces is a Set, we need to be careful with assertion order
|
||||
if (firstCallResult.equals("service-a-reached")) {
|
||||
Assertions.assertThat(secondCallResult).isEqualTo("service-b-reached");
|
||||
}
|
||||
else {
|
||||
Assertions.assertThat(firstCallResult).isEqualTo("service-b-reached");
|
||||
Assertions.assertThat(secondCallResult).isEqualTo("service-a-reached");
|
||||
}
|
||||
|
||||
CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
|
||||
.getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class);
|
||||
}
|
||||
|
||||
private static void mockWatchers() {
|
||||
V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
|
||||
V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
|
||||
V1Service serviceC = Util.service("c", "my-service", SERVICE_C_PORT);
|
||||
|
||||
V1ServiceList serviceListA = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceA).build();
|
||||
V1ServiceList serviceListB = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceB).build();
|
||||
V1ServiceList serviceListC = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceC).build();
|
||||
|
||||
Util.servicesInNamespaceServiceMode(wireMockServer, serviceListA, "a", "my-service");
|
||||
Util.servicesInNamespaceServiceMode(wireMockServer, serviceListB, "b", "my-service");
|
||||
Util.servicesInNamespaceServiceMode(wireMockServer, serviceListC, "c", "my-service");
|
||||
|
||||
V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsC = Util.endpoints("c", "my-service", SERVICE_C_PORT, "127.0.0.1");
|
||||
|
||||
V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsA).build();
|
||||
V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsB).build();
|
||||
V1EndpointsList endpointsListC = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsC).build();
|
||||
|
||||
Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListA, "a", "my-service");
|
||||
Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListB, "b", "my-service");
|
||||
Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListC, "c", "my-service");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.service;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsList;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceList;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
|
||||
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.Configuration;
|
||||
import static org.springframework.cloud.kubernetes.client.loadbalancer.it.Util.LoadBalancerConfiguration;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootTest(
|
||||
properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE", "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.cloud.kubernetes.discovery.all-namespaces=false",
|
||||
"spring.cloud.kubernetes.client.namespace=a" },
|
||||
classes = { LoadBalancerConfiguration.class, Configuration.class })
|
||||
class SpecificNamespaceTest {
|
||||
|
||||
private static final String SERVICE_A_URL = "http://my-service";
|
||||
|
||||
private static final int SERVICE_A_PORT = 8888;
|
||||
|
||||
private static final int SERVICE_B_PORT = 8889;
|
||||
|
||||
private static WireMockServer wireMockServer;
|
||||
|
||||
private static WireMockServer serviceAMockServer;
|
||||
|
||||
private static WireMockServer serviceBMockServer;
|
||||
|
||||
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
|
||||
.mockStatic(KubernetesServiceInstanceMapper.class);
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtils;
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
||||
wireMockServer = new WireMockServer(options().dynamicPort());
|
||||
wireMockServer.start();
|
||||
WireMock.configureFor("localhost", wireMockServer.port());
|
||||
mockWatchers();
|
||||
|
||||
serviceAMockServer = new WireMockServer(SERVICE_A_PORT);
|
||||
serviceAMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_A_PORT);
|
||||
|
||||
serviceBMockServer = new WireMockServer(SERVICE_B_PORT);
|
||||
serviceBMockServer.start();
|
||||
WireMock.configureFor("localhost", SERVICE_B_PORT);
|
||||
|
||||
// we mock host creation so that it becomes something like : localhost:8888
|
||||
// then wiremock can catch this request, and we can assert for the result
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "b", "cluster.local"))
|
||||
.thenReturn("localhost");
|
||||
|
||||
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
|
||||
// we need to not mock 'getApplicationNamespace'
|
||||
clientUtils = mockStatic(KubernetesClientUtils.class, Mockito.CALLS_REAL_METHODS);
|
||||
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
wireMockServer.stop();
|
||||
serviceAMockServer.stop();
|
||||
serviceBMockServer.stop();
|
||||
MOCKED_STATIC.close();
|
||||
clientUtils.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - my-service is present in 'a' namespace
|
||||
* - my-service is present in 'b' namespace
|
||||
* - we enable search in namespace 'a'
|
||||
* - load balancer mode is 'POD'
|
||||
*
|
||||
* - as such, only my-service in namespace a is load balanced
|
||||
* - we also assert the type of ServiceInstanceListSupplier corresponding to the POD mode.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
serviceAMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-a-reached").withStatus(200)));
|
||||
|
||||
serviceBMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/"))
|
||||
.willReturn(WireMock.aResponse().withBody("service-b-reached").withStatus(200)));
|
||||
|
||||
String serviceAResult = builder.baseUrl(SERVICE_A_URL).build().method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).block();
|
||||
Assertions.assertThat(serviceAResult).isEqualTo("service-a-reached");
|
||||
|
||||
CachingServiceInstanceListSupplier supplier = (CachingServiceInstanceListSupplier) loadBalancerClientFactory
|
||||
.getIfAvailable().getProvider("my-service", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
Assertions.assertThat(supplier.getDelegate().getClass()).isSameAs(KubernetesClientServicesListSupplier.class);
|
||||
}
|
||||
|
||||
private static void mockWatchers() {
|
||||
V1Service serviceA = Util.service("a", "my-service", SERVICE_A_PORT);
|
||||
V1Service serviceB = Util.service("b", "my-service", SERVICE_B_PORT);
|
||||
|
||||
V1ServiceList serviceListA = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceA).build();
|
||||
V1ServiceList serviceListB = new V1ServiceListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(serviceB).build();
|
||||
|
||||
Util.servicesInNamespaceServiceMode(wireMockServer, serviceListA, "a", "my-service");
|
||||
Util.servicesInNamespaceServiceMode(wireMockServer, serviceListB, "b", "my-service");
|
||||
|
||||
V1Endpoints endpointsA = Util.endpoints("a", "my-service", SERVICE_A_PORT, "127.0.0.1");
|
||||
V1Endpoints endpointsB = Util.endpoints("b", "my-service", SERVICE_B_PORT, "127.0.0.1");
|
||||
|
||||
V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsA).build();
|
||||
V1EndpointsList endpointsListB = new V1EndpointsListBuilder()
|
||||
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build()).endMetadata()
|
||||
.withItems(endpointsB).build();
|
||||
|
||||
Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListA, "a", "my-service");
|
||||
Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListB, "b", "my-service");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,4 +3,6 @@
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<!-- needed for CapturedOutput -->
|
||||
<logger name="org.springframework.cloud.kubernetes.client.loadbalancer" level="DEBUG"/>
|
||||
<!-- otherwise there would be far too much logging from 'it' package -->
|
||||
<logger name="io.kubernetes.client.informer.cache" level="OFF" />
|
||||
</configuration>
|
||||
|
||||
@@ -128,9 +128,6 @@
|
||||
<!-- configuration watcher is based on k8s-client, there's no fabric8 counterpart -->
|
||||
<module>spring-cloud-kubernetes-k8s-client-configuration-watcher</module>
|
||||
|
||||
<!-- loadbalancer -->
|
||||
<module>spring-cloud-kubernetes-k8s-client-loadbalancer</module>
|
||||
|
||||
<!-- config watcher reload, using kafka and configmap -->
|
||||
<module>spring-cloud-kubernetes-k8s-client-kafka-configmap-reload-multiple-apps</module>
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.k8s.client.loadbalancer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
|
||||
@SpringBootApplication
|
||||
@RestController
|
||||
class KubernetesClientLoadBalancerApplication {
|
||||
|
||||
private static final String URL = "http://service-wiremock/__admin/mappings";
|
||||
|
||||
private final WebClient.Builder client;
|
||||
|
||||
private final ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory;
|
||||
|
||||
KubernetesClientLoadBalancerApplication(WebClient.Builder client,
|
||||
ObjectProvider<LoadBalancerClientFactory> loadBalancerClientFactory) {
|
||||
this.client = client;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(KubernetesClientLoadBalancerApplication.class, args);
|
||||
}
|
||||
|
||||
@GetMapping("/loadbalancer-it/service")
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> greeting() {
|
||||
return (Map<String, Object>) client.baseUrl(URL).build().method(HttpMethod.GET).retrieve().bodyToMono(Map.class)
|
||||
.block();
|
||||
}
|
||||
|
||||
@GetMapping("/loadbalancer-it/supplier")
|
||||
String supplier() {
|
||||
ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getIfAvailable()
|
||||
.getProvider("service-wiremock", ServiceInstanceListSupplier.class).getIfAvailable();
|
||||
if (supplier instanceof CachingServiceInstanceListSupplier cachingSupplier) {
|
||||
return cachingSupplier.getDelegate().getClass().getSimpleName();
|
||||
}
|
||||
return supplier.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.k8s.client.loadbalancer;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@Configuration
|
||||
class KubernetesClientLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
WebClient.Builder client() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
debug: true
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.k8s.client.loadbalancer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.boot.test.json.BasicJsonTester;
|
||||
import org.springframework.cloud.kubernetes.client.loadbalancer.KubernetesClientServicesListSupplier;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;
|
||||
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithMerge;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class LoadBalancerIT {
|
||||
|
||||
private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(LoadBalancerIT.class);
|
||||
|
||||
private static final String BODY_FOR_MERGE = """
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "spring-cloud-kubernetes-k8s-client-loadbalancer",
|
||||
"env": [
|
||||
{
|
||||
"name": "SPRING_CLOUD_KUBERNETES_LOADBALANCER_MODE",
|
||||
"value": "SERVICE"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final Map<String, String> POD_LABELS = Map.of("app",
|
||||
"spring-cloud-kubernetes-k8s-client-loadbalancer");
|
||||
|
||||
private static final String SERVICE_URL = "http://localhost:80/loadbalancer-it/service";
|
||||
|
||||
private static final String SERVICE_INSTANCE_LIST_SUPPLIER_URL = "http://localhost:80/loadbalancer-it/supplier";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME = "spring-cloud-kubernetes-k8s-client-loadbalancer";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
private static Util util;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
|
||||
Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
|
||||
util = new Util(K3S);
|
||||
util.setUp(NAMESPACE);
|
||||
loadbalancerIt(Phase.CREATE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
loadbalancerIt(Phase.DELETE);
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
|
||||
Commons.systemPrune();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE, false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
void testLoadBalancerPodMode() {
|
||||
testLoadBalancer(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
void testLoadBalancerServiceMode() {
|
||||
patchForServiceMode();
|
||||
testLoadBalancer(false);
|
||||
}
|
||||
|
||||
private void testLoadBalancer(boolean podMode) {
|
||||
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl(SERVICE_URL).build();
|
||||
|
||||
String result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block();
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathArrayValue("$.mappings").isEmpty();
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathNumberValue("$.meta.total")
|
||||
.isEqualTo(0);
|
||||
|
||||
WebClient.Builder supplierBuilder = builder();
|
||||
WebClient supplierServiceClient = supplierBuilder.baseUrl(SERVICE_INSTANCE_LIST_SUPPLIER_URL).build();
|
||||
String supplierResult = supplierServiceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.block();
|
||||
if (podMode) {
|
||||
Assertions.assertThat(supplierResult)
|
||||
.isEqualTo(DiscoveryClientServiceInstanceListSupplier.class.getSimpleName());
|
||||
}
|
||||
else {
|
||||
Assertions.assertThat(supplierResult).isEqualTo(KubernetesClientServicesListSupplier.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadbalancerIt(Phase phase) {
|
||||
V1Deployment deployment = (V1Deployment) util
|
||||
.yaml("spring-cloud-kubernetes-k8s-client-loadbalancer-deployment.yaml");
|
||||
V1Service service = (V1Service) util.yaml("spring-cloud-kubernetes-k8s-client-loadbalancer-service.yaml");
|
||||
V1Ingress ingress = (V1Ingress) util.yaml("spring-cloud-kubernetes-k8s-client-loadbalancer-ingress.yaml");
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
}
|
||||
else if (phase.equals(Phase.DELETE)) {
|
||||
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
|
||||
}
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private static void patchForServiceMode() {
|
||||
patchWithMerge("spring-cloud-kubernetes-k8s-client-loadbalancer", LoadBalancerIT.NAMESPACE, BODY_FOR_MERGE,
|
||||
POD_LABELS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -1,31 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
spec:
|
||||
serviceAccountName: spring-cloud-kubernetes-serviceaccount
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
env:
|
||||
- name: SPRING_CLOUD_KUBERNETES_LOADBALANCER_MODE
|
||||
value: POD
|
||||
image: docker.io/springcloud/spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -1,19 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /loadbalancer-it/
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
name: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-k8s-client-loadbalancer
|
||||
type: ClusterIP
|
||||
Reference in New Issue
Block a user