Skip to content

Commit 0bd7bc5

Browse files
authored
Add namespace provider to fabric8 loadbalancer (#1597)
1 parent 19fa6ab commit 0bd7bc5

File tree

12 files changed

+1052
-12
lines changed

12 files changed

+1052
-12
lines changed

spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesClientServicesFunctionProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2023 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ public static KubernetesClientServicesFunction servicesFunction(KubernetesDiscov
4646

4747
}
4848

49+
@Deprecated(forRemoval = true)
4950
public static KubernetesClientServicesFunction servicesFunction(KubernetesDiscoveryProperties properties,
5051
Binder binder, BindHandler bindHandler) {
5152
return servicesFunction(properties, new KubernetesNamespaceProvider(binder, bindHandler));

spring-cloud-kubernetes-fabric8-loadbalancer/pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,35 @@
4040
<groupId>org.springframework.boot</groupId>
4141
<artifactId>spring-boot-starter-test</artifactId>
4242
<scope>test</scope>
43+
<exclusions>
44+
<exclusion>
45+
<groupId>org.mokito</groupId>
46+
<artifactId>mockito-core</artifactId>
47+
</exclusion>
48+
</exclusions>
4349
</dependency>
50+
51+
<dependency>
52+
<groupId>org.mockito</groupId>
53+
<artifactId>mockito-inline</artifactId>
54+
<scope>test</scope>
55+
</dependency>
56+
4457
<dependency>
4558
<groupId>io.fabric8</groupId>
4659
<artifactId>kubernetes-server-mock</artifactId>
4760
<scope>test</scope>
4861
</dependency>
62+
<dependency>
63+
<groupId>org.springframework.boot</groupId>
64+
<artifactId>spring-boot-starter-webflux</artifactId>
65+
<scope>test</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>org.wiremock</groupId>
69+
<artifactId>wiremock-standalone</artifactId>
70+
<scope>test</scope>
71+
</dependency>
4972
<dependency>
5073
<groupId>org.springframework.cloud</groupId>
5174
<artifactId>spring-cloud-kubernetes-test-support</artifactId>

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2020 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,14 +21,17 @@
2121

2222
import io.fabric8.kubernetes.api.model.Service;
2323
import io.fabric8.kubernetes.client.KubernetesClient;
24+
import org.apache.commons.logging.LogFactory;
2425
import reactor.core.publisher.Flux;
2526

2627
import org.springframework.cloud.client.ServiceInstance;
28+
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
2729
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
2830
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesServicesListSupplier;
31+
import org.springframework.cloud.kubernetes.fabric8.Fabric8Utils;
2932
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
3033
import org.springframework.core.env.Environment;
31-
import org.springframework.util.StringUtils;
34+
import org.springframework.core.log.LogAccessor;
3235

3336
/**
3437
* Implementation of {@link ServiceInstanceListSupplier} for load balancer in SERVICE
@@ -38,30 +41,45 @@
3841
*/
3942
public class Fabric8ServicesListSupplier extends KubernetesServicesListSupplier<Service> {
4043

44+
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8ServicesListSupplier.class));
45+
4146
private final KubernetesClient kubernetesClient;
4247

48+
private final KubernetesNamespaceProvider namespaceProvider;
49+
4350
Fabric8ServicesListSupplier(Environment environment, KubernetesClient kubernetesClient,
4451
Fabric8ServiceInstanceMapper mapper, KubernetesDiscoveryProperties discoveryProperties) {
4552
super(environment, mapper, discoveryProperties);
4653
this.kubernetesClient = kubernetesClient;
54+
namespaceProvider = new KubernetesNamespaceProvider(environment);
4755
}
4856

4957
@Override
5058
public Flux<List<ServiceInstance>> get() {
5159
List<ServiceInstance> result = new ArrayList<>();
60+
String serviceName = getServiceId();
61+
LOG.debug(() -> "serviceID : " + serviceName);
62+
5263
if (discoveryProperties.allNamespaces()) {
64+
LOG.debug(() -> "discovering services in all namespaces");
5365
List<Service> services = kubernetesClient.services().inAnyNamespace()
54-
.withField("metadata.name", getServiceId()).list().getItems();
66+
.withField("metadata.name", serviceName).list().getItems();
5567
services.forEach(service -> result.add(mapper.map(service)));
5668
}
5769
else {
58-
Service service = StringUtils.hasText(kubernetesClient.getNamespace()) ? kubernetesClient.services()
59-
.inNamespace(kubernetesClient.getNamespace()).withName(getServiceId()).get()
60-
: kubernetesClient.services().withName(getServiceId()).get();
70+
String namespace = Fabric8Utils.getApplicationNamespace(kubernetesClient, null, "loadbalancer-service",
71+
namespaceProvider);
72+
LOG.debug(() -> "discovering services in namespace : " + namespace);
73+
Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get();
6174
if (service != null) {
6275
result.add(mapper.map(service));
6376
}
77+
else {
78+
LOG.debug(() -> "did not find service with name : " + serviceName + " in namespace : " + namespace);
79+
}
6480
}
81+
82+
LOG.debug(() -> "found services : " + result);
6583
return Flux.defer(() -> Flux.just(result));
6684
}
6785

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2023 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -78,7 +78,6 @@ void testPositiveMatch() {
7878
KubernetesServicesListSupplier<Service> supplier = new Fabric8ServicesListSupplier(environment, client, mapper,
7979
KubernetesDiscoveryProperties.DEFAULT);
8080
List<ServiceInstance> instances = supplier.get().blockFirst();
81-
assert instances != null;
8281
Assertions.assertEquals(1, instances.size());
8382
}
8483

@@ -98,7 +97,6 @@ void testPositiveMatchAllNamespaces() {
9897
KubernetesServicesListSupplier<Service> supplier = new Fabric8ServicesListSupplier(environment, client, mapper,
9998
discoveryProperties);
10099
List<ServiceInstance> instances = supplier.get().blockFirst();
101-
assert instances != null;
102100
Assertions.assertEquals(1, instances.size());
103101
}
104102

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2013-2024 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.fabric8.loadbalancer;
18+
19+
import java.util.Comparator;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Set;
23+
24+
import io.fabric8.kubernetes.api.model.Service;
25+
import io.fabric8.kubernetes.api.model.ServiceBuilder;
26+
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
27+
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
28+
import io.fabric8.kubernetes.client.Config;
29+
import io.fabric8.kubernetes.client.KubernetesClient;
30+
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
31+
import org.junit.jupiter.api.AfterEach;
32+
import org.junit.jupiter.api.Assertions;
33+
import org.junit.jupiter.api.BeforeAll;
34+
import org.junit.jupiter.api.Test;
35+
import org.junit.jupiter.api.extension.ExtendWith;
36+
37+
import org.springframework.boot.test.system.CapturedOutput;
38+
import org.springframework.boot.test.system.OutputCaptureExtension;
39+
import org.springframework.cloud.client.ServiceInstance;
40+
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
41+
import org.springframework.cloud.kubernetes.commons.loadbalancer.KubernetesLoadBalancerProperties;
42+
import org.springframework.core.env.Environment;
43+
import org.springframework.mock.env.MockEnvironment;
44+
45+
/**
46+
* @author wind57
47+
*/
48+
@EnableKubernetesMockClient(crud = true, https = false)
49+
@ExtendWith(OutputCaptureExtension.class)
50+
class Fabric8ServicesListSupplierMockClientTests {
51+
52+
private static KubernetesClient mockClient;
53+
54+
@BeforeAll
55+
static void setUpBeforeClass() {
56+
// Configure the kubernetes master url to point to the mock server
57+
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
58+
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
59+
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
60+
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
61+
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
62+
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
63+
}
64+
65+
@AfterEach
66+
void afterEach() {
67+
mockClient.services().inAnyNamespace().delete();
68+
}
69+
70+
@Test
71+
void testAllNamespaces(CapturedOutput output) {
72+
73+
createService("a", "service-a", 8887);
74+
createService("b", "service-b", 8888);
75+
createService("c", "service-a", 8889);
76+
77+
Environment environment = new MockEnvironment().withProperty("loadbalancer.client.name", "service-a");
78+
boolean allNamespaces = true;
79+
Set<String> selectiveNamespaces = Set.of();
80+
81+
KubernetesLoadBalancerProperties loadBalancerProperties = new KubernetesLoadBalancerProperties();
82+
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, allNamespaces,
83+
selectiveNamespaces, true, 60, false, null, Set.of(), Map.of(), null,
84+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false, false, null);
85+
86+
Fabric8ServicesListSupplier supplier = new Fabric8ServicesListSupplier(environment, mockClient,
87+
new Fabric8ServiceInstanceMapper(loadBalancerProperties, discoveryProperties), discoveryProperties);
88+
89+
List<List<ServiceInstance>> serviceInstances = supplier.get().collectList().block();
90+
Assertions.assertEquals(serviceInstances.size(), 1);
91+
List<ServiceInstance> inner = serviceInstances.get(0);
92+
93+
List<ServiceInstance> serviceInstancesSorted = serviceInstances.get(0).stream()
94+
.sorted(Comparator.comparing(ServiceInstance::getServiceId)).toList();
95+
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);
99+
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);
103+
104+
Assertions.assertTrue(output.getOut().contains("discovering services in all namespaces"));
105+
}
106+
107+
@Test
108+
void testOneNamespace(CapturedOutput output) {
109+
110+
createService("a", "service-c", 8887);
111+
createService("b", "service-b", 8888);
112+
createService("c", "service-c", 8889);
113+
114+
Environment environment = new MockEnvironment().withProperty("spring.cloud.kubernetes.client.namespace", "c")
115+
.withProperty("loadbalancer.client.name", "service-c");
116+
boolean allNamespaces = false;
117+
Set<String> selectiveNamespaces = Set.of();
118+
119+
KubernetesLoadBalancerProperties loadBalancerProperties = new KubernetesLoadBalancerProperties();
120+
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, allNamespaces,
121+
selectiveNamespaces, true, 60, false, null, Set.of(), Map.of(), null,
122+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false, false, null);
123+
124+
Fabric8ServicesListSupplier supplier = new Fabric8ServicesListSupplier(environment, mockClient,
125+
new Fabric8ServiceInstanceMapper(loadBalancerProperties, discoveryProperties), discoveryProperties);
126+
127+
List<List<ServiceInstance>> serviceInstances = supplier.get().collectList().block();
128+
Assertions.assertEquals(serviceInstances.size(), 1);
129+
List<ServiceInstance> inner = serviceInstances.get(0);
130+
131+
List<ServiceInstance> serviceInstancesSorted = serviceInstances.get(0).stream()
132+
.sorted(Comparator.comparing(ServiceInstance::getServiceId)).toList();
133+
Assertions.assertEquals(serviceInstancesSorted.size(), 1);
134+
Assertions.assertEquals(inner.get(0).getServiceId(), "service-c");
135+
Assertions.assertEquals(inner.get(0).getHost(), "service-c.c.svc.cluster.local");
136+
Assertions.assertEquals(inner.get(0).getPort(), 8889);
137+
138+
Assertions.assertTrue(output.getOut().contains("discovering services in namespace : c"));
139+
}
140+
141+
private void createService(String namespace, String name, int port) {
142+
Service service = new ServiceBuilder().withNewMetadata().withNamespace(namespace).withName(name).endMetadata()
143+
.withSpec(new ServiceSpecBuilder()
144+
.withPorts(new ServicePortBuilder().withName("http").withPort(port).build()).build())
145+
.build();
146+
mockClient.services().resource(service).create();
147+
}
148+
149+
}

0 commit comments

Comments
 (0)