Merge branch 'Salaboy-salaboy-gateway-spel-filter'

This commit is contained in:
Spencer Gibb
2018-09-04 16:18:20 -04:00
4 changed files with 212 additions and 31 deletions

View File

@@ -91,12 +91,7 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-dependencies</artifactId>

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*
*/
package org.springframework.cloud.kubernetes.discovery;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import io.fabric8.kubernetes.api.model.EndpointAddress;
@@ -31,9 +31,13 @@ import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.utils.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.util.Assert;
public class KubernetesDiscoveryClient implements DiscoveryClient {
@@ -42,10 +46,16 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
private static final String HOSTNAME = "HOSTNAME";
private KubernetesClient client;
private KubernetesDiscoveryProperties properties;
private final KubernetesDiscoveryProperties properties;
private final SpelExpressionParser parser = new SpelExpressionParser();
private final SimpleEvaluationContext evalCtxt = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withInstanceMethods()
.build();
public KubernetesDiscoveryClient(KubernetesClient client,
KubernetesDiscoveryProperties kubernetesDiscoveryProperties) {
KubernetesDiscoveryProperties kubernetesDiscoveryProperties) {
this.client = client;
this.properties = kubernetesDiscoveryProperties;
}
@@ -67,9 +77,9 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
String serviceName = properties.getServiceName();
String podName = System.getenv(HOSTNAME);
ServiceInstance defaultInstance = new DefaultServiceInstance(serviceName,
"localhost",
8080,
false);
"localhost",
8080,
false);
Endpoints endpoints = client.endpoints().withName(serviceName).get();
Optional<Service> service = Optional.ofNullable(client.services().withName(serviceName).get());
@@ -86,14 +96,14 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
List<EndpointSubset> subsets = endpoints.getSubsets();
if (subsets != null) {
for (EndpointSubset subset : subsets) {
List<EndpointAddress> addresses = subset.getAddresses();
for (EndpointAddress address : addresses) {
for (EndpointSubset s : subsets) {
List<EndpointAddress> addresses = s.getAddresses();
for (EndpointAddress a : addresses) {
return new KubernetesServiceInstance(serviceName,
address,
subset.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
labels,
false);
a,
s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
labels,
false);
}
}
}
@@ -107,7 +117,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
@Override
public List<ServiceInstance> getInstances(String serviceId) {
Assert.notNull(serviceId,
"[Assertion failed] - the object argument must be null");
"[Assertion failed] - the object argument must be null");
Optional<Service> service = Optional.ofNullable(client.services().withName(serviceId).get());
final Map<String, String> labels;
if (service.isPresent()) {
@@ -120,14 +130,14 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
List<EndpointSubset> subsets = endpoints.get().getSubsets();
List<ServiceInstance> instances = new ArrayList<>();
if (subsets != null) {
for (EndpointSubset subset : subsets) {
List<EndpointAddress> addresses = subset.getAddresses();
for (EndpointAddress address : addresses) {
for (EndpointSubset s : subsets) {
List<EndpointAddress> addresses = s.getAddresses();
for (EndpointAddress a : addresses) {
instances.add(new KubernetesServiceInstance(serviceId,
address,
subset.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
labels,
false));
a,
s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
labels,
false));
}
}
}
@@ -137,9 +147,30 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
@Override
public List<String> getServices() {
return client.services().list()
.getItems()
.stream().map(s -> s.getMetadata().getName())
.collect(Collectors.toList());
String spelExpression = properties.getFilter();
Predicate<Service> filteredServices;
if (spelExpression == null || spelExpression.isEmpty()) {
filteredServices = (Service instance) -> true;
} else {
Expression filterExpr = parser.parseExpression(spelExpression);
filteredServices = (Service instance) -> {
Boolean include = filterExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
return false;
}
return include;
};
}
return getServices(filteredServices);
}
public List<String> getServices(Predicate<Service> filter) {
return client.services().list()
.getItems()
.stream()
.filter(filter)
.map(s -> s.getMetadata().getName())
.collect(Collectors.toList());
}
}

View File

@@ -29,6 +29,11 @@ public class KubernetesDiscoveryProperties extends AutoServiceRegistrationProper
@Value("${spring.application.name:unknown}")
private String serviceName = "unknown";
/**
* SpEL expression to filter services
**/
private String filter;
public boolean isEnabled() {
return enabled;
}
@@ -41,11 +46,20 @@ public class KubernetesDiscoveryProperties extends AutoServiceRegistrationProper
return serviceName;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter){
this.filter = filter;
}
@Override
public String toString() {
return "KubernetesDiscoveryProperties{" +
"enabled=" + enabled +
", serviceName='" + serviceName + '\'' +
", filter='" + filter + '\'' +
'}';
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.fabric8.kubernetes.api.model.DoneableService;
import io.fabric8.kubernetes.api.model.ObjectMeta;
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.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class KubernetesDiscoveryClientTest {
@Mock
private KubernetesClient kubernetesClient;
@Mock
private KubernetesDiscoveryProperties properties;
@Mock
private MixedOperation<Service, ServiceList, DoneableService, Resource<Service, DoneableService>> serviceOperation;
@InjectMocks
private KubernetesDiscoveryClient underTest;
@Test
public void testFilteredServices() {
List<String> springBootServiceNames = Arrays.asList("serviceA", "serviceB");
List<Service> services = createSpringBootServiceByName(springBootServiceNames);
// Add non spring boot service
Service service = new Service();
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setName("ServiceNonSpringBoot");
service.setMetadata(objectMeta);
services.add(service);
ServiceList serviceList = new ServiceList();
serviceList.setItems(services);
when(serviceOperation.list())
.thenReturn(serviceList);
when(kubernetesClient.services()).thenReturn(serviceOperation);
when(properties.getFilter()).thenReturn("metadata.additionalProperties['spring-boot']");
List<String> filteredServices = underTest.getServices();
System.out.println("Filtered Services: " + filteredServices);
assertEquals(springBootServiceNames, filteredServices);
}
@Test
public void testFilteredServicesByPrefix() {
List<String> springBootServiceNames = Arrays.asList("serviceA", "serviceB", "serviceC");
List<Service> services = createSpringBootServiceByName(springBootServiceNames);
// Add non spring boot service
Service service = new Service();
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setName("anotherService");
service.setMetadata(objectMeta);
services.add(service);
ServiceList serviceList = new ServiceList();
serviceList.setItems(services);
when(serviceOperation.list())
.thenReturn(serviceList);
when(kubernetesClient.services()).thenReturn(serviceOperation);
when(properties.getFilter()).thenReturn("metadata.name.startsWith('service')");
List<String> filteredServices = underTest.getServices();
System.out.println("Filtered Services: " + filteredServices);
assertEquals(springBootServiceNames, filteredServices);
}
@Test
public void testNoExpression() {
List<String> springBootServiceNames = Arrays.asList("serviceA", "serviceB", "serviceC");
List<Service> services = createSpringBootServiceByName(springBootServiceNames);
ServiceList serviceList = new ServiceList();
serviceList.setItems(services);
when(serviceOperation.list())
.thenReturn(serviceList);
when(kubernetesClient.services()).thenReturn(serviceOperation);
when(properties.getFilter()).thenReturn("");
List<String> filteredServices = underTest.getServices();
System.out.println("Filtered Services: " + filteredServices);
assertEquals(springBootServiceNames, filteredServices);
}
private List<Service> createSpringBootServiceByName(List<String> serviceNames) {
List<Service> serviceCollection = new ArrayList<>(serviceNames.size());
for (String serviceName : serviceNames) {
Service service = new Service();
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setName(serviceName);
objectMeta.setAdditionalProperty("spring-boot", "true");
service.setMetadata(objectMeta);
serviceCollection.add(service);
}
return serviceCollection;
}
}