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:
erabii
2024-04-03 20:01:24 +03:00
committed by GitHub
parent 17471a1ed6
commit 0acf32f7c7
18 changed files with 1349 additions and 368 deletions

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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