Skip to content

Commit c33e0ff

Browse files
committed
Merge branch 'geoand-284'
2 parents eb73e05 + 740c4fd commit c33e0ff

File tree

6 files changed

+183
-25
lines changed

6 files changed

+183
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2013-2018 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+
* http://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.discovery;
18+
19+
import io.fabric8.kubernetes.api.model.Service;
20+
import io.fabric8.kubernetes.api.model.ServiceList;
21+
import io.fabric8.kubernetes.client.KubernetesClient;
22+
import io.fabric8.kubernetes.client.Watch;
23+
import io.fabric8.kubernetes.client.Watcher;
24+
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
25+
26+
import java.util.function.Function;
27+
28+
/**
29+
* A regular java.util.function that is used to hide the complexity of the KubernetesClient interfaces
30+
*
31+
* It's meant to be used to abstract things like:
32+
*
33+
* client.services()
34+
* client.services().withLabel("key", "value")
35+
* client.services().withoutLabel("key")
36+
*
37+
* The result of the application of the function can then be used for example to list the services like so:
38+
*
39+
* function.apply(client).list()
40+
*
41+
* See KubernetesDiscoveryClientAutoConfiguration.servicesFunction
42+
*/
43+
public interface KubernetesClientServicesFunction extends
44+
Function<KubernetesClient, FilterWatchListDeletable<Service, ServiceList, Boolean, Watch, Watcher<Service>>> {
45+
}

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@
1616
*/
1717
package org.springframework.cloud.kubernetes.discovery;
1818

19-
import java.util.ArrayList;
20-
import java.util.HashMap;
21-
import java.util.List;
22-
import java.util.Map;
23-
import java.util.function.Predicate;
24-
import java.util.stream.Collectors;
25-
2619
import io.fabric8.kubernetes.api.model.EndpointAddress;
2720
import io.fabric8.kubernetes.api.model.EndpointPort;
2821
import io.fabric8.kubernetes.api.model.EndpointSubset;
@@ -31,7 +24,6 @@
3124
import io.fabric8.kubernetes.client.KubernetesClient;
3225
import org.apache.commons.logging.Log;
3326
import org.apache.commons.logging.LogFactory;
34-
3527
import org.springframework.cloud.client.ServiceInstance;
3628
import org.springframework.cloud.client.discovery.DiscoveryClient;
3729
import org.springframework.expression.Expression;
@@ -40,6 +32,13 @@
4032
import org.springframework.util.Assert;
4133
import org.springframework.util.StringUtils;
4234

35+
import java.util.ArrayList;
36+
import java.util.HashMap;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.function.Predicate;
40+
import java.util.stream.Collectors;
41+
4342
import static java.util.stream.Collectors.toMap;
4443

4544
public class KubernetesDiscoveryClient implements DiscoveryClient {
@@ -50,24 +49,29 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
5049
private final KubernetesDiscoveryProperties properties;
5150
private final DefaultIsServicePortSecureResolver isServicePortSecureResolver;
5251

52+
private final KubernetesClientServicesFunction kubernetesClientServicesFunction;
5353
private final SpelExpressionParser parser = new SpelExpressionParser();
5454
private final SimpleEvaluationContext evalCtxt = SimpleEvaluationContext
5555
.forReadOnlyDataBinding()
5656
.withInstanceMethods()
5757
.build();
5858

5959
public KubernetesDiscoveryClient(KubernetesClient client,
60-
KubernetesDiscoveryProperties kubernetesDiscoveryProperties) {
60+
KubernetesDiscoveryProperties kubernetesDiscoveryProperties,
61+
KubernetesClientServicesFunction kubernetesClientServicesFunction) {
6162

62-
this(client, kubernetesDiscoveryProperties, new DefaultIsServicePortSecureResolver(kubernetesDiscoveryProperties));
63+
this(client, kubernetesDiscoveryProperties, kubernetesClientServicesFunction,
64+
new DefaultIsServicePortSecureResolver(kubernetesDiscoveryProperties));
6365
}
6466

6567
KubernetesDiscoveryClient(KubernetesClient client,
66-
KubernetesDiscoveryProperties kubernetesDiscoveryProperties,
67-
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
68+
KubernetesDiscoveryProperties kubernetesDiscoveryProperties,
69+
KubernetesClientServicesFunction kubernetesClientServicesFunction,
70+
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
6871

6972
this.client = client;
7073
this.properties = kubernetesDiscoveryProperties;
74+
this.kubernetesClientServicesFunction = kubernetesClientServicesFunction;
7175
this.isServicePortSecureResolver = isServicePortSecureResolver;
7276
}
7377

@@ -191,7 +195,7 @@ public List<String> getServices() {
191195
}
192196

193197
public List<String> getServices(Predicate<Service> filter) {
194-
return client.services().list()
198+
return kubernetesClientServicesFunction.apply(client).list()
195199
.getItems()
196200
.stream()
197201
.filter(filter)

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,23 @@ public DefaultIsServicePortSecureResolver isServicePortSecureResolver(Kubernetes
4040
return new DefaultIsServicePortSecureResolver(properties);
4141
}
4242

43+
@Bean
44+
public KubernetesClientServicesFunction servicesFunction(KubernetesDiscoveryProperties properties) {
45+
if (properties.getServiceLabels().isEmpty()) {
46+
return KubernetesClient::services;
47+
}
48+
49+
return (client) -> client.services().withLabels(properties.getServiceLabels());
50+
}
51+
4352
@Bean
4453
@ConditionalOnMissingBean
4554
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.enabled",matchIfMissing = true)
46-
public KubernetesDiscoveryClient kubernetesDiscoveryClient(
47-
KubernetesClient client, KubernetesDiscoveryProperties properties,
48-
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
49-
return new KubernetesDiscoveryClient(client, properties, isServicePortSecureResolver);
55+
public KubernetesDiscoveryClient kubernetesDiscoveryClient(KubernetesClient client,
56+
KubernetesDiscoveryProperties properties,
57+
KubernetesClientServicesFunction kubernetesClientServicesFunction,
58+
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
59+
return new KubernetesDiscoveryClient(client, properties, kubernetesClientServicesFunction, isServicePortSecureResolver);
5060
}
5161

5262
@Bean

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import org.springframework.boot.context.properties.ConfigurationProperties;
2222
import org.springframework.core.style.ToStringCreator;
2323

24-
import java.util.ArrayList;
24+
import java.util.HashMap;
2525
import java.util.HashSet;
26-
import java.util.List;
26+
import java.util.Map;
2727
import java.util.Set;
2828

2929
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
@@ -36,7 +36,7 @@ public class KubernetesDiscoveryProperties {
3636
@Value("${spring.application.name:unknown}")
3737
private String serviceName = "unknown";
3838

39-
/** SpEL expression to filter services. */
39+
/** SpEL expression to filter services AFTER they have been retrieved from the Kubernetes API server. */
4040
private String filter;
4141

4242

@@ -46,6 +46,9 @@ public class KubernetesDiscoveryProperties {
4646
add(8443);
4747
}};
4848

49+
/** If set, then only the services matching these labels will be fetched from the Kubernetes API server */
50+
private Map<String, String> serviceLabels = new HashMap<>();
51+
4952
private Metadata metadata = new Metadata();
5053

5154
public boolean isEnabled() {
@@ -80,6 +83,14 @@ public void setKnownSecurePorts(Set<Integer> knownSecurePorts) {
8083
this.knownSecurePorts = knownSecurePorts;
8184
}
8285

86+
public Map<String, String> getServiceLabels() {
87+
return serviceLabels;
88+
}
89+
90+
public void setServiceLabels(Map<String, String> serviceLabels) {
91+
this.serviceLabels = serviceLabels;
92+
}
93+
8394
public Metadata getMetadata() {
8495
return metadata;
8596
}
@@ -94,6 +105,8 @@ public String toString() {
94105
.append("enabled", enabled)
95106
.append("serviceName", serviceName)
96107
.append("filter", filter)
108+
.append("knownSecurePorts", knownSecurePorts)
109+
.append("serviceLabels", serviceLabels)
97110
.append("metadata", metadata)
98111
.toString();
99112
}

spring-cloud-kubernetes-discovery/src/test/groovy/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientTest.groovy

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ package org.springframework.cloud.kubernetes.discovery
1919

2020
import io.fabric8.kubernetes.api.model.EndpointsBuilder
2121
import io.fabric8.kubernetes.api.model.ServiceBuilder
22+
import io.fabric8.kubernetes.api.model.ServiceListBuilder
2223
import io.fabric8.kubernetes.client.Config
2324
import io.fabric8.kubernetes.client.KubernetesClient
2425
import io.fabric8.kubernetes.server.mock.KubernetesMockServer
2526
import org.springframework.cloud.client.ServiceInstance
2627
import org.springframework.cloud.client.discovery.DiscoveryClient
2728
import spock.lang.Specification
2829

30+
import static org.assertj.core.api.Assertions.assertThat
31+
2932
class KubernetesDiscoveryClientTest extends Specification {
3033

3134
private static KubernetesMockServer mockServer = new KubernetesMockServer()
@@ -47,7 +50,7 @@ class KubernetesDiscoveryClientTest extends Specification {
4750
mockServer.destroy()
4851
}
4952

50-
def "Should be able to handle endpoints single address"() {
53+
def "getInstances should be able to handle endpoints single address"() {
5154
given:
5255
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint").andReturn(200, new EndpointsBuilder()
5356
.withNewMetadata()
@@ -73,7 +76,7 @@ class KubernetesDiscoveryClientTest extends Specification {
7376

7477
final properties = new KubernetesDiscoveryProperties()
7578
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
76-
mockClient, properties, new DefaultIsServicePortSecureResolver(properties))
79+
mockClient, properties, {client -> client.services()}, new DefaultIsServicePortSecureResolver(properties))
7780

7881
when:
7982
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint")
@@ -86,7 +89,7 @@ class KubernetesDiscoveryClientTest extends Specification {
8689

8790

8891

89-
def "Should be able to handle endpoints multiple addresses"() {
92+
def "getInstances should be able to handle endpoints multiple addresses"() {
9093
given:
9194
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint").andReturn(200, new EndpointsBuilder()
9295
.withNewMetadata()
@@ -115,7 +118,7 @@ class KubernetesDiscoveryClientTest extends Specification {
115118

116119
final properties = new KubernetesDiscoveryProperties()
117120
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
118-
mockClient, properties, new DefaultIsServicePortSecureResolver(properties))
121+
mockClient, properties, {client -> client.services()}, new DefaultIsServicePortSecureResolver(properties))
119122

120123
when:
121124
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint")
@@ -127,4 +130,78 @@ class KubernetesDiscoveryClientTest extends Specification {
127130
instances.find({s -> s.host == "ip2" && s.secure})
128131

129132
}
133+
134+
def "getServices should return all services when no labels are applied to the client"() {
135+
given:
136+
mockServer.expect().get().withPath("/api/v1/namespaces/test/services").andReturn(200, new ServiceListBuilder()
137+
.addNewItem()
138+
.withNewMetadata()
139+
.withName("s1")
140+
.withLabels(new HashMap<String, String>() {{
141+
put("label", "value")
142+
}})
143+
.endMetadata()
144+
.endItem()
145+
.addNewItem()
146+
.withNewMetadata()
147+
.withName("s2")
148+
.withLabels(new HashMap<String, String>() {{
149+
put("label", "value")
150+
put("label2", "value2")
151+
}})
152+
.endMetadata()
153+
.endItem()
154+
.addNewItem()
155+
.withNewMetadata()
156+
.withName("s3")
157+
.endMetadata()
158+
.endItem()
159+
.build()).once()
160+
161+
and:
162+
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
163+
mockClient, new KubernetesDiscoveryProperties(), {client -> client.services()})
164+
165+
when:
166+
List<String> instances = discoveryClient.getServices()
167+
168+
then:
169+
assertThat(instances).containsOnly("s1", "s2", "s3")
170+
}
171+
172+
def "getServices should return only matching services when labels are applied to the client"() {
173+
given:
174+
// this is the URL that is created by the KubernetesClient when a a label named 'label'
175+
// with a value of 'value' is specified
176+
mockServer.expect().get().withPath("/api/v1/namespaces/test/services?labelSelector=label%3Dvalue").andReturn(200, new ServiceListBuilder()
177+
.addNewItem()
178+
.withNewMetadata()
179+
.withName("s1")
180+
.withLabels(new HashMap<String, String>() {{
181+
put("label", "value")
182+
}})
183+
.endMetadata()
184+
.endItem()
185+
.addNewItem()
186+
.withNewMetadata()
187+
.withName("s2")
188+
.withLabels(new HashMap<String, String>() {{
189+
put("label", "value")
190+
put("label2", "value2")
191+
}})
192+
.endMetadata()
193+
.endItem()
194+
.build()).once()
195+
196+
and:
197+
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
198+
mockClient,
199+
new KubernetesDiscoveryProperties(), {client -> client.services().withLabels(["label": "value"])})
200+
201+
when:
202+
List<String> instances = discoveryClient.getServices()
203+
204+
then:
205+
assertThat(instances).containsOnly("s1", "s2")
206+
}
130207
}

spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientFilterTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@
2727
import io.fabric8.kubernetes.client.KubernetesClient;
2828
import io.fabric8.kubernetes.client.dsl.MixedOperation;
2929
import io.fabric8.kubernetes.client.dsl.ServiceResource;
30+
import org.junit.Before;
3031
import org.junit.Test;
3132
import org.junit.runner.RunWith;
3233
import org.mockito.InjectMocks;
3334
import org.mockito.Mock;
3435
import org.mockito.junit.MockitoJUnitRunner;
3536

3637
import static org.junit.Assert.assertEquals;
38+
import static org.mockito.ArgumentMatchers.any;
39+
import static org.mockito.Mockito.doReturn;
3740
import static org.mockito.Mockito.when;
3841

3942
@RunWith(MockitoJUnitRunner.class)
@@ -45,12 +48,18 @@ public class KubernetesDiscoveryClientFilterTest {
4548
@Mock
4649
private KubernetesDiscoveryProperties properties;
4750

51+
private KubernetesClientServicesFunction kubernetesClientServicesFunction = KubernetesClient::services;
52+
4853
@Mock
4954
private MixedOperation<Service, ServiceList, DoneableService, ServiceResource<Service, DoneableService>> serviceOperation;
5055

51-
@InjectMocks
5256
private KubernetesDiscoveryClient underTest;
5357

58+
@Before
59+
public void setUp() {
60+
underTest = new KubernetesDiscoveryClient(kubernetesClient, properties, kubernetesClientServicesFunction);
61+
}
62+
5463
@Test
5564
public void testFilteredServices() {
5665
List<String> springBootServiceNames = Arrays.asList("serviceA", "serviceB");

0 commit comments

Comments
 (0)