Skip to content

Commit ba18563

Browse files
committed
Merge branch '3.2.x' into 3.3.x
2 parents 1f42676 + 9432ef8 commit ba18563

File tree

27 files changed

+1056
-231
lines changed

27 files changed

+1056
-231
lines changed

spring-cloud-kubernetes-client-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplier.java

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,34 @@ public KubernetesClientServicesListSupplier(Environment environment,
5757

5858
@Override
5959
public Flux<List<ServiceInstance>> get() {
60-
List<ServiceInstance> result = new ArrayList<>();
61-
String serviceName = getServiceId();
62-
LOG.debug(() -> "serviceID : " + serviceName);
63-
64-
if (discoveryProperties.allNamespaces()) {
65-
LOG.debug(() -> "discovering services in all namespaces");
66-
List<V1Service> services = services(null, serviceName);
67-
services.forEach(service -> addMappedService(mapper, result, service));
68-
}
69-
else if (!discoveryProperties.namespaces().isEmpty()) {
70-
List<String> selectiveNamespaces = discoveryProperties.namespaces().stream().sorted().toList();
71-
LOG.debug(() -> "discovering services in selective namespaces : " + selectiveNamespaces);
72-
selectiveNamespaces.forEach(selectiveNamespace -> {
73-
List<V1Service> services = services(selectiveNamespace, serviceName);
60+
return Flux.defer(() -> {
61+
List<ServiceInstance> result = new ArrayList<>();
62+
String serviceName = getServiceId();
63+
LOG.debug(() -> "serviceID : " + serviceName);
64+
65+
if (discoveryProperties.allNamespaces()) {
66+
LOG.debug(() -> "discovering services in all namespaces");
67+
List<V1Service> services = services(null, serviceName);
7468
services.forEach(service -> addMappedService(mapper, result, service));
75-
});
76-
}
77-
else {
78-
String namespace = getApplicationNamespace(null, "loadbalancer-service", kubernetesNamespaceProvider);
79-
LOG.debug(() -> "discovering services in namespace : " + namespace);
80-
List<V1Service> services = services(namespace, serviceName);
81-
services.forEach(service -> addMappedService(mapper, result, service));
82-
}
69+
}
70+
else if (!discoveryProperties.namespaces().isEmpty()) {
71+
List<String> selectiveNamespaces = discoveryProperties.namespaces().stream().sorted().toList();
72+
LOG.debug(() -> "discovering services in selective namespaces : " + selectiveNamespaces);
73+
selectiveNamespaces.forEach(selectiveNamespace -> {
74+
List<V1Service> services = services(selectiveNamespace, serviceName);
75+
services.forEach(service -> addMappedService(mapper, result, service));
76+
});
77+
}
78+
else {
79+
String namespace = getApplicationNamespace(null, "loadbalancer-service", kubernetesNamespaceProvider);
80+
LOG.debug(() -> "discovering services in namespace : " + namespace);
81+
List<V1Service> services = services(namespace, serviceName);
82+
services.forEach(service -> addMappedService(mapper, result, service));
83+
}
8384

84-
LOG.debug(() -> "found services : " + result);
85-
return Flux.just(result);
85+
LOG.debug(() -> "found services : " + result);
86+
return Flux.just(result);
87+
});
8688
}
8789

8890
private void addMappedService(KubernetesServiceInstanceMapper<V1Service> mapper, List<ServiceInstance> services,

spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Util.java

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,16 @@
2525
import io.kubernetes.client.openapi.models.V1Endpoints;
2626
import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
2727
import io.kubernetes.client.openapi.models.V1EndpointsList;
28+
import io.kubernetes.client.openapi.models.V1EndpointsListBuilder;
29+
import io.kubernetes.client.openapi.models.V1ListMetaBuilder;
2830
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
2931
import io.kubernetes.client.openapi.models.V1Service;
3032
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
3133
import io.kubernetes.client.openapi.models.V1ServiceList;
34+
import io.kubernetes.client.openapi.models.V1ServiceListBuilder;
3235
import io.kubernetes.client.openapi.models.V1ServicePortBuilder;
3336
import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
3437

35-
import org.springframework.boot.SpringApplication;
36-
import org.springframework.boot.autoconfigure.SpringBootApplication;
37-
import org.springframework.boot.test.context.TestConfiguration;
38-
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
39-
import org.springframework.context.annotation.Bean;
40-
import org.springframework.web.reactive.function.client.WebClient;
41-
4238
/**
4339
* @author wind57
4440
*/
@@ -142,24 +138,26 @@ public static void endpointsInNamespaceServiceMode(WireMockServer server, V1Endp
142138
.willReturn(WireMock.aResponse().withBody(new JSON().serialize(endpointsList)).withStatus(200)));
143139
}
144140

145-
@TestConfiguration
146-
public static class LoadBalancerConfiguration {
141+
public static void mockWatchers(WireMockServer wireMockServer) {
142+
V1Service serviceA = Util.service("a", "service-a", 8888);
147143

148-
@Bean
149-
@LoadBalanced
150-
WebClient.Builder client() {
151-
return WebClient.builder();
152-
}
144+
V1ServiceList serviceListA = new V1ServiceListBuilder()
145+
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build())
146+
.endMetadata()
147+
.withItems(serviceA)
148+
.build();
153149

154-
}
150+
servicesInNamespaceServiceMode(wireMockServer, serviceListA, "a", "service-a");
155151

156-
@SpringBootApplication
157-
public static class Configuration {
152+
V1Endpoints endpointsA = Util.endpoints("a", "service-a", 8888, "127.0.0.1");
158153

159-
public static void main(String[] args) {
160-
SpringApplication.run(Configuration.class);
161-
}
154+
V1EndpointsList endpointsListA = new V1EndpointsListBuilder()
155+
.withNewMetadataLike(new V1ListMetaBuilder().withResourceVersion("0").build())
156+
.endMetadata()
157+
.withItems(endpointsA)
158+
.build();
162159

160+
Util.endpointsInNamespaceServiceMode(wireMockServer, endpointsListA, "a", "service-a");
163161
}
164162

165163
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2013-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode;
18+
19+
import org.springframework.boot.SpringApplication;
20+
import org.springframework.boot.autoconfigure.SpringBootApplication;
21+
22+
/**
23+
* @author wind57
24+
*/
25+
@SpringBootApplication
26+
public class App {
27+
28+
public static void main(String[] args) {
29+
SpringApplication.run(App.class);
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2013-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode;
18+
19+
import org.springframework.boot.test.context.TestConfiguration;
20+
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.web.reactive.function.client.WebClient;
23+
24+
/**
25+
* @author wind57
26+
*/
27+
@TestConfiguration
28+
public class LoadBalancerConfiguration {
29+
30+
@Bean
31+
@LoadBalanced
32+
WebClient.Builder client() {
33+
return WebClient.builder();
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2013-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.cache;
18+
19+
import com.github.tomakehurst.wiremock.WireMockServer;
20+
import com.github.tomakehurst.wiremock.client.WireMock;
21+
import io.kubernetes.client.openapi.ApiClient;
22+
import io.kubernetes.client.util.ClientBuilder;
23+
import org.junit.jupiter.api.AfterAll;
24+
import org.junit.jupiter.api.BeforeAll;
25+
import org.junit.jupiter.api.Test;
26+
import org.mockito.MockedStatic;
27+
import org.mockito.Mockito;
28+
import reactor.core.publisher.Mono;
29+
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.boot.test.context.SpringBootTest;
32+
import org.springframework.cloud.client.ServiceInstance;
33+
import org.springframework.cloud.client.loadbalancer.Response;
34+
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
35+
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
36+
import org.springframework.cloud.kubernetes.client.loadbalancer.it.Util;
37+
import org.springframework.cloud.kubernetes.client.loadbalancer.it.mode.App;
38+
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
39+
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
40+
import org.springframework.test.annotation.DirtiesContext;
41+
42+
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
43+
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.mockito.Mockito.mockStatic;
45+
46+
/**
47+
* @author wind57
48+
*/
49+
@SpringBootTest(properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE",
50+
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.discovery.all-namespaces=false",
51+
"spring.cloud.kubernetes.client.namespace=a", "spring.cloud.loadbalancer.cache.enabled=false" },
52+
classes = App.class)
53+
@DirtiesContext
54+
class CacheDisabledTest {
55+
56+
private static final int SERVICE_PORT = 8888;
57+
58+
private static WireMockServer wireMockServer;
59+
60+
private static WireMockServer serviceAMockServer;
61+
62+
private static MockedStatic<KubernetesClientUtils> clientUtils;
63+
64+
@SuppressWarnings("rawtypes")
65+
private static final MockedStatic<KubernetesServiceInstanceMapper> MOCKED_STATIC = Mockito
66+
.mockStatic(KubernetesServiceInstanceMapper.class);
67+
68+
@Autowired
69+
private LoadBalancerClientFactory loadBalancerClientFactory;
70+
71+
@BeforeAll
72+
static void beforeAll() {
73+
74+
wireMockServer = new WireMockServer(options().dynamicPort());
75+
wireMockServer.start();
76+
WireMock.configureFor("localhost", wireMockServer.port());
77+
78+
Util.mockWatchers(wireMockServer);
79+
80+
serviceAMockServer = new WireMockServer(SERVICE_PORT);
81+
serviceAMockServer.start();
82+
WireMock.configureFor("localhost", SERVICE_PORT);
83+
84+
// we mock host creation so that it becomes something like : localhost:8888
85+
// then wiremock can catch this request, and we can assert for the result
86+
MOCKED_STATIC.when(() -> KubernetesServiceInstanceMapper.createHost("my-service", "a", "cluster.local"))
87+
.thenReturn("localhost");
88+
89+
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
90+
// we need to not mock 'getApplicationNamespace'
91+
clientUtils = mockStatic(KubernetesClientUtils.class, Mockito.CALLS_REAL_METHODS);
92+
clientUtils.when(KubernetesClientUtils::kubernetesApiClient).thenReturn(client);
93+
}
94+
95+
@AfterAll
96+
static void afterAll() {
97+
wireMockServer.stop();
98+
serviceAMockServer.stop();
99+
MOCKED_STATIC.close();
100+
clientUtils.close();
101+
}
102+
103+
/**
104+
* <pre>
105+
* - we disable caching via 'spring.cloud.loadbalancer.cache.enabled=false'
106+
* - as such, two calls to : loadBalancer.choose() will both execute
107+
* on the delegate itself, which we assert via 'wireMockServer.verify'
108+
* </pre>
109+
*/
110+
@Test
111+
void test() {
112+
113+
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance("service-a");
114+
Response<ServiceInstance> firstResponse = Mono.from(loadBalancer.choose()).block();
115+
assertThat(firstResponse.hasServer()).isTrue();
116+
117+
Response<ServiceInstance> secondResponse = Mono.from(loadBalancer.choose()).block();
118+
assertThat(secondResponse.hasServer()).isTrue();
119+
120+
// called two times
121+
wireMockServer.verify(WireMock.exactly(2), WireMock.getRequestedFor(
122+
WireMock.urlEqualTo("/api/v1/namespaces/a/services?fieldSelector=metadata.name%3D" + "service-a")));
123+
124+
}
125+
126+
}

0 commit comments

Comments
 (0)