Add externalname support (#1243)
This commit is contained in:
@@ -6,6 +6,8 @@ This client lets you query Kubernetes endpoints (see https://kubernetes.io/docs/
|
||||
A service is typically exposed by the Kubernetes API server as a collection of endpoints that represent `http` and `https` addresses and that a client can
|
||||
access from a Spring Boot application running as a pod.
|
||||
|
||||
DiscoveryClient can also find services of type `ExternalName` (see https://kubernetes.io/docs/concepts/services-networking/service/#externalname[ExternalName services]). At the moment, external name support type of services is only available if the following property `spring.cloud.kubernetes.discovery.include-external-name-services` is set to `true` and only in the `fabric8` implementation. In a later release, support will be added for the kubernetes native client also.
|
||||
|
||||
This is something that you get for free by adding the following dependency inside your project:
|
||||
|
||||
====
|
||||
@@ -112,6 +114,8 @@ Please make sure to configure your service and/or application accordingly.
|
||||
|
||||
By default all of the ports and their names will be added to the metadata of the `ServiceInstance`.
|
||||
|
||||
As said before, if you want to get the list of `ServiceInstance` to also include the `ExternalName` type services, you need to enable that support via: `spring.cloud.kubernetes.discovery.include-external-name-services=true`. As such, when calling `DiscoveryClient::getInstances` those will be returned also. You can distinguish between `ExternalName` and any other types by inspecting `ServiceInstance::getMetadata` and lookup for a field called `type`. This will be the type of the service returned : `ExternalName`/`ClusterIP`, etc.
|
||||
|
||||
If, for any reason, you need to disable the `DiscoveryClient`, you can set the following property in `application.properties`:
|
||||
|
||||
====
|
||||
|
||||
@@ -102,6 +102,10 @@ public record DefaultKubernetesServiceInstance(String instanceId, String service
|
||||
}
|
||||
|
||||
private URI createUri(String scheme, String host, int port) {
|
||||
// assume ExternalName type of service
|
||||
if (port == -1) {
|
||||
return URI.create(host);
|
||||
}
|
||||
return URI.create(scheme + "://" + host + ":" + port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,4 +67,14 @@ public final class KubernetesDiscoveryConstants {
|
||||
*/
|
||||
public static final String ENDPOINT_SLICE = "EndpointSlice";
|
||||
|
||||
/**
|
||||
* ExternalName type of service.
|
||||
*/
|
||||
public static final String EXTERNAL_NAME = "ExternalName";
|
||||
|
||||
/**
|
||||
* Type of the service.
|
||||
*/
|
||||
public static final String SERVICE_TYPE = "type";
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
|
||||
import static org.springframework.cloud.client.discovery.DiscoveryClient.DEFAULT_ORDER;
|
||||
@@ -43,6 +44,8 @@ import static org.springframework.cloud.client.discovery.DiscoveryClient.DEFAULT
|
||||
* @param primaryPortName If set then the port with a given name is used as primary when
|
||||
* multiple ports are defined for a service.
|
||||
* @param useEndpointSlices use EndpointSlice instead of Endpoints
|
||||
* @param includeExternalNameServices should the discovery also search for services that
|
||||
* have "type: ExternalName" in their spec.
|
||||
*/
|
||||
// @formatter:off
|
||||
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
|
||||
@@ -56,14 +59,31 @@ public record KubernetesDiscoveryProperties(
|
||||
@DefaultValue Map<String, String> serviceLabels, String primaryPortName,
|
||||
@DefaultValue Metadata metadata,
|
||||
@DefaultValue("" + DEFAULT_ORDER) int order,
|
||||
boolean useEndpointSlices) {
|
||||
boolean useEndpointSlices,
|
||||
boolean includeExternalNameServices) {
|
||||
// @formatter:on
|
||||
|
||||
@ConstructorBinding
|
||||
public KubernetesDiscoveryProperties {
|
||||
|
||||
}
|
||||
|
||||
public KubernetesDiscoveryProperties(@DefaultValue("true") boolean enabled, boolean allNamespaces,
|
||||
@DefaultValue Set<String> namespaces, @DefaultValue("true") boolean waitCacheReady,
|
||||
@DefaultValue("60") long cacheLoadingTimeoutSeconds, boolean includeNotReadyAddresses, String filter,
|
||||
@DefaultValue({ "443", "8443" }) Set<Integer> knownSecurePorts,
|
||||
@DefaultValue Map<String, String> serviceLabels, String primaryPortName, @DefaultValue Metadata metadata,
|
||||
@DefaultValue("" + DEFAULT_ORDER) int order, boolean useEndpointSlices) {
|
||||
this(enabled, allNamespaces, namespaces, waitCacheReady, cacheLoadingTimeoutSeconds, includeNotReadyAddresses,
|
||||
filter, knownSecurePorts, serviceLabels, primaryPortName, metadata, order, useEndpointSlices, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default instance.
|
||||
*/
|
||||
public static final KubernetesDiscoveryProperties DEFAULT = new KubernetesDiscoveryProperties(true, false, Set.of(),
|
||||
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
|
||||
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false,
|
||||
false);
|
||||
|
||||
/**
|
||||
* @param addLabels include labels as metadata
|
||||
|
||||
@@ -53,6 +53,7 @@ class KubernetesDiscoveryPropertiesTests {
|
||||
assertThat(props.primaryPortName()).isNull();
|
||||
assertThat(props.order()).isZero();
|
||||
assertThat(props.useEndpointSlices()).isFalse();
|
||||
assertThat(props.includeExternalNameServices()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +65,8 @@ class KubernetesDiscoveryPropertiesTests {
|
||||
"spring.cloud.kubernetes.discovery.metadata.labelsPrefix=labelsPrefix",
|
||||
"spring.cloud.kubernetes.discovery.use-endpoint-slices=true",
|
||||
"spring.cloud.kubernetes.discovery.namespaces[0]=ns1",
|
||||
"spring.cloud.kubernetes.discovery.namespaces[1]=ns2")
|
||||
"spring.cloud.kubernetes.discovery.namespaces[1]=ns2",
|
||||
"spring.cloud.kubernetes.discovery.include-external-name-services=true")
|
||||
.run(context -> {
|
||||
KubernetesDiscoveryProperties props = context.getBean(KubernetesDiscoveryProperties.class);
|
||||
assertThat(props).isNotNull();
|
||||
@@ -84,6 +86,7 @@ class KubernetesDiscoveryPropertiesTests {
|
||||
assertThat(props.primaryPortName()).isNull();
|
||||
assertThat(props.order()).isZero();
|
||||
assertThat(props.useEndpointSlices()).isTrue();
|
||||
assertThat(props.includeExternalNameServices()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,16 @@ import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.EXTERNAL_NAME;
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.addresses;
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.endpoints;
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.endpointsPort;
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.serviceInstance;
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.serviceMetadata;
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.services;
|
||||
|
||||
/**
|
||||
* Kubernetes implementation of {@link DiscoveryClient}.
|
||||
* Fabric8 Kubernetes implementation of {@link DiscoveryClient}.
|
||||
*
|
||||
* @author Ioannis Canellos
|
||||
* @author Tim Ysewyn
|
||||
@@ -110,6 +112,20 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw
|
||||
instances.addAll(getNamespaceServiceInstances(es, serviceId));
|
||||
}
|
||||
|
||||
if (properties.includeExternalNameServices()) {
|
||||
LOG.debug(() -> "Searching for 'ExternalName' type of services with serviceId : " + serviceId);
|
||||
List<Service> services = services(properties, client, namespaceProvider,
|
||||
s -> s.getSpec().getType().equals(EXTERNAL_NAME), Map.of("metadata.name", serviceId),
|
||||
"fabric8-discovery");
|
||||
for (Service service : services) {
|
||||
Map<String, String> serviceMetadata = serviceMetadata(serviceId, service, properties, List.of(),
|
||||
service.getMetadata().getNamespace());
|
||||
ServiceInstance externalNameServiceInstance = serviceInstance(null, service, null, -1, serviceId,
|
||||
serviceMetadata, service.getMetadata().getNamespace());
|
||||
instances.add(externalNameServiceInstance);
|
||||
}
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.HashMap;
|
||||
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;
|
||||
@@ -30,10 +31,12 @@ import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsList;
|
||||
import io.fabric8.kubernetes.api.model.ObjectReference;
|
||||
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.FilterNested;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import io.fabric8.kubernetes.client.dsl.ServiceResource;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -52,6 +55,7 @@ import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesD
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.HTTPS;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.NAMESPACE_METADATA_KEY;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.PRIMARY_PORT_NAME_LABEL_KEY;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.SERVICE_TYPE;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
@@ -159,11 +163,14 @@ final class KubernetesDiscoveryClientUtils {
|
||||
.filter(port -> StringUtils.hasText(port.getName()))
|
||||
.collect(toMap(EndpointPort::getName, port -> Integer.toString(port.getPort())));
|
||||
Map<String, String> portMetadata = keysWithPrefix(ports, properties.metadata().portsPrefix());
|
||||
LOG.debug(() -> "Adding port metadata: " + portMetadata + " for serviceId : " + serviceId);
|
||||
if (!portMetadata.isEmpty()) {
|
||||
LOG.debug(() -> "Adding port metadata: " + portMetadata + " for serviceId : " + serviceId);
|
||||
}
|
||||
serviceMetadata.putAll(portMetadata);
|
||||
}
|
||||
|
||||
serviceMetadata.put(NAMESPACE_METADATA_KEY, namespace);
|
||||
serviceMetadata.put(SERVICE_TYPE, service.getSpec().getType());
|
||||
return serviceMetadata;
|
||||
}
|
||||
|
||||
@@ -227,19 +234,76 @@ final class KubernetesDiscoveryClientUtils {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
static ServiceInstance serviceInstance(ServicePortSecureResolver servicePortSecureResolver, Service service,
|
||||
EndpointAddress endpointAddress, int endpointPort, String serviceId, Map<String, String> serviceMetadata,
|
||||
String namespace) {
|
||||
static ServiceInstance serviceInstance(@Nullable ServicePortSecureResolver servicePortSecureResolver,
|
||||
Service service, @Nullable EndpointAddress endpointAddress, int endpointPort, String serviceId,
|
||||
Map<String, String> serviceMetadata, String namespace) {
|
||||
// instanceId is usually the pod-uid as seen in the .metadata.uid
|
||||
String instanceId = Optional.ofNullable(endpointAddress.getTargetRef()).map(ObjectReference::getUid)
|
||||
.orElse(null);
|
||||
String instanceId = Optional.ofNullable(endpointAddress).map(EndpointAddress::getTargetRef)
|
||||
.map(ObjectReference::getUid).orElseGet(() -> service.getMetadata().getUid());
|
||||
|
||||
boolean secured = servicePortSecureResolver
|
||||
.resolve(new ServicePortSecureResolver.Input(endpointPort, service.getMetadata().getName(),
|
||||
service.getMetadata().getLabels(), service.getMetadata().getAnnotations()));
|
||||
boolean secured;
|
||||
if (servicePortSecureResolver == null) {
|
||||
secured = false;
|
||||
}
|
||||
else {
|
||||
secured = servicePortSecureResolver
|
||||
.resolve(new ServicePortSecureResolver.Input(endpointPort, service.getMetadata().getName(),
|
||||
service.getMetadata().getLabels(), service.getMetadata().getAnnotations()));
|
||||
}
|
||||
|
||||
String host = Optional.ofNullable(endpointAddress).map(EndpointAddress::getIp)
|
||||
.orElseGet(() -> service.getSpec().getExternalName());
|
||||
|
||||
return new DefaultKubernetesServiceInstance(instanceId, serviceId, host, endpointPort, serviceMetadata, secured,
|
||||
namespace, null);
|
||||
}
|
||||
|
||||
static List<Service> services(KubernetesDiscoveryProperties properties, KubernetesClient client,
|
||||
KubernetesNamespaceProvider namespaceProvider, Predicate<Service> predicate,
|
||||
Map<String, String> fieldFilters, String target) {
|
||||
|
||||
List<Service> services;
|
||||
|
||||
if (properties.allNamespaces()) {
|
||||
LOG.debug(() -> "discovering services in all namespaces");
|
||||
services = filteredServices(client.services().inAnyNamespace().withNewFilter(), properties, predicate,
|
||||
fieldFilters);
|
||||
}
|
||||
else if (!properties.namespaces().isEmpty()) {
|
||||
LOG.debug(() -> "discovering services in namespaces : " + properties.namespaces());
|
||||
List<Service> inner = new ArrayList<>(properties.namespaces().size());
|
||||
properties.namespaces().forEach(
|
||||
namespace -> inner.addAll(filteredServices(client.services().inNamespace(namespace).withNewFilter(),
|
||||
properties, predicate, fieldFilters)));
|
||||
services = inner;
|
||||
}
|
||||
else {
|
||||
String namespace = Fabric8Utils.getApplicationNamespace(client, null, target, namespaceProvider);
|
||||
LOG.debug(() -> "discovering services in namespace : " + namespace);
|
||||
services = filteredServices(client.services().inNamespace(namespace).withNewFilter(), properties, predicate,
|
||||
fieldFilters);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/**
|
||||
* serviceName can be null, in which case, such a filter will not be applied.
|
||||
*/
|
||||
private static List<Service> filteredServices(
|
||||
FilterNested<FilterWatchListDeletable<Service, ServiceList, ServiceResource<Service>>> filterNested,
|
||||
KubernetesDiscoveryProperties properties, Predicate<Service> predicate,
|
||||
@Nullable Map<String, String> fieldFilters) {
|
||||
|
||||
FilterNested<FilterWatchListDeletable<Service, ServiceList, ServiceResource<Service>>> partial = filterNested
|
||||
.withLabels(properties.serviceLabels());
|
||||
|
||||
if (fieldFilters != null) {
|
||||
partial = partial.withFields(fieldFilters);
|
||||
}
|
||||
|
||||
return partial.endFilter().list().getItems().stream().filter(predicate).toList();
|
||||
|
||||
return new DefaultKubernetesServiceInstance(instanceId, serviceId, endpointAddress.getIp(), endpointPort,
|
||||
serviceMetadata, secured, namespace, null);
|
||||
}
|
||||
|
||||
private static Optional<Integer> fromMap(Map<String, Integer> existingPorts, String key, String message) {
|
||||
|
||||
@@ -302,7 +302,7 @@ abstract class Fabric8EndpointsAndEndpointSlicesTests {
|
||||
Endpoints endpoints = new EndpointsBuilder()
|
||||
.withMetadata(new ObjectMetaBuilder().withLabels(labels).withName("endpoints-" + podName).build())
|
||||
.withSubsets(List.of(endpointSubset)).build();
|
||||
mockClient().endpoints().inNamespace(namespace).create(endpoints);
|
||||
mockClient().endpoints().inNamespace(namespace).resource(endpoints).create();
|
||||
}
|
||||
|
||||
static void endpointSlice(String namespace, Map<String, String> labels, String podName) {
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* 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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
|
||||
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.KubernetesNamespaceProvider;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClientUtils.services;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
class Fabric8KubernetesDiscoveryClientUtilsTests {
|
||||
|
||||
private static KubernetesClient client;
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
client.services().inAnyNamespace().delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = true
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "B"
|
||||
* - no filters are applied, so both are present
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesAllNamespacesNoFilters() {
|
||||
boolean allNamespaces = true;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of());
|
||||
service("serviceB", "B", Map.of());
|
||||
|
||||
List<Service> result = services(properties, client, null, x -> true, null, "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result.stream().map(s -> s.getMetadata().getName()).sorted().toList(),
|
||||
List.of("serviceA", "serviceB"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = true
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "B"
|
||||
* - we search only for "serviceA" filter, so only one is returned
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesAllNamespacesNameFilter() {
|
||||
boolean allNamespaces = true;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of());
|
||||
service("serviceB", "B", Map.of());
|
||||
|
||||
List<Service> result = services(properties, client, null, x -> true, Map.of("metadata.name", "serviceA"),
|
||||
"fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceA");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = true
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "B"
|
||||
* - we search with a filter where a label with name "letter" and value "b" is present
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesAllNamespacesPredicateFilter() {
|
||||
boolean allNamespaces = true;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of("letter", "a"));
|
||||
service("serviceB", "B", Map.of("letter", "b"));
|
||||
|
||||
List<Service> result = services(properties, client, null,
|
||||
x -> x.getMetadata().getLabels().equals(Map.of("letter", "b")), null, "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceB");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - selective namespaces : [A, B]
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "B"
|
||||
* - serviceC present in namespace "C"
|
||||
* - we search in namespaces [A, B], as such two services are returned
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesSelectiveNamespacesNoFilters() {
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces,
|
||||
Set.of("A", "B"), true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of());
|
||||
service("serviceB", "B", Map.of());
|
||||
service("serviceC", "C", Map.of());
|
||||
|
||||
List<Service> result = services(properties, client, null, x -> true, null, "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result.stream().map(x -> x.getMetadata().getName()).sorted().toList(),
|
||||
List.of("serviceA", "serviceB"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - selective namespaces : [A, B]
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "B"
|
||||
* - serviceC present in namespace "C"
|
||||
* - we search in namespaces [A, B] with name filter = "serviceA", so we get a single result
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesSelectiveNamespacesNameFilter() {
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces,
|
||||
Set.of("A", "B"), true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of());
|
||||
service("serviceB", "B", Map.of());
|
||||
service("serviceC", "C", Map.of());
|
||||
|
||||
List<Service> result = services(properties, client, null, x -> true, Map.of("metadata.name", "serviceA"),
|
||||
"fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceA");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - selective namespaces : [A, B]
|
||||
* - serviceA present in namespace "A" with labels [letter, a]
|
||||
* - serviceB present in namespace "B" with labels [letter, b]
|
||||
* - serviceC present in namespace "C" with labels [letter, c]
|
||||
* - we search in namespaces [A, B] with predicate filter = [letter, b], so we get a single result
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesSelectiveNamespacesPredicateFilter() {
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces,
|
||||
Set.of("A", "B"), true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of("letter", "a"));
|
||||
service("serviceB", "B", Map.of("letter", "b"));
|
||||
service("serviceC", "C", Map.of("letter", "c"));
|
||||
|
||||
List<Service> result = services(properties, client, null,
|
||||
x -> x.getMetadata().getLabels().equals(Map.of("letter", "b")), null, "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceB");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - selective namespaces : []
|
||||
* - namespace from kubernetes namespace provider = [A]
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "B"
|
||||
* - serviceC present in namespace "C"
|
||||
* - we search in namespaces [A], as such we get one service
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesNamespaceProviderNoFilters() {
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of());
|
||||
service("serviceB", "B", Map.of());
|
||||
service("serviceC", "C", Map.of());
|
||||
|
||||
List<Service> result = services(properties, client, namespaceProvider("A"), x -> true, null,
|
||||
"fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceA");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - selective namespaces : []
|
||||
* - namespace from kubernetes namespace provider = [A]
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "A"
|
||||
* - we search in namespaces [A] with name filter = "serviceA", so we get a single result
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesNamespaceProviderNameFilter() {
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of());
|
||||
service("serviceB", "A", Map.of());
|
||||
|
||||
List<Service> result = services(properties, client, namespaceProvider("A"), x -> true,
|
||||
Map.of("metadata.name", "serviceA"), "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceA");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - selective namespaces : []
|
||||
* - namespace from kubernetes namespace provider = [A]
|
||||
* - serviceA present in namespace "A"
|
||||
* - serviceB present in namespace "A"
|
||||
* - we search in namespaces [A] with predicate filter = [letter, b], so we get a single result
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServicesNamespaceProviderPredicateFilter() {
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
service("serviceA", "A", Map.of("letter", "a"));
|
||||
service("serviceB", "A", Map.of("letter", "b"));
|
||||
|
||||
List<Service> result = services(properties, client, namespaceProvider("A"),
|
||||
x -> x.getMetadata().getLabels().equals(Map.of("letter", "b")), null, "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "serviceB");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExternalName() {
|
||||
Service service = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").withExternalName("k8s-spring").build())
|
||||
.withNewMetadata().withName("external-name-service").and().build();
|
||||
client.services().inNamespace("test").resource(service).create();
|
||||
|
||||
boolean allNamespaces = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, Set.of(),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
|
||||
List<Service> result = services(properties, client, namespaceProvider("test"),
|
||||
x -> x.getSpec().getType().equals("ExternalName"), Map.of(), "fabric8-discovery");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "external-name-service");
|
||||
}
|
||||
|
||||
private void service(String name, String namespace, Map<String, String> labels) {
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName(name).withLabels(labels)
|
||||
.withNamespace(namespace).and().build();
|
||||
client.services().inNamespace(namespace).resource(service).create();
|
||||
}
|
||||
|
||||
private KubernetesNamespaceProvider namespaceProvider(String namespace) {
|
||||
MockEnvironment mockEnvironment = new MockEnvironment();
|
||||
mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", namespace);
|
||||
return new KubernetesNamespaceProvider(mockEnvironment);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.api.model.ServicePort;
|
||||
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterNested;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
|
||||
@@ -91,7 +92,7 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).isEqualTo(Map.of("k8s_namespace", "ns"));
|
||||
assertThat(instances.get(0).getMetadata()).isEqualTo(Map.of("k8s_namespace", "ns", "type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,7 +112,7 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("l1", "v1"), entry("l2", "v2"),
|
||||
entry("k8s_namespace", "ns"));
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,7 +132,7 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("l_l1", "v1"), entry("l_l2", "v2"),
|
||||
entry("k8s_namespace", "ns"));
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -151,7 +152,7 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a1", "v1"), entry("a2", "v2"),
|
||||
entry("k8s_namespace", "ns"));
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -171,7 +172,7 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a_a1", "v1"), entry("a_a2", "v2"),
|
||||
entry("k8s_namespace", "ns"));
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,7 +191,8 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("http", "80"), entry("k8s_namespace", "test"));
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("http", "80"), entry("k8s_namespace", "test"),
|
||||
entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,7 +211,8 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("p_http", "80"), entry("k8s_namespace", "ns"));
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("p_http", "80"), entry("k8s_namespace", "ns"),
|
||||
entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -229,14 +232,15 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a_a1", "an1"), entry("a_a2", "an2"),
|
||||
entry("l_l1", "la1"), entry("p_http", "80"), entry("k8s_namespace", "ns"));
|
||||
entry("l_l1", "la1"), entry("p_http", "80"), entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
private void setupServiceWithLabelsAndAnnotationsAndPorts(String serviceId, String namespace,
|
||||
Map<String, String> labels, Map<String, String> annotations, Map<Integer, String> ports) {
|
||||
Service service = new ServiceBuilder().withNewMetadata().withNamespace(namespace).withLabels(labels)
|
||||
.withAnnotations(annotations).endMetadata().withNewSpec().withPorts(getServicePorts(ports)).endSpec()
|
||||
.build();
|
||||
Service service = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ClusterIP").withPorts(getServicePorts(ports)).build())
|
||||
.withNewMetadata().withNamespace(namespace).withLabels(labels).withAnnotations(annotations)
|
||||
.endMetadata().build();
|
||||
when(serviceOperation.withName(serviceId)).thenReturn(serviceResource);
|
||||
when(serviceResource.get()).thenReturn(service);
|
||||
when(CLIENT.services()).thenReturn(serviceOperation);
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
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.ServiceSpecBuilder;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
@@ -73,8 +74,8 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
.withLabels(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint").withNamespace("test").withLabels(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -99,8 +100,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint").withNamespace("test").withLabels(labels).withAnnotations(labels)
|
||||
.endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -207,8 +209,8 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
.withLabels(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint").withNamespace("test").withLabels(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -320,10 +322,12 @@ class KubernetesDiscoveryClientTest {
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoints1).create();
|
||||
mockClient.endpoints().inNamespace("test2").resource(endPoints2).create();
|
||||
|
||||
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
Service service1 = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
.withLabels(Collections.singletonMap("l", "v")).endMetadata().build();
|
||||
|
||||
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test2")
|
||||
Service service2 = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint").withNamespace("test2")
|
||||
.withLabels(Collections.singletonMap("l", "v")).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service1).create();
|
||||
@@ -375,8 +379,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint1).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint2").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint2").withNamespace("test").withLabels(labels)
|
||||
.withAnnotations(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -405,8 +410,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint1).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint3").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint3").withNamespace("test").withLabels(labels)
|
||||
.withAnnotations(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -435,8 +441,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint1).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint4").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint4").withNamespace("test").withLabels(labels)
|
||||
.withAnnotations(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -464,8 +471,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint1).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint5").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint5").withNamespace("test").withLabels(labels)
|
||||
.withAnnotations(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -493,8 +501,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint1).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint5").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint5").withNamespace("test").withLabels(labels)
|
||||
.withAnnotations(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
@@ -520,8 +529,9 @@ class KubernetesDiscoveryClientTest {
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint1).create();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint5").withNamespace("test")
|
||||
.withLabels(labels).withAnnotations(labels).endMetadata().build();
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ExternalName").build())
|
||||
.withNewMetadata().withName("endpoint5").withNamespace("test").withLabels(labels)
|
||||
.withAnnotations(labels).endMetadata().build();
|
||||
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
@@ -35,6 +38,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
/**
|
||||
@@ -60,6 +65,7 @@ class KubernetesDiscoveryClientTests {
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
client.endpoints().inAnyNamespace().delete();
|
||||
client.services().inAnyNamespace().delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +232,7 @@ class KubernetesDiscoveryClientTests {
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of();
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<Endpoints> result = discoveryClient.getEndPointsList("serviceId");
|
||||
@@ -276,7 +282,7 @@ class KubernetesDiscoveryClientTests {
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<Endpoints> result = discoveryClient.getEndPointsList("blue-service");
|
||||
@@ -327,7 +333,7 @@ class KubernetesDiscoveryClientTests {
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<Endpoints> result = discoveryClient.getEndPointsList("service-one");
|
||||
@@ -377,7 +383,7 @@ class KubernetesDiscoveryClientTests {
|
||||
Set<String> namespaces = Set.of("test");
|
||||
Map<String, String> serviceLabels = Map.of();
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<Endpoints> result = discoveryClient.getEndPointsList("serviceId");
|
||||
@@ -459,7 +465,7 @@ class KubernetesDiscoveryClientTests {
|
||||
String namespacesAsString = namespaces.toString();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<Endpoints> result = discoveryClient.getEndPointsList("blue-service");
|
||||
@@ -467,6 +473,62 @@ class KubernetesDiscoveryClientTests {
|
||||
Assertions.assertTrue(output.getOut().contains("discovering endpoints in namespaces : " + namespacesAsString));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - two services are present in two namespaces [a, b]
|
||||
* - both are returned
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testGetServicesWithExternalNameService() {
|
||||
Service nonExternalNameService = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ClusterIP").build()).withNewMetadata()
|
||||
.withName("blue-service").withNamespace("a").endMetadata().build();
|
||||
client.services().inNamespace("a").resource(nonExternalNameService).create();
|
||||
|
||||
Service externalNameService = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").withExternalName("k8s-spring").build())
|
||||
.withNewMetadata().withName("blue-service").withNamespace("b").endMetadata().build();
|
||||
client.services().inNamespace("b").resource(externalNameService).create();
|
||||
|
||||
// last argument is irrelevant, as getServices does not care about that flag
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of("a", "b"), true,
|
||||
60L, false, "", Set.of(), Map.of(), "", KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<String> result = discoveryClient.getServices();
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
// this looks weird at the moment, but there is an issue that will fix this
|
||||
Assertions.assertEquals(result.get(0), "blue-service");
|
||||
Assertions.assertEquals(result.get(1), "blue-service");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExternalNameService() {
|
||||
Service externalNameService = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").withExternalName("k8s-spring-b").build())
|
||||
.withNewMetadata().withLabels(Map.of("label-key", "label-value")).withAnnotations(Map.of("abc", "def"))
|
||||
.withName("blue-service").withNamespace("b").endMetadata().build();
|
||||
client.services().inNamespace("b").resource(externalNameService).create();
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(true,
|
||||
"labels-prefix-", true, "annotations-prefix-", true, "ports-prefix");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of("a", "b"), true,
|
||||
60L, false, "", Set.of(), Map.of(), "", metadata, 0, false, true);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
List<ServiceInstance> result = discoveryClient.getInstances("blue-service");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
DefaultKubernetesServiceInstance externalNameServiceInstance = (DefaultKubernetesServiceInstance) result.get(0);
|
||||
Assertions.assertEquals(externalNameServiceInstance.getServiceId(), "blue-service");
|
||||
Assertions.assertEquals(externalNameServiceInstance.getHost(), "k8s-spring-b");
|
||||
Assertions.assertEquals(externalNameServiceInstance.getPort(), -1);
|
||||
Assertions.assertFalse(externalNameServiceInstance.isSecure());
|
||||
Assertions.assertEquals(externalNameServiceInstance.getUri().toASCIIString(), "k8s-spring-b");
|
||||
Assertions.assertEquals(externalNameServiceInstance.getMetadata(), Map.of("k8s_namespace", "b",
|
||||
"labels-prefix-label-key", "label-value", "annotations-prefix-abc", "def", "type", "ExternalName"));
|
||||
}
|
||||
|
||||
private void createEndpoints(String namespace, String name, Map<String, String> labels) {
|
||||
client.endpoints().inNamespace(namespace)
|
||||
.resource(new EndpointsBuilder()
|
||||
|
||||
@@ -32,6 +32,7 @@ import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -282,7 +283,7 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
|
||||
String primaryPortName = "three";
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), primaryPortName, null, 0, false);
|
||||
true, "", Set.of(), Map.of(), primaryPortName, null, 0, false, false);
|
||||
|
||||
Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build();
|
||||
|
||||
@@ -345,13 +346,13 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder().build();
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build()).build();
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
List.of(), namespace);
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result, Map.of("k8s_namespace", "default"));
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result, Map.of("k8s_namespace", "default", "type", "ClusterIP"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,15 +375,15 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder()
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withLabels(Map.of("a", "b")).build()).build();
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
List.of(), namespace);
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result, Map.of("a", "b", "k8s_namespace", "default"));
|
||||
String labelsMetadata = filterOnK8sNamespace(result);
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("a", "b", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
String labelsMetadata = filterOnK8sNamespaceAndType(result);
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding labels metadata: " + labelsMetadata + " for serviceId: my-service"));
|
||||
}
|
||||
@@ -407,16 +408,17 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder()
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withLabels(Map.of("a", "b", "c", "d")).build()).build();
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
List.of(), namespace);
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("prefix-a", "b", "prefix-c", "d", "k8s_namespace", "default"));
|
||||
Assertions.assertEquals(result.size(), 4);
|
||||
Assertions.assertEquals(result,
|
||||
Map.of("prefix-a", "b", "prefix-c", "d", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
// so that result is deterministic in assertion
|
||||
String labelsMetadata = filterOnK8sNamespace(result);
|
||||
String labelsMetadata = filterOnK8sNamespaceAndType(result);
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding labels metadata: " + labelsMetadata + " for serviceId: my-service"));
|
||||
}
|
||||
@@ -441,15 +443,16 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder().withMetadata(
|
||||
new ObjectMetaBuilder().withAnnotations(Map.of("aa", "bb")).withLabels(Map.of("a", "b")).build())
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withAnnotations(Map.of("aa", "bb")).withLabels(Map.of("a", "b"))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
List.of(), namespace);
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result, Map.of("aa", "bb", "k8s_namespace", "default"));
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("aa", "bb", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
Assertions
|
||||
.assertTrue(output.getOut().contains("Adding annotations metadata: {aa=bb} for serviceId: my-service"));
|
||||
}
|
||||
@@ -474,16 +477,19 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder().withMetadata(new ObjectMetaBuilder()
|
||||
.withAnnotations(Map.of("aa", "bb", "cc", "dd")).withLabels(Map.of("a", "b")).build()).build();
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withAnnotations(Map.of("aa", "bb", "cc", "dd"))
|
||||
.withLabels(Map.of("a", "b")).build())
|
||||
.build();
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
List.of(), namespace);
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("prefix-aa", "bb", "prefix-cc", "dd", "k8s_namespace", "default"));
|
||||
Assertions.assertEquals(result.size(), 4);
|
||||
Assertions.assertEquals(result,
|
||||
Map.of("prefix-aa", "bb", "prefix-cc", "dd", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
// so that result is deterministic in assertion
|
||||
String annotations = filterOnK8sNamespace(result);
|
||||
String annotations = filterOnK8sNamespaceAndType(result);
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding annotations metadata: " + annotations + " for serviceId: my-service"));
|
||||
}
|
||||
@@ -508,16 +514,17 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder().withMetadata(new ObjectMetaBuilder()
|
||||
.withAnnotations(Map.of("aa", "bb", "cc", "dd")).withLabels(Map.of("a", "b", "c", "d")).build())
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ClusterIP").build()).withMetadata(new ObjectMetaBuilder()
|
||||
.withAnnotations(Map.of("aa", "bb", "cc", "dd")).withLabels(Map.of("a", "b", "c", "d")).build())
|
||||
.build();
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
List.of(), namespace);
|
||||
Assertions.assertEquals(result.size(), 5);
|
||||
Assertions.assertEquals(result.size(), 6);
|
||||
Assertions.assertEquals(result, Map.of("annotation-aa", "bb", "annotation-cc", "dd", "label-a", "b", "label-c",
|
||||
"d", "k8s_namespace", "default"));
|
||||
"d", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
// so that result is deterministic in assertion
|
||||
String labels = result.entrySet().stream().filter(en -> en.getKey().contains("label"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
|
||||
@@ -548,9 +555,11 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder().withMetadata(new ObjectMetaBuilder()
|
||||
.withAnnotations(Map.of("aa", "bb", "cc", "dd")).withLabels(Map.of("a", "b")).build()).build();
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withAnnotations(Map.of("aa", "bb", "cc", "dd"))
|
||||
.withLabels(Map.of("a", "b")).build())
|
||||
.build();
|
||||
|
||||
List<EndpointSubset> endpointSubsets = List.of(
|
||||
new EndpointSubsetBuilder().withPorts(new EndpointPortBuilder().withPort(8081).withName("").build())
|
||||
@@ -560,8 +569,8 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
endpointSubsets, namespace);
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result, Map.of("https", "8080", "k8s_namespace", "default"));
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("https", "8080", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
Assertions
|
||||
.assertTrue(output.getOut().contains("Adding port metadata: {https=8080} for serviceId : my-service"));
|
||||
}
|
||||
@@ -585,9 +594,11 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false);
|
||||
Service service = new ServiceBuilder().withMetadata(new ObjectMetaBuilder()
|
||||
.withAnnotations(Map.of("aa", "bb", "cc", "dd")).withLabels(Map.of("a", "b")).build()).build();
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withAnnotations(Map.of("aa", "bb", "cc", "dd"))
|
||||
.withLabels(Map.of("a", "b")).build())
|
||||
.build();
|
||||
|
||||
List<EndpointSubset> endpointSubsets = List.of(
|
||||
new EndpointSubsetBuilder().withPorts(new EndpointPortBuilder().withPort(8081).withName("http").build())
|
||||
@@ -597,9 +608,9 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata("my-service", service, properties,
|
||||
endpointSubsets, namespace);
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result.size(), 4);
|
||||
Assertions.assertEquals(result,
|
||||
Map.of("prefix-https", "8080", "prefix-http", "8081", "k8s_namespace", "default"));
|
||||
Map.of("prefix-https", "8080", "prefix-http", "8081", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
Assertions.assertTrue(output.getOut()
|
||||
.contains("Adding port metadata: {prefix-http=8081, prefix-https=8080} for serviceId : my-service"));
|
||||
}
|
||||
@@ -614,7 +625,7 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
void testEmptyAddresses() {
|
||||
boolean includeNotReadyAddresses = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false, false);
|
||||
EndpointSubset endpointSubset = new EndpointSubsetBuilder().build();
|
||||
List<EndpointAddress> addresses = KubernetesDiscoveryClientUtils.addresses(endpointSubset, properties);
|
||||
Assertions.assertEquals(addresses.size(), 0);
|
||||
@@ -649,7 +660,7 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
void testReadyAddressesTakenNotReadyAddressesNotTaken() {
|
||||
boolean includeNotReadyAddresses = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false, false);
|
||||
EndpointSubset endpointSubset = new EndpointSubsetBuilder()
|
||||
.withAddresses(new EndpointAddressBuilder().withHostname("one").build(),
|
||||
new EndpointAddressBuilder().withHostname("two").build())
|
||||
@@ -684,7 +695,7 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
@Test
|
||||
void testServiceInstance() {
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
false, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
false, "", Set.of(), Map.of(), "", null, 0, false, false);
|
||||
ServicePortSecureResolver resolver = new ServicePortSecureResolver(properties);
|
||||
Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build();
|
||||
EndpointAddress address = new EndpointAddressBuilder().withNewTargetRef().withUid("123").endTargetRef()
|
||||
@@ -706,8 +717,31 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
Assertions.assertNull(defaultInstance.getCluster());
|
||||
}
|
||||
|
||||
private String filterOnK8sNamespace(Map<String, String> result) {
|
||||
@Test
|
||||
void testExternalNameServiceInstance() {
|
||||
Service service = new ServiceBuilder()
|
||||
.withSpec(new ServiceSpecBuilder().withExternalName("spring.io").withType("ExternalName").build())
|
||||
.withMetadata(new ObjectMetaBuilder().withUid("123").build()).build();
|
||||
|
||||
ServiceInstance serviceInstance = KubernetesDiscoveryClientUtils.serviceInstance(null, service, null, -1,
|
||||
"my-service", Map.of("a", "b"), "k8s");
|
||||
Assertions.assertTrue(serviceInstance instanceof DefaultKubernetesServiceInstance);
|
||||
DefaultKubernetesServiceInstance defaultInstance = (DefaultKubernetesServiceInstance) serviceInstance;
|
||||
Assertions.assertEquals(defaultInstance.getInstanceId(), "123");
|
||||
Assertions.assertEquals(defaultInstance.getServiceId(), "my-service");
|
||||
Assertions.assertEquals(defaultInstance.getHost(), "spring.io");
|
||||
Assertions.assertEquals(defaultInstance.getPort(), -1);
|
||||
Assertions.assertFalse(defaultInstance.isSecure());
|
||||
Assertions.assertEquals(defaultInstance.getUri().toASCIIString(), "spring.io");
|
||||
Assertions.assertEquals(defaultInstance.getMetadata(), Map.of("a", "b"));
|
||||
Assertions.assertEquals(defaultInstance.getScheme(), "http");
|
||||
Assertions.assertEquals(defaultInstance.getNamespace(), "k8s");
|
||||
Assertions.assertNull(defaultInstance.getCluster());
|
||||
}
|
||||
|
||||
private String filterOnK8sNamespaceAndType(Map<String, String> result) {
|
||||
return result.entrySet().stream().filter(en -> !en.getKey().contains("k8s_namespace"))
|
||||
.filter(en -> !en.getKey().equals("type"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.fabric8.kubernetes.api.model.EndpointsList;
|
||||
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.ServiceSpecBuilder;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
@@ -136,7 +137,8 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
@Test
|
||||
void shouldReturnFlux() {
|
||||
ServiceList services = new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withNamespace("test").withLabels(Map.of("label", "value")).endMetadata().endItem().build();
|
||||
.withNamespace("test").withLabels(Map.of("label", "value")).endMetadata()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build();
|
||||
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("existing-service").withNamespace("test")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().addNewSubset().addNewAddress().withIp("ip1")
|
||||
@@ -166,8 +168,10 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
@Test
|
||||
void shouldReturnFluxWithPrefixedMetadata() {
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
|
||||
.andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().endItem().build())
|
||||
.andReturn(200,
|
||||
new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
|
||||
.once();
|
||||
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
@@ -184,12 +188,11 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
.withPath("/api/v1/namespaces/test/endpoints?fieldSelector=metadata.name%3Dexisting-service")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service")
|
||||
.andReturn(200, new ServiceBuilder().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().build())
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service").andReturn(200,
|
||||
new ServiceBuilder().withNewMetadata().withName("existing-service").withLabels(Map.of("label", "value"))
|
||||
.endMetadata().withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).build())
|
||||
.once();
|
||||
|
||||
Metadata metadata = new Metadata(true, "label.", true, "annotation.", true, "port.");
|
||||
ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(kubernetesClient,
|
||||
KubernetesDiscoveryProperties.DEFAULT, KubernetesClient::services);
|
||||
Flux<ServiceInstance> instances = client.getInstances("existing-service");
|
||||
@@ -199,8 +202,10 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
@Test
|
||||
void shouldReturnFluxWhenServiceHasMultiplePortsAndPrimaryPortNameIsSet() {
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
|
||||
.andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().endItem().build())
|
||||
.andReturn(200,
|
||||
new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
|
||||
.once();
|
||||
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
@@ -218,9 +223,9 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
.withPath("/api/v1/namespaces/test/endpoints?fieldSelector=metadata.name%3Dexisting-service")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service")
|
||||
.andReturn(200, new ServiceBuilder().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().build())
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service").andReturn(200,
|
||||
new ServiceBuilder().withNewMetadata().withName("existing-service").withLabels(Map.of("label", "value"))
|
||||
.endMetadata().withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).build())
|
||||
.once();
|
||||
|
||||
ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(kubernetesClient,
|
||||
@@ -232,8 +237,10 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
@Test
|
||||
void shouldReturnFluxOfServicesAcrossAllNamespaces() {
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
|
||||
.andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().endItem().build())
|
||||
.andReturn(200,
|
||||
new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata()
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
|
||||
.once();
|
||||
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
@@ -247,9 +254,9 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
kubernetesServer.expect().get().withPath("/api/v1/endpoints?fieldSelector=metadata.name%3Dexisting-service")
|
||||
.andReturn(200, endpointsList).once();
|
||||
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service")
|
||||
.andReturn(200, new ServiceBuilder().withNewMetadata().withName("existing-service")
|
||||
.withLabels(Map.of("label", "value")).endMetadata().build())
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service").andReturn(200,
|
||||
new ServiceBuilder().withNewMetadata().withName("existing-service").withLabels(Map.of("label", "value"))
|
||||
.endMetadata().withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).build())
|
||||
.once();
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.List;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -47,4 +48,9 @@ public class Fabric8DiscoveryController {
|
||||
return discoveryClient.getEndPointsList(serviceId);
|
||||
}
|
||||
|
||||
@GetMapping("/service-instances/{serviceId}")
|
||||
public List<ServiceInstance> serviceInstances(@PathVariable("serviceId") String serviceId) {
|
||||
return discoveryClient.getInstances(serviceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,9 +18,13 @@ package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
@@ -34,9 +38,11 @@ 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;
|
||||
@@ -78,18 +84,46 @@ class Fabric8DiscoveryIT {
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesDiscoveryClient::getServices call must include the external-name-service
|
||||
* also.
|
||||
*/
|
||||
@Test
|
||||
void test() {
|
||||
void testAllServices() {
|
||||
WebClient client = builder().baseUrl("localhost/services").build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> result = (List<String>) client.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
List<String> result = client.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
|
||||
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(result.size(), 4);
|
||||
Assertions.assertTrue(result.contains("kubernetes"));
|
||||
Assertions.assertTrue(result.contains("spring-cloud-kubernetes-fabric8-client-discovery"));
|
||||
Assertions.assertTrue(result.contains("service-wiremock"));
|
||||
Assertions.assertTrue(result.contains("external-name-service"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExternalNameServiceInstance() {
|
||||
|
||||
WebClient client = builder().baseUrl("localhost/service-instances/external-name-service").build();
|
||||
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
DefaultKubernetesServiceInstance result = serviceInstances.get(0);
|
||||
|
||||
Assertions.assertEquals(serviceInstances.size(), 1);
|
||||
Assertions.assertEquals(result.getServiceId(), "external-name-service");
|
||||
Assertions.assertNotNull(result.getInstanceId());
|
||||
Assertions.assertEquals(result.getHost(), "spring.io");
|
||||
Assertions.assertEquals(result.getPort(), -1);
|
||||
Assertions.assertEquals(result.getMetadata(), Map.of("k8s_namespace", "default", "type", "ExternalName"));
|
||||
Assertions.assertFalse(result.isSecure());
|
||||
Assertions.assertEquals(result.getUri().toASCIIString(), "spring.io");
|
||||
Assertions.assertEquals(result.getScheme(), "http");
|
||||
}
|
||||
|
||||
private static void manifests(Phase phase) {
|
||||
@@ -97,16 +131,30 @@ class Fabric8DiscoveryIT {
|
||||
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
|
||||
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
|
||||
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
|
||||
InputStream externalNameServiceInputStream = util.inputStream("external-name-service.yaml");
|
||||
|
||||
Deployment deployment = client.apps().deployments().load(deploymentStream).get();
|
||||
|
||||
List<EnvVar> existing = new ArrayList<>(
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
|
||||
existing.add(new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES")
|
||||
.withValue("true").build());
|
||||
existing.add(
|
||||
new EnvVarBuilder().withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY")
|
||||
.withValue("DEBUG").build());
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(existing);
|
||||
|
||||
Service service = client.services().load(serviceStream).get();
|
||||
Service externalNameService = client.services().load(externalNameServiceInputStream).get();
|
||||
Ingress ingress = client.network().v1().ingresses().load(ingressStream).get();
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
util.createAndWait(NAMESPACE, null, null, externalNameService, null, false);
|
||||
}
|
||||
else {
|
||||
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
|
||||
util.deleteAndWait(NAMESPACE, null, externalNameService, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: external-name-service
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: spring.io
|
||||
@@ -74,24 +74,29 @@ public final class Util {
|
||||
* tight as possible, providing reasonable defaults.
|
||||
*
|
||||
*/
|
||||
public void createAndWait(String namespace, String name, Deployment deployment, Service service,
|
||||
public void createAndWait(String namespace, String name, @Nullable Deployment deployment, Service service,
|
||||
@Nullable Ingress ingress, boolean changeVersion) {
|
||||
try {
|
||||
|
||||
String imageFromDeployment = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
|
||||
if (changeVersion) {
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
|
||||
.setImage(imageFromDeployment + ":" + pomVersion());
|
||||
}
|
||||
else {
|
||||
String[] image = imageFromDeployment.split(":", 2);
|
||||
pullImage(image[0], image[1], container);
|
||||
loadImage(image[0], image[1], name, container);
|
||||
if (deployment != null) {
|
||||
String imageFromDeployment = deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
|
||||
.getImage();
|
||||
if (changeVersion) {
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
|
||||
.setImage(imageFromDeployment + ":" + pomVersion());
|
||||
}
|
||||
else {
|
||||
String[] image = imageFromDeployment.split(":", 2);
|
||||
pullImage(image[0], image[1], container);
|
||||
loadImage(image[0], image[1], name, container);
|
||||
}
|
||||
|
||||
client.apps().deployments().inNamespace(namespace).resource(deployment).create();
|
||||
waitForDeployment(namespace, deployment);
|
||||
}
|
||||
|
||||
client.apps().deployments().inNamespace(namespace).resource(deployment).create();
|
||||
client.services().inNamespace(namespace).resource(service).create();
|
||||
waitForDeployment(namespace, deployment);
|
||||
|
||||
if (ingress != null) {
|
||||
client.network().v1().ingresses().inNamespace(namespace).resource(ingress).create();
|
||||
waitForIngress(namespace, ingress);
|
||||
@@ -116,11 +121,16 @@ public final class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAndWait(String namespace, Deployment deployment, Service service, @Nullable Ingress ingress) {
|
||||
public void deleteAndWait(String namespace, @Nullable Deployment deployment, Service service,
|
||||
@Nullable Ingress ingress) {
|
||||
try {
|
||||
client.apps().deployments().inNamespace(namespace).resource(deployment).delete();
|
||||
|
||||
if (deployment != null) {
|
||||
client.apps().deployments().inNamespace(namespace).resource(deployment).delete();
|
||||
waitForDeploymentToBeDeleted(namespace, deployment);
|
||||
}
|
||||
|
||||
client.services().inNamespace(namespace).resource(service).delete();
|
||||
waitForDeploymentToBeDeleted(namespace, deployment);
|
||||
|
||||
if (ingress != null) {
|
||||
client.network().v1().ingresses().inNamespace(namespace).resource(ingress).delete();
|
||||
|
||||
Reference in New Issue
Block a user