Skip to content

Commit cc43ce5

Browse files
authored
Add selective namespaces to fabric8 loadbalancer (#1604)
1 parent 75dffef commit cc43ce5

File tree

9 files changed

+670
-206
lines changed

9 files changed

+670
-206
lines changed

spring-cloud-kubernetes-fabric8-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplier.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.cloud.client.ServiceInstance;
2828
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
2929
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
30+
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
3031
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServicesListSupplier;
3132
import org.springframework.cloud.kubernetes.fabric8.Fabric8Utils;
3233
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
@@ -64,15 +65,30 @@ public Flux<List<ServiceInstance>> get() {
6465
LOG.debug(() -> "discovering services in all namespaces");
6566
List<Service> services = kubernetesClient.services().inAnyNamespace()
6667
.withField("metadata.name", serviceName).list().getItems();
67-
services.forEach(service -> result.add(mapper.map(service)));
68+
services.forEach(service -> addMappedService(mapper, result, service));
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+
Service service = kubernetesClient.services().inNamespace(selectiveNamespace).withName(serviceName)
75+
.get();
76+
if (service != null) {
77+
addMappedService(mapper, result, service);
78+
}
79+
else {
80+
LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : "
81+
+ selectiveNamespace);
82+
}
83+
});
6884
}
6985
else {
7086
String namespace = Fabric8Utils.getApplicationNamespace(kubernetesClient, null, "loadbalancer-service",
7187
namespaceProvider);
7288
LOG.debug(() -> "discovering services in namespace : " + namespace);
7389
Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get();
7490
if (service != null) {
75-
result.add(mapper.map(service));
91+
addMappedService(mapper, result, service);
7692
}
7793
else {
7894
LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " + namespace);
@@ -83,4 +99,9 @@ public Flux<List<ServiceInstance>> get() {
8399
return Flux.defer(() -> Flux.just(result));
84100
}
85101

102+
private void addMappedService(KubernetesServiceInstanceMapper<Service> mapper, List<ServiceInstance> services,
103+
Service service) {
104+
services.add(mapper.map(service));
105+
}
106+
86107
}

spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServicesListSupplierMockClientTests.java

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,18 @@ void testAllNamespaces(CapturedOutput output) {
8888

8989
List<List<ServiceInstance>> serviceInstances = supplier.get().collectList().block();
9090
Assertions.assertEquals(serviceInstances.size(), 1);
91-
List<ServiceInstance> inner = serviceInstances.get(0);
9291

9392
List<ServiceInstance> serviceInstancesSorted = serviceInstances.get(0).stream()
9493
.sorted(Comparator.comparing(ServiceInstance::getServiceId)).toList();
9594
Assertions.assertEquals(serviceInstancesSorted.size(), 2);
96-
Assertions.assertEquals(inner.get(0).getServiceId(), "service-a");
97-
Assertions.assertEquals(inner.get(0).getHost(), "service-a.a.svc.cluster.local");
98-
Assertions.assertEquals(inner.get(0).getPort(), 8887);
9995

100-
Assertions.assertEquals(inner.get(1).getServiceId(), "service-a");
101-
Assertions.assertEquals(inner.get(1).getHost(), "service-a.c.svc.cluster.local");
102-
Assertions.assertEquals(inner.get(1).getPort(), 8889);
96+
Assertions.assertEquals(serviceInstancesSorted.get(0).getServiceId(), "service-a");
97+
Assertions.assertEquals(serviceInstancesSorted.get(0).getHost(), "service-a.a.svc.cluster.local");
98+
Assertions.assertEquals(serviceInstancesSorted.get(0).getPort(), 8887);
99+
100+
Assertions.assertEquals(serviceInstancesSorted.get(1).getServiceId(), "service-a");
101+
Assertions.assertEquals(serviceInstancesSorted.get(1).getHost(), "service-a.c.svc.cluster.local");
102+
Assertions.assertEquals(serviceInstancesSorted.get(1).getPort(), 8889);
103103

104104
Assertions.assertTrue(output.getOut().contains("discovering services in all namespaces"));
105105
}
@@ -138,6 +138,42 @@ void testOneNamespace(CapturedOutput output) {
138138
Assertions.assertTrue(output.getOut().contains("discovering services in namespace : c"));
139139
}
140140

141+
@Test
142+
void testSelectiveNamespaces(CapturedOutput output) {
143+
144+
createService("a", "my-service", 8887);
145+
createService("b", "my-service", 8888);
146+
createService("c", "my-service", 8889);
147+
148+
Environment environment = new MockEnvironment().withProperty("loadbalancer.client.name", "my-service");
149+
boolean allNamespaces = false;
150+
Set<String> selectiveNamespaces = Set.of("a", "b");
151+
152+
KubernetesLoadBalancerProperties loadBalancerProperties = new KubernetesLoadBalancerProperties();
153+
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, allNamespaces,
154+
selectiveNamespaces, true, 60, false, null, Set.of(), Map.of(), null,
155+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false, false, null);
156+
157+
Fabric8ServicesListSupplier supplier = new Fabric8ServicesListSupplier(environment, mockClient,
158+
new Fabric8ServiceInstanceMapper(loadBalancerProperties, discoveryProperties), discoveryProperties);
159+
160+
List<List<ServiceInstance>> serviceInstances = supplier.get().collectList().block();
161+
Assertions.assertEquals(serviceInstances.size(), 1);
162+
163+
List<ServiceInstance> serviceInstancesSorted = serviceInstances.get(0).stream()
164+
.sorted(Comparator.comparing(ServiceInstance::getPort)).toList();
165+
Assertions.assertEquals(serviceInstancesSorted.size(), 2);
166+
Assertions.assertEquals(serviceInstancesSorted.get(0).getServiceId(), "my-service");
167+
Assertions.assertEquals(serviceInstancesSorted.get(0).getHost(), "my-service.a.svc.cluster.local");
168+
Assertions.assertEquals(serviceInstancesSorted.get(0).getPort(), 8887);
169+
170+
Assertions.assertEquals(serviceInstancesSorted.get(1).getServiceId(), "my-service");
171+
Assertions.assertEquals(serviceInstancesSorted.get(1).getHost(), "my-service.b.svc.cluster.local");
172+
Assertions.assertEquals(serviceInstancesSorted.get(1).getPort(), 8888);
173+
174+
Assertions.assertTrue(output.getOut().contains("discovering services in selective namespaces : [a, b]"));
175+
}
176+
141177
private void createService(String namespace, String name, int port) {
142178
Service service = new ServiceBuilder().withNewMetadata().withNamespace(namespace).withName(name).endMetadata()
143179
.withSpec(new ServiceSpecBuilder()

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

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,65 @@
1616

1717
package org.springframework.cloud.kubernetes.fabric8.loadbalancer.it;
1818

19+
import io.fabric8.kubernetes.api.model.EndpointAddressBuilder;
20+
import io.fabric8.kubernetes.api.model.EndpointPortBuilder;
21+
import io.fabric8.kubernetes.api.model.EndpointSubsetBuilder;
22+
import io.fabric8.kubernetes.api.model.Endpoints;
23+
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
24+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
1925
import io.fabric8.kubernetes.api.model.Service;
2026
import io.fabric8.kubernetes.api.model.ServiceBuilder;
2127
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
2228
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
2329

30+
import org.springframework.boot.SpringApplication;
31+
import org.springframework.boot.autoconfigure.SpringBootApplication;
32+
import org.springframework.boot.test.context.TestConfiguration;
33+
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.web.reactive.function.client.WebClient;
36+
2437
/**
2538
* @author wind57
2639
*/
27-
final class Util {
40+
public final class Util {
2841

2942
private Util() {
3043

3144
}
3245

33-
static Service createService(String namespace, String name, int port) {
46+
public static Service service(String namespace, String name, int port) {
3447
return new ServiceBuilder().withNewMetadata().withNamespace(namespace).withName(name).endMetadata()
3548
.withSpec(new ServiceSpecBuilder()
3649
.withPorts(new ServicePortBuilder().withName("http").withPort(port).build()).build())
3750
.build();
3851
}
3952

53+
public static Endpoints endpoints(int port, String host, String namespace) {
54+
return new EndpointsBuilder()
55+
.withSubsets(new EndpointSubsetBuilder().withPorts(new EndpointPortBuilder().withPort(port).build())
56+
.withAddresses(new EndpointAddressBuilder().withIp(host).build()).build())
57+
.withMetadata(new ObjectMetaBuilder().withName("random-name").withNamespace(namespace).build()).build();
58+
}
59+
60+
@TestConfiguration
61+
public static class LoadBalancerConfiguration {
62+
63+
@Bean
64+
@LoadBalanced
65+
WebClient.Builder client() {
66+
return WebClient.builder();
67+
}
68+
69+
}
70+
71+
@SpringBootApplication
72+
public static class Configuration {
73+
74+
public static void main(String[] args) {
75+
SpringApplication.run(Configuration.class);
76+
}
77+
78+
}
79+
4080
}

spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/PodModeAllNamespacesTest.java renamed to spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/it/mode/pod/AllNamespacesTest.java

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.cloud.kubernetes.fabric8.loadbalancer.it;
17+
package org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.mode.pod;
1818

1919
import com.github.tomakehurst.wiremock.WireMockServer;
2020
import com.github.tomakehurst.wiremock.client.WireMock;
21-
import io.fabric8.kubernetes.api.model.EndpointAddressBuilder;
22-
import io.fabric8.kubernetes.api.model.EndpointPortBuilder;
23-
import io.fabric8.kubernetes.api.model.EndpointSubsetBuilder;
2421
import io.fabric8.kubernetes.api.model.Endpoints;
25-
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
2622
import io.fabric8.kubernetes.api.model.EndpointsListBuilder;
27-
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
2823
import io.fabric8.kubernetes.api.model.Service;
2924
import io.fabric8.kubernetes.client.Config;
3025
import io.fabric8.kubernetes.client.utils.Serialization;
@@ -37,23 +32,19 @@
3732

3833
import org.springframework.beans.factory.ObjectProvider;
3934
import org.springframework.beans.factory.annotation.Autowired;
40-
import org.springframework.boot.SpringApplication;
41-
import org.springframework.boot.autoconfigure.SpringBootApplication;
4235
import org.springframework.boot.test.context.SpringBootTest;
43-
import org.springframework.boot.test.context.TestConfiguration;
44-
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
4536
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServiceInstanceMapper;
37+
import org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util;
4638
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
4739
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
4840
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
4941
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
50-
import org.springframework.context.annotation.Bean;
5142
import org.springframework.http.HttpMethod;
5243
import org.springframework.web.reactive.function.client.WebClient;
5344

5445
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
55-
import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.PodModeAllNamespacesTest.Configuration;
56-
import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.PodModeAllNamespacesTest.LoadBalancerConfiguration;
46+
import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.Configuration;
47+
import static org.springframework.cloud.kubernetes.fabric8.loadbalancer.it.Util.LoadBalancerConfiguration;
5748

5849
/**
5950
* @author wind57
@@ -62,7 +53,7 @@
6253
properties = { "spring.cloud.kubernetes.loadbalancer.mode=POD", "spring.main.cloud-platform=KUBERNETES",
6354
"spring.cloud.kubernetes.discovery.all-namespaces=true" },
6455
classes = { LoadBalancerConfiguration.class, Configuration.class })
65-
class PodModeAllNamespacesTest {
56+
class AllNamespacesTest {
6657

6758
private static final String SERVICE_A_URL = "http://service-a";
6859

@@ -137,22 +128,11 @@ static void afterAll() {
137128
@Test
138129
void test() {
139130

140-
Service serviceA = Util.createService("a", "service-a", SERVICE_A_PORT);
141-
Service serviceB = Util.createService("b", "service-b", SERVICE_B_PORT);
131+
Service serviceA = Util.service("a", "service-a", SERVICE_A_PORT);
132+
Service serviceB = Util.service("b", "service-b", SERVICE_B_PORT);
142133

143-
Endpoints endpointsA = new EndpointsBuilder()
144-
.withSubsets(new EndpointSubsetBuilder()
145-
.withPorts(new EndpointPortBuilder().withPort(SERVICE_A_PORT).build())
146-
.withAddresses(new EndpointAddressBuilder().withIp("127.0.0.1").build()).build())
147-
.withMetadata(new ObjectMetaBuilder().withName("no-port-name-service").withNamespace("a").build())
148-
.build();
149-
150-
Endpoints endpointsB = new EndpointsBuilder()
151-
.withSubsets(new EndpointSubsetBuilder()
152-
.withPorts(new EndpointPortBuilder().withPort(SERVICE_B_PORT).build())
153-
.withAddresses(new EndpointAddressBuilder().withIp("127.0.0.1").build()).build())
154-
.withMetadata(new ObjectMetaBuilder().withName("no-port-name-service").withNamespace("b").build())
155-
.build();
134+
Endpoints endpointsA = Util.endpoints(SERVICE_A_PORT, "127.0.0.1", "a");
135+
Endpoints endpointsB = Util.endpoints(SERVICE_B_PORT, "127.0.0.1", "b");
156136

157137
String endpointsAListAsString = Serialization.asJson(new EndpointsListBuilder().withItems(endpointsA).build());
158138
String endpointsBListAsString = Serialization.asJson(new EndpointsListBuilder().withItems(endpointsB).build());
@@ -192,26 +172,18 @@ void test() {
192172
.getIfAvailable().getProvider("service-a", ServiceInstanceListSupplier.class).getIfAvailable();
193173
Assertions.assertThat(supplier.getDelegate().getClass())
194174
.isSameAs(DiscoveryClientServiceInstanceListSupplier.class);
195-
}
196-
197-
@TestConfiguration
198-
static class LoadBalancerConfiguration {
199175

200-
@Bean
201-
@LoadBalanced
202-
WebClient.Builder client() {
203-
return WebClient.builder();
204-
}
205-
206-
}
176+
wireMockServer.verify(WireMock.exactly(1), WireMock
177+
.getRequestedFor(WireMock.urlEqualTo("/api/v1/endpoints?fieldSelector=metadata.name%3Dservice-a")));
207178

208-
@SpringBootApplication
209-
static class Configuration {
179+
wireMockServer.verify(WireMock.exactly(1), WireMock
180+
.getRequestedFor(WireMock.urlEqualTo("/api/v1/endpoints?fieldSelector=metadata.name%3Dservice-b")));
210181

211-
public static void main(String[] args) {
212-
SpringApplication.run(ServiceModeAllNamespacesTest.Configuration.class);
213-
}
182+
wireMockServer.verify(WireMock.exactly(1),
183+
WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/a/services/service-a")));
214184

185+
wireMockServer.verify(WireMock.exactly(1),
186+
WireMock.getRequestedFor(WireMock.urlEqualTo("/api/v1/namespaces/b/services/service-b")));
215187
}
216188

217189
}

0 commit comments

Comments
 (0)