Add externalname support (#1243)

This commit is contained in:
erabii
2023-03-08 23:53:35 +02:00
committed by GitHub
parent b9f9780e5a
commit 8438ff60da
18 changed files with 738 additions and 134 deletions

View File

@@ -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`:
====

View File

@@ -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);
}
}

View File

@@ -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";
}

View File

@@ -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

View File

@@ -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();
});
}

View File

@@ -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;
}

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.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) {

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,7 @@
kind: Service
apiVersion: v1
metadata:
name: external-name-service
spec:
type: ExternalName
externalName: spring.io

View File

@@ -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();