Merge branch 'geoand-284'
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2018 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
|
||||
*
|
||||
* http://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.discovery;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A regular java.util.function that is used to hide the complexity of the KubernetesClient interfaces
|
||||
*
|
||||
* It's meant to be used to abstract things like:
|
||||
*
|
||||
* client.services()
|
||||
* client.services().withLabel("key", "value")
|
||||
* client.services().withoutLabel("key")
|
||||
*
|
||||
* The result of the application of the function can then be used for example to list the services like so:
|
||||
*
|
||||
* function.apply(client).list()
|
||||
*
|
||||
* See KubernetesDiscoveryClientAutoConfiguration.servicesFunction
|
||||
*/
|
||||
public interface KubernetesClientServicesFunction extends
|
||||
Function<KubernetesClient, FilterWatchListDeletable<Service, ServiceList, Boolean, Watch, Watcher<Service>>> {
|
||||
}
|
||||
@@ -16,13 +16,6 @@
|
||||
*/
|
||||
package org.springframework.cloud.kubernetes.discovery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EndpointAddress;
|
||||
import io.fabric8.kubernetes.api.model.EndpointPort;
|
||||
import io.fabric8.kubernetes.api.model.EndpointSubset;
|
||||
@@ -31,7 +24,6 @@ import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -40,6 +32,13 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
@@ -50,6 +49,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
private final KubernetesDiscoveryProperties properties;
|
||||
private final DefaultIsServicePortSecureResolver isServicePortSecureResolver;
|
||||
|
||||
private final KubernetesClientServicesFunction kubernetesClientServicesFunction;
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
private final SimpleEvaluationContext evalCtxt = SimpleEvaluationContext
|
||||
.forReadOnlyDataBinding()
|
||||
@@ -57,17 +57,21 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
.build();
|
||||
|
||||
public KubernetesDiscoveryClient(KubernetesClient client,
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties) {
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties,
|
||||
KubernetesClientServicesFunction kubernetesClientServicesFunction) {
|
||||
|
||||
this(client, kubernetesDiscoveryProperties, new DefaultIsServicePortSecureResolver(kubernetesDiscoveryProperties));
|
||||
this(client, kubernetesDiscoveryProperties, kubernetesClientServicesFunction,
|
||||
new DefaultIsServicePortSecureResolver(kubernetesDiscoveryProperties));
|
||||
}
|
||||
|
||||
KubernetesDiscoveryClient(KubernetesClient client,
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties,
|
||||
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties,
|
||||
KubernetesClientServicesFunction kubernetesClientServicesFunction,
|
||||
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
|
||||
|
||||
this.client = client;
|
||||
this.properties = kubernetesDiscoveryProperties;
|
||||
this.kubernetesClientServicesFunction = kubernetesClientServicesFunction;
|
||||
this.isServicePortSecureResolver = isServicePortSecureResolver;
|
||||
}
|
||||
|
||||
@@ -191,7 +195,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
}
|
||||
|
||||
public List<String> getServices(Predicate<Service> filter) {
|
||||
return client.services().list()
|
||||
return kubernetesClientServicesFunction.apply(client).list()
|
||||
.getItems()
|
||||
.stream()
|
||||
.filter(filter)
|
||||
|
||||
@@ -40,13 +40,23 @@ public class KubernetesDiscoveryClientAutoConfiguration {
|
||||
return new DefaultIsServicePortSecureResolver(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KubernetesClientServicesFunction servicesFunction(KubernetesDiscoveryProperties properties) {
|
||||
if (properties.getServiceLabels().isEmpty()) {
|
||||
return KubernetesClient::services;
|
||||
}
|
||||
|
||||
return (client) -> client.services().withLabels(properties.getServiceLabels());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.enabled",matchIfMissing = true)
|
||||
public KubernetesDiscoveryClient kubernetesDiscoveryClient(
|
||||
KubernetesClient client, KubernetesDiscoveryProperties properties,
|
||||
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
|
||||
return new KubernetesDiscoveryClient(client, properties, isServicePortSecureResolver);
|
||||
public KubernetesDiscoveryClient kubernetesDiscoveryClient(KubernetesClient client,
|
||||
KubernetesDiscoveryProperties properties,
|
||||
KubernetesClientServicesFunction kubernetesClientServicesFunction,
|
||||
DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
|
||||
return new KubernetesDiscoveryClient(client, properties, kubernetesClientServicesFunction, isServicePortSecureResolver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -21,9 +21,9 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
|
||||
@@ -36,7 +36,7 @@ public class KubernetesDiscoveryProperties {
|
||||
@Value("${spring.application.name:unknown}")
|
||||
private String serviceName = "unknown";
|
||||
|
||||
/** SpEL expression to filter services. */
|
||||
/** SpEL expression to filter services AFTER they have been retrieved from the Kubernetes API server. */
|
||||
private String filter;
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ public class KubernetesDiscoveryProperties {
|
||||
add(8443);
|
||||
}};
|
||||
|
||||
/** If set, then only the services matching these labels will be fetched from the Kubernetes API server */
|
||||
private Map<String, String> serviceLabels = new HashMap<>();
|
||||
|
||||
private Metadata metadata = new Metadata();
|
||||
|
||||
public boolean isEnabled() {
|
||||
@@ -80,6 +83,14 @@ public class KubernetesDiscoveryProperties {
|
||||
this.knownSecurePorts = knownSecurePorts;
|
||||
}
|
||||
|
||||
public Map<String, String> getServiceLabels() {
|
||||
return serviceLabels;
|
||||
}
|
||||
|
||||
public void setServiceLabels(Map<String, String> serviceLabels) {
|
||||
this.serviceLabels = serviceLabels;
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
@@ -94,6 +105,8 @@ public class KubernetesDiscoveryProperties {
|
||||
.append("enabled", enabled)
|
||||
.append("serviceName", serviceName)
|
||||
.append("filter", filter)
|
||||
.append("knownSecurePorts", knownSecurePorts)
|
||||
.append("serviceLabels", serviceLabels)
|
||||
.append("metadata", metadata)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.discovery
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder
|
||||
import io.fabric8.kubernetes.api.model.ServiceListBuilder
|
||||
import io.fabric8.kubernetes.client.Config
|
||||
import io.fabric8.kubernetes.client.KubernetesClient
|
||||
import io.fabric8.kubernetes.server.mock.KubernetesMockServer
|
||||
@@ -26,6 +27,8 @@ import org.springframework.cloud.client.ServiceInstance
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient
|
||||
import spock.lang.Specification
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat
|
||||
|
||||
class KubernetesDiscoveryClientTest extends Specification {
|
||||
|
||||
private static KubernetesMockServer mockServer = new KubernetesMockServer()
|
||||
@@ -47,7 +50,7 @@ class KubernetesDiscoveryClientTest extends Specification {
|
||||
mockServer.destroy()
|
||||
}
|
||||
|
||||
def "Should be able to handle endpoints single address"() {
|
||||
def "getInstances should be able to handle endpoints single address"() {
|
||||
given:
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint").andReturn(200, new EndpointsBuilder()
|
||||
.withNewMetadata()
|
||||
@@ -73,7 +76,7 @@ class KubernetesDiscoveryClientTest extends Specification {
|
||||
|
||||
final properties = new KubernetesDiscoveryProperties()
|
||||
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
|
||||
mockClient, properties, new DefaultIsServicePortSecureResolver(properties))
|
||||
mockClient, properties, {client -> client.services()}, new DefaultIsServicePortSecureResolver(properties))
|
||||
|
||||
when:
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint")
|
||||
@@ -86,7 +89,7 @@ class KubernetesDiscoveryClientTest extends Specification {
|
||||
|
||||
|
||||
|
||||
def "Should be able to handle endpoints multiple addresses"() {
|
||||
def "getInstances should be able to handle endpoints multiple addresses"() {
|
||||
given:
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint").andReturn(200, new EndpointsBuilder()
|
||||
.withNewMetadata()
|
||||
@@ -115,7 +118,7 @@ class KubernetesDiscoveryClientTest extends Specification {
|
||||
|
||||
final properties = new KubernetesDiscoveryProperties()
|
||||
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
|
||||
mockClient, properties, new DefaultIsServicePortSecureResolver(properties))
|
||||
mockClient, properties, {client -> client.services()}, new DefaultIsServicePortSecureResolver(properties))
|
||||
|
||||
when:
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint")
|
||||
@@ -127,4 +130,78 @@ class KubernetesDiscoveryClientTest extends Specification {
|
||||
instances.find({s -> s.host == "ip2" && s.secure})
|
||||
|
||||
}
|
||||
|
||||
def "getServices should return all services when no labels are applied to the client"() {
|
||||
given:
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services").andReturn(200, new ServiceListBuilder()
|
||||
.addNewItem()
|
||||
.withNewMetadata()
|
||||
.withName("s1")
|
||||
.withLabels(new HashMap<String, String>() {{
|
||||
put("label", "value")
|
||||
}})
|
||||
.endMetadata()
|
||||
.endItem()
|
||||
.addNewItem()
|
||||
.withNewMetadata()
|
||||
.withName("s2")
|
||||
.withLabels(new HashMap<String, String>() {{
|
||||
put("label", "value")
|
||||
put("label2", "value2")
|
||||
}})
|
||||
.endMetadata()
|
||||
.endItem()
|
||||
.addNewItem()
|
||||
.withNewMetadata()
|
||||
.withName("s3")
|
||||
.endMetadata()
|
||||
.endItem()
|
||||
.build()).once()
|
||||
|
||||
and:
|
||||
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
|
||||
mockClient, new KubernetesDiscoveryProperties(), {client -> client.services()})
|
||||
|
||||
when:
|
||||
List<String> instances = discoveryClient.getServices()
|
||||
|
||||
then:
|
||||
assertThat(instances).containsOnly("s1", "s2", "s3")
|
||||
}
|
||||
|
||||
def "getServices should return only matching services when labels are applied to the client"() {
|
||||
given:
|
||||
// this is the URL that is created by the KubernetesClient when a a label named 'label'
|
||||
// with a value of 'value' is specified
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services?labelSelector=label%3Dvalue").andReturn(200, new ServiceListBuilder()
|
||||
.addNewItem()
|
||||
.withNewMetadata()
|
||||
.withName("s1")
|
||||
.withLabels(new HashMap<String, String>() {{
|
||||
put("label", "value")
|
||||
}})
|
||||
.endMetadata()
|
||||
.endItem()
|
||||
.addNewItem()
|
||||
.withNewMetadata()
|
||||
.withName("s2")
|
||||
.withLabels(new HashMap<String, String>() {{
|
||||
put("label", "value")
|
||||
put("label2", "value2")
|
||||
}})
|
||||
.endMetadata()
|
||||
.endItem()
|
||||
.build()).once()
|
||||
|
||||
and:
|
||||
DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
|
||||
mockClient,
|
||||
new KubernetesDiscoveryProperties(), {client -> client.services().withLabels(["label": "value"])})
|
||||
|
||||
when:
|
||||
List<String> instances = discoveryClient.getServices()
|
||||
|
||||
then:
|
||||
assertThat(instances).containsOnly("s1", "s2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.dsl.MixedOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.ServiceResource;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -34,6 +35,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -45,12 +48,18 @@ public class KubernetesDiscoveryClientFilterTest {
|
||||
@Mock
|
||||
private KubernetesDiscoveryProperties properties;
|
||||
|
||||
private KubernetesClientServicesFunction kubernetesClientServicesFunction = KubernetesClient::services;
|
||||
|
||||
@Mock
|
||||
private MixedOperation<Service, ServiceList, DoneableService, ServiceResource<Service, DoneableService>> serviceOperation;
|
||||
|
||||
@InjectMocks
|
||||
private KubernetesDiscoveryClient underTest;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
underTest = new KubernetesDiscoveryClient(kubernetesClient, properties, kubernetesClientServicesFunction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilteredServices() {
|
||||
List<String> springBootServiceNames = Arrays.asList("serviceA", "serviceB");
|
||||
|
||||
Reference in New Issue
Block a user