This commit is contained in:
erabii
2023-03-23 14:47:32 +02:00
committed by GitHub
parent cbbdeaa975
commit d09589204f
14 changed files with 727 additions and 42 deletions

View File

@@ -24,8 +24,10 @@ import java.util.function.Predicate;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.core.log.LogAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
@@ -39,6 +41,8 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext;
*/
final class Fabric8DiscoveryServicesAdapter implements Function<KubernetesClient, List<Service>> {
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8DiscoveryServicesAdapter.class));
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final SimpleEvaluationContext EVALUATION_CONTEXT = SimpleEvaluationContext.forReadOnlyDataBinding()
@@ -65,6 +69,8 @@ final class Fabric8DiscoveryServicesAdapter implements Function<KubernetesClient
@Override
public List<Service> apply(KubernetesClient client) {
if (!properties.namespaces().isEmpty()) {
LOG.debug(() -> "searching in namespaces : " + properties.namespaces() + " with filter : "
+ properties.filter());
List<Service> services = new ArrayList<>();
properties.namespaces().forEach(namespace -> services.addAll(client.services().inNamespace(namespace)
.withLabels(properties.serviceLabels()).list().getItems().stream().filter(filter).toList()));

View File

@@ -41,7 +41,7 @@ final class Fabric8EndpointsCatalogWatch
@Override
public List<EndpointNameAndNamespace> apply(Fabric8CatalogWatchContext context) {
List<Endpoints> endpoints = endpoints(context.properties(), context.kubernetesClient(),
context.namespaceProvider(), "catalog-watcher", null);
context.namespaceProvider(), "catalog-watcher", null, x -> true);
/**
* <pre>

View File

@@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -187,7 +188,8 @@ final class Fabric8KubernetesDiscoveryClientUtils {
}
static List<Endpoints> endpoints(KubernetesDiscoveryProperties properties, KubernetesClient client,
KubernetesNamespaceProvider namespaceProvider, String target, @Nullable String serviceName) {
KubernetesNamespaceProvider namespaceProvider, String target, @Nullable String serviceName,
Predicate<Service> filter) {
List<Endpoints> endpoints;
@@ -209,7 +211,35 @@ final class Fabric8KubernetesDiscoveryClientUtils {
serviceName);
}
return endpoints;
return withFilter(endpoints, properties, client, filter);
}
// see https://github.com/spring-cloud/spring-cloud-kubernetes/issues/1182 on why this
// is needed
static List<Endpoints> withFilter(List<Endpoints> initial, KubernetesDiscoveryProperties properties,
KubernetesClient client, Predicate<Service> filter) {
if (properties.filter() == null || properties.filter().isBlank()) {
LOG.debug(() -> "filter not present");
return initial;
}
List<Endpoints> result = new ArrayList<>();
// group by namespace in order to make a single API call per namespace when
// retrieving services
Map<String, List<Endpoints>> byNamespace = initial.stream()
.collect(Collectors.groupingBy(x -> x.getMetadata().getNamespace()));
for (Map.Entry<String, List<Endpoints>> entry : byNamespace.entrySet()) {
Set<String> withFilter = client.services().inNamespace(entry.getKey()).list().getItems().stream()
.filter(filter).map(service -> service.getMetadata().getName()).collect(Collectors.toSet());
result.addAll(
entry.getValue().stream().filter(x -> withFilter.contains(x.getMetadata().getName())).toList());
}
return result;
}
/**

View File

@@ -131,7 +131,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw
}
public List<Endpoints> getEndPointsList(String serviceId) {
return endpoints(properties, client, namespaceProvider, "fabric8-discovery", serviceId);
return endpoints(properties, client, namespaceProvider, "fabric8-discovery", serviceId, adapter.filter());
}
private List<ServiceInstance> getNamespaceServiceInstances(EndpointSubsetNS es, String serviceId) {

View File

@@ -28,6 +28,8 @@ import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.discovery.v1.Endpoint;
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointBuilder;
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSlice;
@@ -305,6 +307,14 @@ abstract class Fabric8EndpointsAndEndpointSlicesTests {
mockClient().endpoints().inNamespace(namespace).resource(endpoints).create();
}
void service(String namespace, Map<String, String> labels, String podName) {
Service service = new ServiceBuilder()
.withMetadata(new ObjectMetaBuilder().withLabels(labels).withName("endpoints-" + podName).build())
.build();
mockClient().services().inNamespace(namespace).resource(service).create();
}
static void endpointSlice(String namespace, Map<String, String> labels, String podName) {
Endpoint endpoint = new EndpointBuilder()
@@ -320,7 +330,8 @@ abstract class Fabric8EndpointsAndEndpointSlicesTests {
static void invokeAndAssert(KubernetesCatalogWatch watch, List<EndpointNameAndNamespace> state) {
watch.catalogServicesWatch();
verify(APPLICATION_EVENT_PUBLISHER).publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture());
verify(APPLICATION_EVENT_PUBLISHER, Mockito.atLeastOnce())
.publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture());
HeartbeatEvent event = HEARTBEAT_EVENT_ARGUMENT_CAPTOR.getValue();
assertThat(event.getValue()).isInstanceOf(List.class);

View File

@@ -22,6 +22,7 @@ import java.util.Set;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace;
@@ -38,6 +39,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
private static KubernetesClient mockClient;
@AfterEach
void afterEach() {
mockClient.endpoints().inAnyNamespace().delete();
mockClient.services().inAnyNamespace().delete();
}
@Test
@Override
void testInSpecificNamespaceWithServiceLabels() {
@@ -51,6 +58,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA")));
}
@@ -67,6 +80,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch,
List.of(new EndpointNameAndNamespace("podA", "namespaceA"),
new EndpointNameAndNamespace("podB", "namespaceA"),
@@ -86,6 +105,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
new EndpointNameAndNamespace("podD", "namespaceB")));
}
@@ -102,6 +127,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podA", "namespaceA"),
new EndpointNameAndNamespace("podB", "namespaceA"), new EndpointNameAndNamespace("podC", "namespaceA"),
new EndpointNameAndNamespace("podD", "namespaceB"),
@@ -121,6 +152,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
new EndpointNameAndNamespace("podD", "namespaceB")));
}
@@ -138,6 +175,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA")));
}
@@ -154,6 +197,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podD");
endpoints("namespaceB", Map.of(), "podE");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA")));
}
@@ -172,6 +221,14 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
endpoints("namespaceB", Map.of("color", "blue"), "podF");
endpoints("namespaceC", Map.of("color", "blue"), "podO");
service("namespaceA", Map.of(), "podA");
service("namespaceA", Map.of("color", "blue"), "podB");
service("namespaceA", Map.of("color", "red"), "podC");
service("namespaceB", Map.of("color", "blue"), "podD");
service("namespaceB", Map.of(), "podE");
service("namespaceB", Map.of("color", "blue"), "podF");
service("namespaceC", Map.of("color", "blue"), "podO");
invokeAndAssert(watch,
List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
new EndpointNameAndNamespace("podD", "namespaceB"),

View File

@@ -61,7 +61,8 @@ class Fabric8KubernetesDiscoveryClientTest {
@AfterEach
void afterEach() {
mockClient.close();
mockClient.endpoints().inAnyNamespace().delete();
mockClient.services().inAnyNamespace().delete();
}
@Test
@@ -128,8 +129,11 @@ class Fabric8KubernetesDiscoveryClientTest {
.withLabels(labels).endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef()
.withUid("30").endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP").endSubset()
.build();
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
.endMetadata().build();
mockClient.endpoints().inNamespace("test").resource(endPoint).create();
mockClient.services().inNamespace("test").resource(service).create();
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
KubernetesDiscoveryProperties.DEFAULT, KubernetesClient::services, null,
@@ -148,18 +152,24 @@ class Fabric8KubernetesDiscoveryClientTest {
Endpoints endPoint1 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
.endMetadata().build();
Endpoints endPoint2 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
.endMetadata().build();
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
.endMetadata().build();
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
.endMetadata().build();
mockClient.endpoints().inNamespace(namespace1).resource(endPoint1).create();
mockClient.endpoints().inNamespace(namespace2).resource(endPoint2).create();
mockClient.services().inNamespace(namespace1).resource(service1).create();
mockClient.services().inNamespace(namespace2).resource(service2).create();
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, null, new ServicePortSecureResolver(properties));
KubernetesClient::services, x -> true, new ServicePortSecureResolver(properties));
List<Endpoints> result_endpoints = discoveryClient.getEndPointsList("endpoint");
@@ -180,9 +190,19 @@ class Fabric8KubernetesDiscoveryClientTest {
Endpoints endPoint3 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace3)
.endMetadata().build();
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
.endMetadata().build();
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
.endMetadata().build();
Service service3 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace3)
.endMetadata().build();
mockClient.endpoints().inNamespace(namespace1).resource(endPoint1).create();
mockClient.endpoints().inNamespace(namespace2).resource(endPoint2).create();
mockClient.endpoints().inNamespace(namespace3).resource(endPoint3).create();
mockClient.services().inNamespace(namespace1).resource(service1).create();
mockClient.services().inNamespace(namespace2).resource(service2).create();
mockClient.services().inNamespace(namespace3).resource(service3).create();
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false,
Set.of(namespace1, namespace3), true, 60, false, null, Set.of(), Map.of(), null,

View File

@@ -93,6 +93,7 @@ class Fabric8KubernetesDiscoveryClientTests {
void testAllNamespacesSingleEndpointsMatchExactLabels(CapturedOutput output) {
createEndpoints("default", "blue-service", Map.of("color", "blue"));
createService("default", "blue-service", Map.of("color", "blue"));
boolean allNamespaces = true;
Set<String> namespaces = Set.of();
@@ -118,6 +119,7 @@ class Fabric8KubernetesDiscoveryClientTests {
void testAllNamespacesSingleEndpointsMatchPartialLabels(CapturedOutput output) {
createEndpoints("default", "blue-service", Map.of("color", "blue", "shape", "round"));
createService("default", "blue-service", Map.of("color", "blue", "shape", "round"));
boolean allNamespaces = true;
Set<String> namespaces = Set.of();
@@ -170,6 +172,9 @@ class Fabric8KubernetesDiscoveryClientTests {
createEndpoints("default", "service-one", Map.of("color", "blue", "shape", "round"));
createEndpoints("default", "service-two", Map.of("color", "blue", "shape", "round"));
createService("default", "service-one", Map.of("color", "blue", "shape", "round"));
createService("default", "service-two", Map.of("color", "blue", "shape", "round"));
boolean allNamespaces = true;
Set<String> namespaces = Set.of();
Map<String, String> serviceLabels = Map.of("color", "blue");
@@ -196,6 +201,9 @@ class Fabric8KubernetesDiscoveryClientTests {
createEndpoints("a", "service-one", Map.of("color", "blue", "shape", "round"));
createEndpoints("b", "service-one", Map.of("color", "blue", "shape", "round"));
createService("a", "service-one", Map.of("color", "blue", "shape", "round"));
createService("b", "service-one", Map.of("color", "blue", "shape", "round"));
boolean allNamespaces = true;
Set<String> namespaces = Set.of();
Map<String, String> serviceLabels = Map.of("color", "blue");
@@ -244,6 +252,7 @@ class Fabric8KubernetesDiscoveryClientTests {
void testClientNamespaceSingleEndpointsMatchExactLabels(CapturedOutput output) {
createEndpoints("test", "blue-service", Map.of("color", "blue"));
createService("test", "blue-service", Map.of("color", "blue"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of();
@@ -269,6 +278,7 @@ class Fabric8KubernetesDiscoveryClientTests {
void testClientNamespaceSingleEndpointsMatchPartialLabels(CapturedOutput output) {
createEndpoints("test", "blue-service", Map.of("color", "blue", "shape", "round"));
createService("test", "blue-service", Map.of("color", "blue", "shape", "round"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of();
@@ -321,6 +331,9 @@ class Fabric8KubernetesDiscoveryClientTests {
createEndpoints("test", "service-one", Map.of("color", "blue", "shape", "round"));
createEndpoints("test", "service-two", Map.of("color", "blue", "shape", "round"));
createService("test", "service-one", Map.of("color", "blue", "shape", "round"));
createService("test", "service-two", Map.of("color", "blue", "shape", "round"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of();
Map<String, String> serviceLabels = Map.of("color", "blue");
@@ -347,6 +360,9 @@ class Fabric8KubernetesDiscoveryClientTests {
createEndpoints("test", "service-one", Map.of("color", "blue", "shape", "round"));
createEndpoints("b", "service-one", Map.of("color", "blue", "shape", "round"));
createService("test", "service-one", Map.of("color", "blue", "shape", "round"));
createService("b", "service-one", Map.of("color", "blue", "shape", "round"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of();
Map<String, String> serviceLabels = Map.of("color", "blue");
@@ -395,6 +411,7 @@ class Fabric8KubernetesDiscoveryClientTests {
void testSelectiveNamespacesSingleEndpointsMatchExactLabels(CapturedOutput output) {
createEndpoints("test", "blue-service", Map.of("color", "blue"));
createService("test", "blue-service", Map.of("color", "blue"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of("test");
@@ -402,7 +419,8 @@ class Fabric8KubernetesDiscoveryClientTests {
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, x -> true,
null);
List<Endpoints> result = discoveryClient.getEndPointsList("blue-service");
Assertions.assertEquals(result.size(), 1);
Assertions.assertTrue(output.getOut().contains("discovering endpoints in namespaces : [test]"));
@@ -423,6 +441,9 @@ class Fabric8KubernetesDiscoveryClientTests {
createEndpoints("a", "blue-service", Map.of("color", "blue", "shape", "round"));
createEndpoints("b", "blue-service", Map.of("color", "blue", "shape", "rectangle"));
createService("a", "blue-service", Map.of("color", "blue", "shape", "round"));
createService("b", "blue-service", Map.of("color", "blue", "shape", "rectangle"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of("a");
Map<String, String> serviceLabels = Map.of("color", "blue");
@@ -451,6 +472,9 @@ class Fabric8KubernetesDiscoveryClientTests {
createEndpoints("a", "blue-service", Map.of("color", "blue"));
createEndpoints("b", "blue-service", Map.of("color", "blue"));
createService("a", "blue-service", Map.of("color", "blue"));
createService("b", "blue-service", Map.of("color", "blue"));
boolean allNamespaces = false;
Set<String> namespaces = Set.of("a", "b");
// so that assertion is correct
@@ -564,4 +588,11 @@ class Fabric8KubernetesDiscoveryClientTests {
.create();
}
private void createService(String namespace, String name, Map<String, String> labels) {
client.services().inNamespace(namespace)
.resource(new ServiceBuilder()
.withMetadata(new ObjectMetaBuilder().withName(name).withLabels(labels).build()).build())
.create();
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright 2013-2023 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
*
* https://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.fabric8.discovery;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
/**
* @author wind57
*/
@EnableKubernetesMockClient(crud = true, https = false)
class Fabric8KubernetesDiscoveryClientUtilsFilterTests {
private static KubernetesClient client;
private static final KubernetesDiscoveryProperties PROPERTIES = new KubernetesDiscoveryProperties(true, true,
Set.of(), false, 60L, false, "some", Set.of(), Map.of(), "", null, 0, false);
@AfterEach
void afterEach() {
client.endpoints().inAnyNamespace().delete();
client.services().inAnyNamespace().delete();
}
@Test
void withFilterEmptyInput() {
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(), PROPERTIES, client,
x -> true);
Assertions.assertEquals(result.size(), 0);
}
/**
* <pre>
* - Endpoints with name : "a" and namespace "namespace-a", present
* - Service with name "a" and namespace "namespace-not-a" present
*
* As such, there is no match, empty result.
* </pre>
*/
@Test
void withFilterOneEndpointsNoMatchInService() {
Endpoints endpoints = createEndpoints("a", "namespace-a");
createService("a", "namespace-not-a");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpoints), PROPERTIES,
client, x -> true);
Assertions.assertEquals(result.size(), 0);
}
/**
* <pre>
* - Endpoints with name : "a" and namespace "namespace-a", present
* - Service with name "a" and namespace "namespace-a" present
*
* As such, there is a match.
* </pre>
*/
@Test
void withFilterOneEndpointsMatchInService() {
Endpoints endpoints = createEndpoints("a", "namespace-a");
createService("a", "namespace-a");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpoints), PROPERTIES,
client, x -> true);
Assertions.assertEquals(result.size(), 1);
}
/**
* <pre>
* - Endpoints with name : "a" and namespace "namespace-a", present
* - Endpoints with name : "b" and namespace "namespace-b", present
* - Service with name "a" and namespace "namespace-a" present
*
* As such, there is a match, single endpoints as result.
* </pre>
*/
@Test
void withFilterTwoEndpointsOneMatchInService() {
Endpoints endpointsA = createEndpoints("a", "namespace-a");
Endpoints endpointsB = createEndpoints("b", "namespace-b");
createService("a", "namespace-a");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA, endpointsB),
PROPERTIES, client, x -> true);
Assertions.assertEquals(result.size(), 1);
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a");
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "namespace-a");
}
/**
* <pre>
* - Endpoints with name : "a" and namespace "namespace-a", present
* - Endpoints with name : "b" and namespace "namespace-b", present
* - Service with name "a" and namespace "namespace-a" present
* - Service with name "b" and namespace "namespace-b" present
* - Service with name "c" and namespace "namespace-c" present
*
* As such, there are two matches.
* </pre>
*/
@Test
void withFilterTwoEndpointsAndThreeServices() {
Endpoints endpointsA = createEndpoints("a", "namespace-a");
Endpoints endpointsB = createEndpoints("b", "namespace-b");
createService("a", "namespace-a");
createService("b", "namespace-b");
createService("c", "namespace-c");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA, endpointsB),
PROPERTIES, client, x -> true);
Assertions.assertEquals(result.size(), 2);
result = result.stream().sorted(Comparator.comparing(x -> x.getMetadata().getName())).toList();
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a");
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "namespace-a");
Assertions.assertEquals(result.get(1).getMetadata().getName(), "b");
Assertions.assertEquals(result.get(1).getMetadata().getNamespace(), "namespace-b");
}
/**
* <pre>
* - Endpoints with name : "a" and namespace "namespace-a", present
* - Endpoints with name : "b" and namespace "namespace-b", present
* - Service with name "a" and namespace "namespace-a" present
* - Service with name "b" and namespace "namespace-b" present
* - Service with name "c" and namespace "namespace-c" present
*
* As such, there are two matches.
* </pre>
*/
@Test
void withFilterSingleEndpointsMatchesFilter() {
Endpoints endpointsA = createEndpoints("a", "namespace-a");
createService("a", "namespace-a");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA), PROPERTIES,
client, x -> x.getMetadata().getNamespace().equals("namespace-a"));
Assertions.assertEquals(result.size(), 1);
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a");
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "namespace-a");
}
/**
* <pre>
* - Endpoints with name : "a-1" and namespace "default", present
* - Endpoints with name : "b-1" and namespace "default", present
* - Endpoints with name : "c-2" and namespace "default", present
* - Service with name "a-1" and namespace "default" present
* - Service with name "b-1" and namespace "default" present
*
* As such, there are two matches.
* </pre>
*/
@Test
void withFilterTwoEndpointsMatchesFilter() {
Endpoints endpointsA = createEndpoints("a-1", "default");
Endpoints endpointsB = createEndpoints("b-1", "default");
Endpoints endpointsC = createEndpoints("c-2", "default");
createService("a-1", "default");
createService("b-1", "default");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(
List.of(endpointsA, endpointsB, endpointsC), PROPERTIES, client,
x -> x.getMetadata().getName().contains("1"));
Assertions.assertEquals(result.size(), 2);
result = result.stream().sorted(Comparator.comparing(x -> x.getMetadata().getName())).toList();
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a-1");
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "default");
Assertions.assertEquals(result.get(1).getMetadata().getName(), "b-1");
Assertions.assertEquals(result.get(1).getMetadata().getNamespace(), "default");
}
/**
* <pre>
* - Endpoints with name : "a" and namespace "default", present
* - Service with name "a-1" and namespace "default" present
* - Service with name "b-1" and namespace "default" present
*
* As such, there are two matches.
* </pre>
*/
@Test
void withFilterSingleEndpointsNoPredicateMatch() {
Endpoints endpointsA = createEndpoints("a", "default");
createService("a-1", "default");
createService("b-1", "default");
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA), PROPERTIES,
client, x -> !x.getMetadata().getName().contains("1"));
Assertions.assertEquals(result.size(), 0);
}
private Endpoints createEndpoints(String name, String namespace) {
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName(name).withNamespace(namespace)
.endMetadata().build();
client.endpoints().inNamespace(namespace).resource(endpoints).create();
return endpoints;
}
private void createService(String name, String namespace) {
Service service = new ServiceBuilder().withNewMetadata().withName(name).withNamespace(namespace).endMetadata()
.build();
client.services().inNamespace(namespace).resource(service).create();
}
}

View File

@@ -25,14 +25,21 @@ import java.util.stream.Collectors;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointSubset;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
import io.fabric8.kubernetes.api.model.EndpointsList;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.ObjectReference;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.api.model.ServiceListBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.FilterNested;
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.dsl.ServiceResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -92,8 +99,10 @@ class KubernetesCatalogWatchTest {
createInSpecificNamespaceWatcher();
when(FILTER_WATCH_LIST_DELETABLE.list())
.thenReturn(createSingleEndpointEndpointListByPodName("api-pod", "other-pod"))
.thenReturn(createSingleEndpointEndpointListByPodName("other-pod", "api-pod"));
.thenReturn(createSingleEndpointEndpointListByPodName("test", "api-pod", "other-pod"))
.thenReturn(createSingleEndpointEndpointListByPodName("test", "other-pod", "api-pod"));
mockServicesCall("api-pod", "test");
mockServicesCall("other-pod", "test");
kubernetesCatalogWatch.catalogServicesWatch();
// second execution on shuffleServices
@@ -108,8 +117,11 @@ class KubernetesCatalogWatchTest {
createInAllNamespaceWatcher();
when(FILTER_WATCH_LIST_DELETABLE.list())
.thenReturn(createSingleEndpointEndpointListByPodName("api-pod", "other-pod"))
.thenReturn(createSingleEndpointEndpointListByPodName("other-pod", "api-pod"));
.thenReturn(createSingleEndpointEndpointListByPodName("test", "api-pod", "other-pod"))
.thenReturn(createSingleEndpointEndpointListByPodName("test", "other-pod", "api-pod"));
mockServicesCall("api-pod", "test");
mockServicesCall("other-pod", "test");
kubernetesCatalogWatch.catalogServicesWatch();
// second execution on shuffleServices
@@ -124,8 +136,10 @@ class KubernetesCatalogWatchTest {
createInSpecificNamespaceWatcher();
when(FILTER_WATCH_LIST_DELETABLE.list())
.thenReturn(createEndpointsListByServiceName("api-service", "other-service"))
.thenReturn(createEndpointsListByServiceName("other-service", "api-service"));
.thenReturn(createEndpointsListByServiceName("test", "api-service", "other-service"))
.thenReturn(createEndpointsListByServiceName("test", "other-service", "api-service"));
mockServicesCall("api-service", "test");
mockServicesCall("other-service", "test");
kubernetesCatalogWatch.catalogServicesWatch();
// second execution on shuffleServices
@@ -140,8 +154,10 @@ class KubernetesCatalogWatchTest {
createInAllNamespaceWatcher();
when(FILTER_WATCH_LIST_DELETABLE.list())
.thenReturn(createEndpointsListByServiceName("api-service", "other-service"))
.thenReturn(createEndpointsListByServiceName("other-service", "api-service"));
.thenReturn(createEndpointsListByServiceName("test", "api-service", "other-service"))
.thenReturn(createEndpointsListByServiceName("test", "other-service", "api-service"));
mockServicesCall("api-service", "test");
mockServicesCall("other-service", "test");
kubernetesCatalogWatch.catalogServicesWatch();
// second execution on shuffleServices
@@ -156,7 +172,9 @@ class KubernetesCatalogWatchTest {
createInSpecificNamespaceWatcher();
when(FILTER_WATCH_LIST_DELETABLE.list())
.thenReturn(createSingleEndpointListWithNamespace("default", "api-pod", "other-pod"));
.thenReturn(createSingleEndpointListWithNamespace("default", "other-pod", "api-pod", "other-pod"));
mockServicesCall("api-pod", "default");
mockServicesCall("other-pod", "default");
kubernetesCatalogWatch.catalogServicesWatch();
@@ -176,7 +194,9 @@ class KubernetesCatalogWatchTest {
createInAllNamespaceWatcher();
when(FILTER_WATCH_LIST_DELETABLE.list())
.thenReturn(createSingleEndpointListWithNamespace("default", "api-pod", "other-pod"));
.thenReturn(createSingleEndpointListWithNamespace("default", "other-pod", "api-pod", "other-pod"));
mockServicesCall("api-pod", "default");
mockServicesCall("other-pod", "default");
kubernetesCatalogWatch.catalogServicesWatch();
@@ -195,8 +215,8 @@ class KubernetesCatalogWatchTest {
createInSpecificNamespaceWatcher();
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets();
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets("name", "test");
mockServicesCall("name", "test");
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
kubernetesCatalogWatch.catalogServicesWatch();
@@ -211,7 +231,8 @@ class KubernetesCatalogWatchTest {
createInAllNamespaceWatcher();
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets();
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets("name", "test");
mockServicesCall("name", "test");
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
@@ -227,7 +248,8 @@ class KubernetesCatalogWatchTest {
createInSpecificNamespaceWatcher();
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
mockServicesCall("api-pod", "test");
endpoints.getItems().get(0).getSubsets().get(0).setAddresses(null);
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
@@ -244,7 +266,8 @@ class KubernetesCatalogWatchTest {
createInAllNamespaceWatcher();
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
mockServicesCall("api-pod", "test");
endpoints.getItems().get(0).getSubsets().get(0).setAddresses(null);
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
@@ -261,7 +284,8 @@ class KubernetesCatalogWatchTest {
createInSpecificNamespaceWatcher();
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
mockServicesCall("api-pod", "test");
endpoints.getItems().get(0).getSubsets().get(0).getAddresses().get(0).setTargetRef(null);
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
@@ -278,7 +302,8 @@ class KubernetesCatalogWatchTest {
createInAllNamespaceWatcher();
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
mockServicesCall("api-pod", "test");
endpoints.getItems().get(0).getSubsets().get(0).getAddresses().get(0).setTargetRef(null);
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
@@ -290,34 +315,38 @@ class KubernetesCatalogWatchTest {
verify(APPLICATION_EVENT_PUBLISHER).publishEvent(any(HeartbeatEvent.class));
}
private EndpointsList createEndpointsListByServiceName(String... serviceNames) {
List<Endpoints> endpoints = stream(serviceNames).map(s -> createEndpointsByPodName(s + "-singlePodUniqueId"))
.collect(Collectors.toList());
private EndpointsList createEndpointsListByServiceName(String namespace, String... serviceNames) {
List<Endpoints> endpoints = stream(serviceNames)
.map(s -> createEndpointsByPodName(namespace, s + "-singlePodUniqueId")).collect(Collectors.toList());
EndpointsList endpointsList = new EndpointsList();
endpointsList.setItems(endpoints);
return endpointsList;
}
private EndpointsList createSingleEndpointEndpointListWithoutSubsets() {
Endpoints endpoints = new Endpoints();
private EndpointsList createSingleEndpointEndpointListWithoutSubsets(String name, String namespace) {
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName(name).withNamespace(namespace)
.endMetadata().build();
EndpointsList endpointsList = new EndpointsList();
endpointsList.setItems(Collections.singletonList(endpoints));
return endpointsList;
}
private EndpointsList createSingleEndpointEndpointListByPodName(String... podNames) {
private EndpointsList createSingleEndpointEndpointListByPodName(String namespace, String... podNames) {
Endpoints endpoints = new Endpoints();
endpoints.setSubsets(createSubsetsByPodName(podNames));
endpoints.setMetadata(new ObjectMetaBuilder().withNamespace(namespace).build());
EndpointsList endpointsList = new EndpointsList();
endpointsList.setItems(Collections.singletonList(endpoints));
return endpointsList;
}
private EndpointsList createSingleEndpointListWithNamespace(String namespace, String... podNames) {
Endpoints endpoints = new Endpoints();
private EndpointsList createSingleEndpointListWithNamespace(String namespace, String endpointsName,
String... podNames) {
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withNamespace(namespace).withName(endpointsName)
.and().build();
endpoints.setSubsets(createSubsetsWithNamespace(namespace, podNames));
EndpointsList endpointsList = new EndpointsList();
@@ -325,8 +354,8 @@ class KubernetesCatalogWatchTest {
return endpointsList;
}
private Endpoints createEndpointsByPodName(String podName) {
Endpoints endpoints = new Endpoints();
private Endpoints createEndpointsByPodName(String namespace, String podName) {
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withNamespace(namespace).and().build();
endpoints.setSubsets(createSubsetsByPodName(podName));
return endpoints;
}
@@ -401,4 +430,16 @@ class KubernetesCatalogWatchTest {
when(FILTER_NESTED.endFilter()).thenReturn(FILTER_WATCH_LIST_DELETABLE);
}
private void mockServicesCall(String name, String namespace) {
MixedOperation<Service, ServiceList, ServiceResource<Service>> mixedOperation = Mockito
.mock(MixedOperation.class);
NonNamespaceOperation<Service, ServiceList, ServiceResource<Service>> nonNamespaceOperation = Mockito
.mock(NonNamespaceOperation.class);
when(CLIENT.services()).thenReturn(mixedOperation);
when(mixedOperation.inNamespace(namespace)).thenReturn(nonNamespaceOperation);
when(nonNamespaceOperation.list()).thenReturn(new ServiceListBuilder().withItems(
new ServiceBuilder().withNewMetadata().withName(name).withNamespace(namespace).endMetadata().build())
.build());
}
}

View File

@@ -30,6 +30,7 @@ import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.api.model.ServiceListBuilder;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
@@ -245,9 +246,12 @@ class KubernetesDiscoveryClientFilterMetadataTest {
when(serviceResource.get()).thenReturn(service);
when(CLIENT.services()).thenReturn(serviceOperation);
when(CLIENT.services().inNamespace(anyString())).thenReturn(serviceOperation);
when(serviceOperation.list()).thenReturn(new ServiceListBuilder().withItems(new ServiceBuilder()
.withNewMetadata().withName(serviceId).withNamespace(namespace).endMetadata().build()).build());
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setNamespace(namespace);
objectMeta.setName(serviceId);
Endpoints endpoints = new EndpointsBuilder().withMetadata(objectMeta).addNewSubset()
.addAllToPorts(getEndpointPorts(ports)).addNewAddress().endAddress().endSubset().build();

View File

@@ -158,7 +158,8 @@ class KubernetesReactiveDiscoveryClientTests {
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service")
.andReturn(200, services.getItems().get(0)).once();
Metadata metadata = new Metadata(false, null, false, null, true, "port.");
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services").andReturn(200, services).once();
ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(kubernetesClient,
KubernetesDiscoveryProperties.DEFAULT, KubernetesClient::services);
Flux<ServiceInstance> instances = client.getInstances("existing-service");
@@ -174,7 +175,7 @@ class KubernetesReactiveDiscoveryClientTests {
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
.once();
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("existing-service").withNamespace("test")
.endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP").endSubset().build();
@@ -208,7 +209,7 @@ class KubernetesReactiveDiscoveryClientTests {
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
.once();
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("existing-service").withNamespace("test")
.endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
@@ -243,9 +244,9 @@ class KubernetesReactiveDiscoveryClientTests {
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
.once();
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
.endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName("existing-service")
.withNamespace("test").endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef()
.withUid("uid1").endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
EndpointsList endpointsList = new EndpointsList();

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2013-2023 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
*
* https://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.fabric8.configmap;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
class Fabric8DiscoveryFilterIT {
private static final String FILTER_BOTH_NAMESPACES = "#root.metadata.namespace matches '^.*uat$'";
private static final String FILTER_SINGLE_NAMESPACE = "#root.metadata.namespace matches 'a-uat$'";
private static final String NAMESPACE_A_UAT = "a-uat";
private static final String NAMESPACE_B_UAT = "b-uat";
private static final String NAMESPACE = "default";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static KubernetesClient client;
private static Util util;
private static final K3sContainer K3S = Commons.container();
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(IMAGE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
util = new Util(K3S);
client = util.client();
util.setUp(NAMESPACE);
}
@BeforeEach
void beforeEach() {
util.createNamespace(NAMESPACE_A_UAT);
util.createNamespace(NAMESPACE_B_UAT);
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE);
}
@AfterEach
void afterEach() {
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE);
util.deleteNamespace(NAMESPACE_A_UAT);
util.deleteNamespace(NAMESPACE_B_UAT);
}
@AfterAll
static void after() throws Exception {
Commons.cleanUp(IMAGE_NAME, K3S);
}
/**
* <pre>
* - service "wiremock" is present in namespace "a-uat"
* - service "wiremock" is present in namespace "b-uat"
*
* - we search with a predicate : "#root.metadata.namespace matches '^uat.*$'"
*
* As such, both services are found via 'getInstances' call.
* </pre>
*/
@Test
void filterMatchesBothNamespacesViaThePredicate() {
manifests(Phase.CREATE, FILTER_BOTH_NAMESPACES);
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(serviceInstances.size(), 2);
List<DefaultKubernetesServiceInstance> sorted = serviceInstances.stream()
.sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)).toList();
DefaultKubernetesServiceInstance first = sorted.get(0);
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
Assertions.assertNotNull(first.getInstanceId());
Assertions.assertEquals(first.getPort(), 8080);
Assertions.assertEquals(first.getNamespace(), "a-uat");
Assertions.assertEquals(first.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
DefaultKubernetesServiceInstance second = sorted.get(1);
Assertions.assertEquals(second.getServiceId(), "service-wiremock");
Assertions.assertNotNull(second.getInstanceId());
Assertions.assertEquals(second.getPort(), 8080);
Assertions.assertEquals(second.getNamespace(), "b-uat");
Assertions.assertEquals(second.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP"));
manifests(Phase.DELETE, FILTER_BOTH_NAMESPACES);
}
/**
* <pre>
* - service "wiremock" is present in namespace "a-uat"
* - service "wiremock" is present in namespace "b-uat"
*
* - we search with a predicate : "#root.metadata.namespace matches 'a-uat$'"
*
* As such, only service from 'a-uat' namespace matches.
* </pre>
*/
@Test
void filterMatchesOneNamespaceViaThePredicate() {
manifests(Phase.CREATE, FILTER_SINGLE_NAMESPACE);
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(serviceInstances.size(), 1);
DefaultKubernetesServiceInstance first = serviceInstances.get(0);
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
Assertions.assertNotNull(first.getInstanceId());
Assertions.assertEquals(first.getPort(), 8080);
Assertions.assertEquals(first.getNamespace(), "a-uat");
Assertions.assertEquals(first.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
manifests(Phase.DELETE, FILTER_SINGLE_NAMESPACE);
}
private static void manifests(Phase phase, String serviceFilter) {
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).get();
List<EnvVar> envVars = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
EnvVar namespaceAUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
.withValue(NAMESPACE_A_UAT).build();
EnvVar namespaceBUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1")
.withValue(NAMESPACE_B_UAT).build();
EnvVar filter = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER")
.withValue(serviceFilter).build();
EnvVar debug = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG")
.build();
envVars.add(namespaceAUat);
envVars.add(namespaceBUat);
envVars.add(filter);
envVars.add(debug);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
Service service = client.services().load(serviceStream).get();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).get();
if (phase.equals(Phase.CREATE)) {
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
.create();
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else {
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
.delete();
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private static InputStream getAdminRole() {
return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml");
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull);
}
}

View File

@@ -137,6 +137,8 @@ class Fabric8DiscoveryNamespaceFilterIT {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else {
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
.delete();
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}