Namespace filter for service discovery (issue #1000). (#1113)

This commit is contained in:
mbialkowski1
2022-12-01 02:01:11 +01:00
committed by GitHub
parent 79c1871eb3
commit 8a0dfe98c5
29 changed files with 1017 additions and 92 deletions

View File

@@ -59,7 +59,8 @@
|spring.cloud.kubernetes.config.sources | |
|spring.cloud.kubernetes.config.use-name-as-prefix | `+++false+++` |
|spring.cloud.kubernetes.discovery.all-namespaces | `+++false+++` |
|spring.cloud.kubernetes.discovery.cache-loading-timeout-seconds | `+++60+++` |
|spring.cloud.kubernetes.discovery.namespaces | `+++[]+++` |
|spring.cloud.kubernetes.discovery.cache-loading-timeout-seconds | `+++60+++` |
|spring.cloud.kubernetes.discovery.enabled | `+++true+++` |
|spring.cloud.kubernetes.discovery.filter | |
|spring.cloud.kubernetes.discovery.include-not-ready-addresses | `+++false+++` |

View File

@@ -78,6 +78,16 @@ spring.cloud.kubernetes.discovery.all-namespaces=true
----
====
To discover services and endpoints only from specified namespaces you should set property `all-namespaces` to `false` and set the following property in `application.properties` (in this example namespaces are: `ns1` and `ns2`).
====
[source]
----
spring.cloud.kubernetes.discovery.namespaces[0]=ns1
spring.cloud.kubernetes.discovery.namespaces[1]=ns2
----
====
To discover service endpoint addresses that are not marked as "ready" by the kubernetes api server, you can set the following property in `application.properties` (default: false):
====

View File

@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.kubernetes.client.extended.wait.Wait;
import io.kubernetes.client.informer.SharedInformer;
@@ -98,14 +99,17 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient, Initi
log.warn("Namespace is null or empty, this may cause issues looking up services");
}
V1Service service = properties.allNamespaces() ? this.serviceLister.list().stream()
.filter(svc -> serviceId.equals(svc.getMetadata().getName())).findFirst().orElse(null)
: this.serviceLister.namespace(this.namespace).get(serviceId);
if (service == null || !matchServiceLabels(service)) {
List<V1Service> services = properties.allNamespaces() ? this.serviceLister.list().stream()
.filter(svc -> serviceId.equals(svc.getMetadata().getName())).toList()
: List.of(this.serviceLister.namespace(this.namespace).get(serviceId));
if (services.size() == 0 || !services.stream().anyMatch(this::matchServiceLabels)) {
// no such service present in the cluster
return new ArrayList<>();
}
return services.stream().flatMap(s -> getServiceInstanceDetails(s, serviceId)).toList();
}
private Stream<ServiceInstance> getServiceInstanceDetails(V1Service service, String serviceId) {
Map<String, String> svcMetadata = new HashMap<>();
if (this.properties.metadata() != null) {
if (this.properties.metadata().addLabels()) {
@@ -132,7 +136,7 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient, Initi
.get(service.getMetadata().getName());
if (ep == null || ep.getSubsets() == null) {
// no available endpoints in the cluster
return new ArrayList<>();
return Stream.empty();
}
Optional<String> discoveredPrimaryPortName = Optional.empty();
@@ -166,7 +170,7 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient, Initi
addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId,
addr.getIp(), port, metadata, false, service.getMetadata().getNamespace(),
service.getMetadata().getClusterName()));
}).collect(Collectors.toList());
});
}
private int findEndpointPort(List<V1EndpointPort> endpointPorts, String primaryPortName, String serviceId) {

View File

@@ -65,6 +65,11 @@ public class KubernetesInformerDiscoveryClientTests {
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
private static final V1Endpoints testEndpoints2 = new V1Endpoints()
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace2"))
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
private static final V1Endpoints testEndpointWithoutReadyAddresses = new V1Endpoints()
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
@@ -108,7 +113,8 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithUnsetPortName);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -141,7 +147,7 @@ public class KubernetesInformerDiscoveryClientTests {
labels.put("spring", "true");
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
true, 60, false, null, Set.of(), labels, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
sharedInformerFactory, serviceLister, null, null, null, kubernetesDiscoveryProperties);
@@ -160,7 +166,7 @@ public class KubernetesInformerDiscoveryClientTests {
labels.put("spring", "true");
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
true, 60, false, null, Set.of(), labels, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -188,7 +194,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -203,7 +209,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -230,7 +236,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithoutReadyAddresses);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, true, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, true, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -271,7 +277,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -290,7 +296,7 @@ public class KubernetesInformerDiscoveryClientTests {
testEndpointWithMultiplePortsWithoutSupportedPortNames);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -306,7 +312,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, "https", null, 0);
Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -322,7 +328,7 @@ public class KubernetesInformerDiscoveryClientTests {
testEndpointWithMultiplePortsWithoutSupportedPortNames);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, "oops", null, 0);
Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -337,7 +343,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -352,7 +358,7 @@ public class KubernetesInformerDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePortsWithoutHttps);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -368,7 +374,7 @@ public class KubernetesInformerDiscoveryClientTests {
testEndpointWithMultiplePortsWithoutSupportedPortNames);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -377,6 +383,24 @@ public class KubernetesInformerDiscoveryClientTests {
"test-svc-1", "1.1.1.1", 80, new HashMap<>(), false, "namespace1", null));
}
@Test
public void getInstancesShouldReturnInstancesWithTheSameServiceIdFromNamespaces() {
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1, testEndpoints2);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(null,
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(
new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false,
"namespace1", null),
new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false,
"namespace2", null));
}
private Lister<V1Service> setupServiceLister(V1Service... services) {
Cache<V1Service> serviceCache = new Cache<>();
Lister<V1Service> serviceLister = new Lister<>(serviceCache);

View File

@@ -101,7 +101,7 @@ public class KubernetesInformerReactiveDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister,
@@ -120,7 +120,7 @@ public class KubernetesInformerReactiveDiscoveryClientTests {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
true, 60, false, null, Set.of(), null, null, null, 0);
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("namespace1");

View File

@@ -146,7 +146,8 @@ class KubernetesClientServicesListSupplierTests {
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("default");
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
CoreV1Api coreV1Api = new CoreV1Api();
KubernetesClientServiceInstanceMapper mapper = new KubernetesClientServiceInstanceMapper(
new KubernetesLoadBalancerProperties(), kubernetesDiscoveryProperties);

View File

@@ -27,6 +27,8 @@ import static org.springframework.cloud.client.discovery.DiscoveryClient.DEFAULT
/**
* @param enabled if kubernetes discovery is enabled
* @param allNamespaces if discover is enabled for all namespaces
* @param namespaces If set and allNamespaces is false, then only the services and
* endpoints matching these namespaces will be fetched from the Kubernetes API server.
* @param waitCacheReady wait for the discovery cache (service and endpoints) to be fully
* loaded, otherwise aborts the application on starting
* @param cacheLoadingTimeoutSeconds timeout for initializing discovery cache, will abort
@@ -45,6 +47,7 @@ import static org.springframework.cloud.client.discovery.DiscoveryClient.DEFAULT
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
public record KubernetesDiscoveryProperties(
@DefaultValue("true") boolean enabled, boolean allNamespaces,
@DefaultValue Set<String> namespaces,
@DefaultValue("true") boolean waitCacheReady,
@DefaultValue("60") long cacheLoadingTimeoutSeconds,
boolean includeNotReadyAddresses, String filter,
@@ -57,8 +60,8 @@ public record KubernetesDiscoveryProperties(
/**
* Default instance.
*/
public static final KubernetesDiscoveryProperties DEFAULT = new KubernetesDiscoveryProperties(true, false, true, 60,
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
public static final KubernetesDiscoveryProperties DEFAULT = new KubernetesDiscoveryProperties(true, false, Set.of(),
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
/**
* @param addLabels include labels as metadata

View File

@@ -43,6 +43,7 @@ class KubernetesDiscoveryPropertiesTests {
assertThat(props.enabled()).isTrue();
assertThat(props.allNamespaces()).isFalse();
assertThat(props.namespaces()).isEmpty();
assertThat(props.waitCacheReady()).isTrue();
assertThat(props.cacheLoadingTimeoutSeconds()).isEqualTo(60);
assertThat(props.includeNotReadyAddresses()).isFalse();
@@ -59,7 +60,9 @@ class KubernetesDiscoveryPropertiesTests {
new ApplicationContextRunner().withUserConfiguration(KubernetesDiscoveryPropertiesMetadataTests.Config.class)
.withPropertyValues("spring.cloud.kubernetes.discovery.filter=some-filter",
"spring.cloud.kubernetes.discovery.knownSecurePorts[0]=222",
"spring.cloud.kubernetes.discovery.metadata.labelsPrefix=labelsPrefix")
"spring.cloud.kubernetes.discovery.metadata.labelsPrefix=labelsPrefix",
"spring.cloud.kubernetes.discovery.namespaces[0]=ns1",
"spring.cloud.kubernetes.discovery.namespaces[1]=ns2")
.run(context -> {
KubernetesDiscoveryProperties props = context.getBean(KubernetesDiscoveryProperties.class);
assertThat(props).isNotNull();
@@ -69,6 +72,7 @@ class KubernetesDiscoveryPropertiesTests {
assertThat(props.enabled()).isTrue();
assertThat(props.allNamespaces()).isFalse();
assertThat(props.namespaces()).containsExactlyInAnyOrder("ns1", "ns2");
assertThat(props.waitCacheReady()).isTrue();
assertThat(props.cacheLoadingTimeoutSeconds()).isEqualTo(60);
assertThat(props.includeNotReadyAddresses()).isFalse();

View File

@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
@@ -54,7 +55,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
KubernetesServiceInstance[] responseBody = rest.getForEntity(
properties.getDiscoveryServerUrl() + "/apps/" + serviceId, KubernetesServiceInstance[].class).getBody();
if (responseBody != null && responseBody.length > 0) {
response = Arrays.asList(responseBody);
response = Arrays.stream(responseBody).filter(this::matchNamespaces).collect(Collectors.toList());
}
return response;
}
@@ -64,9 +65,24 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
List<String> response = Collections.emptyList();
Service[] services = rest.getForEntity(properties.getDiscoveryServerUrl() + "/apps", Service[].class).getBody();
if (services != null && services.length > 0) {
response = Arrays.stream(services).map(service -> service.getName()).collect(Collectors.toList());
response = Arrays.stream(services).filter(this::matchNamespaces).map(Service::getName)
.collect(Collectors.toList());
}
return response;
}
private boolean matchNamespaces(KubernetesServiceInstance kubernetesServiceInstance) {
if (CollectionUtils.isEmpty(properties.getNamespaces())) {
return true;
}
return properties.getNamespaces().contains(kubernetesServiceInstance.getNamespace());
}
private boolean matchNamespaces(Service service) {
if (CollectionUtils.isEmpty(service.getServiceInstances())) {
return true;
}
return service.getServiceInstances().stream().anyMatch(this::matchNamespaces);
}
}

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.kubernetes.discovery;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -28,6 +31,12 @@ public class KubernetesDiscoveryClientProperties {
private boolean enabled = true;
/**
* If set then only the services and endpoints matching these namespaces will be
* fetched from the Kubernetes API server.
*/
private List<String> namespaces = new ArrayList<>();
public String getDiscoveryServerUrl() {
return discoveryServerUrl;
}
@@ -44,4 +53,12 @@ public class KubernetesDiscoveryClientProperties {
this.enabled = enabled;
}
List<String> getNamespaces() {
return namespaces;
}
void setNamespaces(List<String> namespaces) {
this.namespaces = namespaces;
}
}

View File

@@ -18,14 +18,20 @@ package org.springframework.cloud.kubernetes.discovery;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.web.client.RestTemplate;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@@ -39,9 +45,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class KubernetesDiscoveryClientTests {
private static final String APPS = "[{\"name\":\"test-svc-1\",\"serviceInstances\":[{\"instanceId\":\"uid1\",\"serviceId\":\"test-svc-1\",\"host\":\"2.2.2.2\",\"port\":8080,\"uri\":\"http://2.2.2.2:8080\",\"secure\":false,\"metadata\":{\"http\":\"8080\"},\"namespace\":\"namespace1\",\"cluster\":null,\"scheme\":\"http\"}]},{\"name\":\"test-svc-3\",\"serviceInstances\":[{\"instanceId\":\"uid2\",\"serviceId\":\"test-svc-3\",\"host\":\"2.2.2.2\",\"port\":8080,\"uri\":\"http://2.2.2.2:8080\",\"secure\":false,\"metadata\":{\"spring\":\"true\",\"http\":\"8080\",\"k8s\":\"true\"},\"namespace\":\"namespace1\",\"cluster\":null,\"scheme\":\"http\"}]}]";
private static final String APPS = "[{\"name\":\"test-svc-1\",\"serviceInstances\":[{\"instanceId\":\"uid1\",\"serviceId\":\"test-svc-1\",\"host\":\"2.2.2.2\",\"port\":8080,\"uri\":\"http://2.2.2.2:8080\",\"secure\":false,\"metadata\":{\"http\":\"8080\"},\"namespace\":\"namespace1\",\"cluster\":null,\"scheme\":\"http\"}]},{\"name\":\"test-svc-3\",\"serviceInstances\":[{\"instanceId\":\"uid2\",\"serviceId\":\"test-svc-3\",\"host\":\"2.2.2.2\",\"port\":8080,\"uri\":\"http://2.2.2.2:8080\",\"secure\":false,\"metadata\":{\"spring\":\"true\",\"http\":\"8080\",\"k8s\":\"true\"},\"namespace\":\"namespace2\",\"cluster\":null,\"scheme\":\"http\"}]}]";
private static final String APPS_NAME = "[{\"instanceId\":\"uid2\",\"serviceId\":\"test-svc-3\",\"host\":\"2.2.2.2\",\"port\":8080,\"uri\":\"http://2.2.2.2:8080\",\"secure\":false,\"metadata\":{\"spring\":\"true\",\"http\":\"8080\",\"k8s\":\"true\"},\"namespace\":\"namespace1\",\"cluster\":null,\"scheme\":\"http\"}]";
private static final String APPS_NAME = "[{\"instanceId\":\"uid2\",\"serviceId\":\"test-svc-3\",\"host\":\"2.2.2.2\",\"port\":8080,\"uri\":\"http://2.2.2.2:8080\",\"secure\":false,\"metadata\":{\"spring\":\"true\",\"http\":\"8080\",\"k8s\":\"true\"},\"namespace\":\"namespace2\",\"cluster\":null,\"scheme\":\"http\"}]";
private static WireMockServer wireMockServer;
@@ -79,8 +85,44 @@ class KubernetesDiscoveryClientTests {
metadata.put("k8s", "true");
assertThat(discoveryClient.getInstances("test-svc-3"))
.contains(new KubernetesServiceInstance("uid2", "test-svc-3", "2.2.2.2", 8080, false,
URI.create("http://2.2.2.2:8080"), metadata, "http", "namespace1"));
URI.create("http://2.2.2.2:8080"), metadata, "http", "namespace2"));
assertThat(discoveryClient.getInstances("does-not-exist")).isEmpty();
}
@ParameterizedTest
@MethodSource("servicesFilteredByNamespacesSource")
void getServicesFilteredByNamespaces(List<String> namespaces, List<String> expectedServices) {
RestTemplate rest = new RestTemplateBuilder().build();
KubernetesDiscoveryClientProperties properties = new KubernetesDiscoveryClientProperties();
properties.setNamespaces(namespaces);
properties.setDiscoveryServerUrl(wireMockServer.baseUrl());
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(rest, properties);
assertThat(discoveryClient.getServices()).containsExactlyInAnyOrderElementsOf(expectedServices);
}
static Stream<Arguments> servicesFilteredByNamespacesSource() {
return Stream.of(Arguments.of(List.of(), List.of("test-svc-1", "test-svc-3")),
Arguments.of(List.of("namespace1", "namespace2"), List.of("test-svc-1", "test-svc-3")),
Arguments.of(List.of("namespace1"), List.of("test-svc-1")),
Arguments.of(List.of("namespace2", "does-not-exist"), List.of("test-svc-3")));
}
@ParameterizedTest
@MethodSource("instancesFilteredByNamespacesSource")
void getInstancesFilteredByNamespaces(List<String> namespaces, String serviceId, List<String> expectedInstances) {
RestTemplate rest = new RestTemplateBuilder().build();
KubernetesDiscoveryClientProperties properties = new KubernetesDiscoveryClientProperties();
properties.setNamespaces(namespaces);
properties.setDiscoveryServerUrl(wireMockServer.baseUrl());
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(rest, properties);
assertThat(discoveryClient.getInstances(serviceId)).map(ServiceInstance::getInstanceId)
.containsExactlyInAnyOrderElementsOf(expectedInstances);
}
static Stream<Arguments> instancesFilteredByNamespacesSource() {
return Stream.of(Arguments.of(List.of(), "test-svc-3", List.of("uid2")),
Arguments.of(List.of("namespace1"), "test-svc-3", List.of()),
Arguments.of(List.of("namespace2"), "test-svc-3", List.of("uid2")));
}
}

View File

@@ -121,11 +121,24 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
}
public List<Endpoints> getEndPointsList(String serviceId) {
return this.properties.allNamespaces()
? this.client.endpoints().inAnyNamespace().withField("metadata.name", serviceId)
.withLabels(properties.serviceLabels()).list().getItems()
: this.client.endpoints().withField("metadata.name", serviceId).withLabels(properties.serviceLabels())
.list().getItems();
if (this.properties.allNamespaces()) {
return this.client.endpoints().inAnyNamespace().withField("metadata.name", serviceId)
.withLabels(properties.serviceLabels()).list().getItems();
}
if (properties.namespaces().isEmpty()) {
return this.client.endpoints().withField("metadata.name", serviceId).withLabels(properties.serviceLabels())
.list().getItems();
}
return findEndPointsFilteredByNamespaces(serviceId);
}
private List<Endpoints> findEndPointsFilteredByNamespaces(String serviceId) {
List<Endpoints> endpoints = new ArrayList<>();
for (String ns : properties.namespaces()) {
endpoints.addAll(getClient().endpoints().inNamespace(ns).withField("metadata.name", serviceId)
.withLabels(properties.serviceLabels()).list().getItems());
}
return endpoints;
}
private List<ServiceInstance> getNamespaceServiceInstances(EndpointSubsetNS es, String serviceId) {
@@ -297,8 +310,16 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
}
public List<String> getServices(Predicate<Service> filter) {
return this.kubernetesClientServicesFunction.apply(this.client).list().getItems().stream().filter(filter)
.map(s -> s.getMetadata().getName()).collect(Collectors.toList());
if (properties.namespaces().isEmpty()) {
return this.kubernetesClientServicesFunction.apply(this.client).list().getItems().stream().filter(filter)
.map(s -> s.getMetadata().getName()).collect(Collectors.toList());
}
List<String> services = new ArrayList<>();
for (String ns : properties.namespaces()) {
services.addAll(getClient().services().inNamespace(ns).list().getItems().stream().filter(filter)
.map(s -> s.getMetadata().getName()).toList());
}
return services;
}
@Override

View File

@@ -245,8 +245,8 @@ class Fabric8KubernetesCatalogWatchTests {
when(namespaceProvider.getNamespace()).thenReturn(namespace);
// all-namespaces = false
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, "",
Set.of(), labels, "", null, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, "", Set.of(), labels, "", null, 0);
KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider);
watch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER);
@@ -257,8 +257,8 @@ class Fabric8KubernetesCatalogWatchTests {
private KubernetesCatalogWatch createWatcherInAllNamespacesAndLabels(Map<String, String> labels) {
// all-namespaces = true
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false, "",
Set.of(), labels, "", null, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
false, "", Set.of(), labels, "", null, 0);
KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider);
watch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER);

View File

@@ -365,8 +365,8 @@ class KubernetesCatalogWatchTest {
when(MIXED_OPERATION.withLabels(Map.of())).thenReturn(MIXED_OPERATION);
// all-namespaces = true
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false, "",
Set.of(), Map.of(), "", null, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
false, "", Set.of(), Map.of(), "", null, 0);
kubernetesCatalogWatch = new KubernetesCatalogWatch(CLIENT, properties, namespaceProvider);
kubernetesCatalogWatch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER);
@@ -375,8 +375,8 @@ class KubernetesCatalogWatchTest {
private void createInSpecificNamespaceWatcher() {
// all-namespaces = false
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, "",
Set.of(), Map.of(), "", null, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, "", Set.of(), Map.of(), "", null, 0);
kubernetesCatalogWatch = new KubernetesCatalogWatch(CLIENT, properties, namespaceProvider);
kubernetesCatalogWatch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER);

View File

@@ -79,8 +79,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(false, null, false, null, false, null);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -109,8 +109,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(true, null, false, null, false, null);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -140,8 +140,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(true, "l_", false, null, false, null);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -171,8 +171,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(false, null, true, null, false, null);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -202,8 +202,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(false, null, true, "a_", false, null);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -233,8 +233,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(false, null, false, null, true, null);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -264,8 +264,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(false, null, false, null, true, "p_");
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);
@@ -295,8 +295,8 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
final String serviceId = "s";
Metadata metadata = new Metadata(true, "l_", true, "a_", true, "p_");
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, null,
Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, null, Set.of(), Map.of(), null, metadata, 0);
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null);

View File

@@ -66,8 +66,8 @@ public class KubernetesDiscoveryClientFilterTest {
when(this.serviceOperation.list()).thenReturn(serviceList);
when(this.kubernetesClient.services()).thenReturn(this.serviceOperation);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false,
"metadata.additionalProperties['spring-boot']", Set.of(), Map.of(), null,
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, "metadata.additionalProperties['spring-boot']", Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryClient client = new KubernetesDiscoveryClient(this.kubernetesClient, properties,
this.kubernetesClientServicesFunction);
@@ -94,8 +94,8 @@ public class KubernetesDiscoveryClientFilterTest {
when(this.serviceOperation.list()).thenReturn(serviceList);
when(this.kubernetesClient.services()).thenReturn(this.serviceOperation);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false,
"metadata.name.startsWith('service')", Set.of(), Map.of(), null,
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, "metadata.name.startsWith('service')", Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryClient client = new KubernetesDiscoveryClient(this.kubernetesClient, properties,
this.kubernetesClientServicesFunction);
@@ -115,8 +115,8 @@ public class KubernetesDiscoveryClientFilterTest {
when(this.serviceOperation.list()).thenReturn(serviceList);
when(this.kubernetesClient.services()).thenReturn(this.serviceOperation);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false, "",
Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
false, "", Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryClient client = new KubernetesDiscoveryClient(this.kubernetesClient, properties,
this.kubernetesClientServicesFunction);

View File

@@ -21,9 +21,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
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.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.client.Config;
@@ -109,8 +111,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false,
null, Set.of(), labels, "http_tcp", Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true,
60, false, null, Set.of(), labels, "http_tcp", Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -143,6 +145,64 @@ public class KubernetesDiscoveryClientTest {
assertThat(result_endpoints).hasSize(1);
}
@Test
public void getEndPointsListTestAllNamespaces() {
final var namespace1 = "ns1";
final var namespace2 = "ns2";
Endpoints endPoint1 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
.endMetadata().build();
Endpoints endPoint2 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
.endMetadata().build();
mockClient.endpoints().inNamespace(namespace1).create(endPoint1);
mockClient.endpoints().inNamespace(namespace2).create(endPoint2);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true,
60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
final KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
final List<Endpoints> result_endpoints = discoveryClient.getEndPointsList("endpoint");
assertThat(result_endpoints).hasSize(2);
}
@Test
public void getEndPointsListShouldHandleNamespaces() {
final var namespace1 = "ns1";
final var namespace2 = "ns2";
final var namespace3 = "ns3";
Endpoints endPoint1 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
.endMetadata().build();
Endpoints endPoint2 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
.endMetadata().build();
Endpoints endPoint3 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace3)
.endMetadata().build();
mockClient.endpoints().inNamespace(namespace1).create(endPoint1);
mockClient.endpoints().inNamespace(namespace2).create(endPoint2);
mockClient.endpoints().inNamespace(namespace3).create(endPoint3);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false,
Set.of(namespace1, namespace3), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
final KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
final List<Endpoints> result_endpoints = discoveryClient.getEndPointsList("endpoint");
assertThat(result_endpoints).hasSize(2);
assertThat(result_endpoints.stream().map(Endpoints::getMetadata).map(ObjectMeta::getNamespace)
.collect(Collectors.toList())).containsOnly(namespace1, namespace3);
}
@Test
public void getInstancesShouldBeAbleToHandleEndpointsMultipleAddresses() {
Map<String, String> labels = new HashMap<>();
@@ -162,8 +222,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
Metadata metadata = new Metadata(false, null, false, null, true, "port.");
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false,
null, Set.of(443, 8443), labels, null, metadata, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true,
60, false, null, Set.of(443, 8443), labels, null, metadata, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -228,6 +288,38 @@ public class KubernetesDiscoveryClientTest {
assertThat(services).containsOnly("s1", "s2");
}
@Test
public void getServicesShouldReturnServicesInNamespaces() {
final var nameSpace1 = "ns1";
final var nameSpace2 = "ns2";
final var nameSpace3 = "ns3";
Service service1 = new ServiceBuilder().withNewMetadata().withName("s1").withNamespace(nameSpace1).endMetadata()
.build();
Service service2 = new ServiceBuilder().withNewMetadata().withName("s2").withNamespace(nameSpace2).endMetadata()
.build();
Service service3 = new ServiceBuilder().withNewMetadata().withName("s3").withNamespace(nameSpace3).endMetadata()
.build();
mockClient.services().inNamespace(nameSpace1).create(service1);
mockClient.services().inNamespace(nameSpace2).create(service2);
mockClient.services().inNamespace(nameSpace3).create(service3);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false,
Set.of(nameSpace1, nameSpace2), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
final List<String> services = discoveryClient.getServices();
assertThat(services).containsOnly("s1", "s2");
}
@Test
public void getInstancesShouldBeAbleToHandleEndpointsFromMultipleNamespaces() {
Endpoints endPoints1 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
@@ -250,8 +342,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service1);
mockClient.services().inNamespace("test2").create(service2);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false,
null, Set.of(), Map.of(), null, Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true,
60, false, null, Set.of(), Map.of(), null, Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -302,8 +394,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false,
null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true,
60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -333,8 +425,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false,
null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true,
60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -363,8 +455,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false,
null, Set.of(443, 8443), Map.of(), "oops", Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true,
60, false, null, Set.of(443, 8443), Map.of(), "oops", Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -392,8 +484,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, true, 60, false,
null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true,
60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));
@@ -449,8 +541,8 @@ public class KubernetesDiscoveryClientTest {
mockClient.services().inNamespace("test").create(service);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, true,
null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true,
60, true, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0);
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
KubernetesClient::services, new ServicePortSecureResolver(properties));

View File

@@ -50,8 +50,9 @@ class ServicePortSecureResolverTest {
@Test
void testPortNumbersOnly() {
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false, null,
Set.of(443, 8443, 12345), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
false, null, Set.of(443, 8443, 12345), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT,
0);
ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);

View File

@@ -301,8 +301,8 @@ class KubernetesReactiveDiscoveryClientTests {
}).endMetadata().build())
.once();
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, true, 60, false, null,
Set.of(), Map.of(), "https_tcp", Metadata.DEFAULT, 0);
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
false, null, Set.of(), Map.of(), "https_tcp", Metadata.DEFAULT, 0);
ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(kubernetesClient, properties,
KubernetesClient::services);
Flux<ServiceInstance> instances = client.getInstances("existing-service");

View File

@@ -78,8 +78,8 @@ class Fabric8ServiceInstanceMapperTests {
@Test
void testMapperSecureNullLabelsAndAnnotations() {
KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties();
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, true, true, 60,
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, true, Set.of(),
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
List<ServicePort> ports = new ArrayList<>();
ports.add(new ServicePortBuilder().withPort(443).build());
Service service = buildService("test", "abc", ports, null, null);

View File

@@ -95,8 +95,8 @@ class KubernetesServiceListSupplierTests {
ServiceList serviceList = new ServiceList();
serviceList.getItems().add(buildService("test-service", 8080));
when(this.multiDeletable.list()).thenReturn(serviceList);
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, true, true, 60,
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, true, Set.of(),
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
KubernetesServicesListSupplier supplier = new Fabric8ServicesListSupplier(environment, client, mapper,
discoveryProperties);
List<ServiceInstance> instances = supplier.get().blockFirst();

View File

@@ -0,0 +1,350 @@
/*
* Copyright 2013-2021 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.discoveryclient.it;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api;
import io.kubernetes.client.openapi.models.V1ClusterRoleBinding;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1EnvVar;
import io.kubernetes.client.openapi.models.V1EnvVarBuilder;
import io.kubernetes.client.openapi.models.V1Ingress;
import io.kubernetes.client.openapi.models.V1Namespace;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Service;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.discovery.KubernetesServiceInstance;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
/**
* @author mbialkowski1
*/
class DiscoveryClientFilterNamespaceIT {
private static final Log LOG = LogFactory.getLog(DiscoveryClientFilterNamespaceIT.class);
private static final String DISCOVERY_SERVER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryserver-deployment";
private static final String DISCOVERY_SERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver";
private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryclient-it-deployment";
private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it";
private static final String MOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment";
private static final String MOCK_CLIENT_APP_NAME = "servicea-wiremock";
private static final String MOCK_IMAGE_NAME = "wiremock";
private static final String NAMESPACE = "default";
private static final String NAMESPACE_1 = "namespace1";
private static final String NAMESPACE_2 = "namespace2";
private static CoreV1Api api;
private static AppsV1Api appsApi;
private static NetworkingV1Api networkingApi;
private static RbacAuthorizationV1Api authApi;
private static K8SUtils k8SUtils;
private static final K3sContainer K3S = Commons.container();
@SuppressWarnings("checkstyle:WhitespaceAround")
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(DISCOVERY_SERVER_APP_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(DISCOVERY_SERVER_APP_NAME, K3S);
Commons.validateImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S);
String[] mockImage = K8SUtils.getImageFromDeployment(getMockServiceDeployment()).split(":");
Commons.pullImage(mockImage[0], mockImage[1], K3S);
Commons.loadImage(mockImage[0], mockImage[1], MOCK_IMAGE_NAME, K3S);
createApiClient(K3S.getKubeConfigYaml());
api = new CoreV1Api();
appsApi = new AppsV1Api();
networkingApi = new NetworkingV1Api();
authApi = new RbacAuthorizationV1Api();
k8SUtils = new K8SUtils(api, appsApi);
k8SUtils.setUp(NAMESPACE);
deployDiscoveryServer();
// Check to make sure the discovery server deployment is ready
k8SUtils.waitForDeployment(DISCOVERY_SERVER_DEPLOYMENT_NAME, NAMESPACE);
// Check to see if endpoint is ready
k8SUtils.waitForEndpointReady(DISCOVERY_SERVER_APP_NAME, NAMESPACE);
}
@AfterAll
static void afterAll() throws Exception {
Commons.cleanUp(DISCOVERY_SERVER_APP_NAME, K3S);
Commons.cleanUp(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S);
Commons.cleanUpDownloadedImage(MOCK_IMAGE_NAME);
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
"metadata.name=" + DISCOVERY_SERVER_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null,
null);
api.deleteNamespacedService(DISCOVERY_SERVER_APP_NAME, NAMESPACE, null, null, null, null, null, null);
networkingApi.deleteNamespacedIngress("discoveryserver-ingress", NAMESPACE, null, null, null, null, null, null);
}
@AfterEach
void afterEach() throws ApiException {
cleanup();
}
@Test
void testDiscoveryClient() throws Exception {
deploySampleAppInNamespace(NAMESPACE_1);
deploySampleAppInNamespace(NAMESPACE_2);
deployDiscoveryIt();
testLoadBalancer();
testHealth();
}
private void cleanup() throws ApiException {
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
"metadata.name=" + SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME, null, null, null, null, null,
null, null, null, null);
api.deleteNamespacedService(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, NAMESPACE, null, null, null, null, null,
null);
networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null);
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE_1, null, null, null,
"metadata.name=" + MOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null);
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE_2, null, null, null,
"metadata.name=" + MOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null);
api.deleteNamespacedService(MOCK_CLIENT_APP_NAME, NAMESPACE_1, null, null, null, null, null, null);
api.deleteNamespacedService(MOCK_CLIENT_APP_NAME, NAMESPACE_2, null, null, null, null, null, null);
networkingApi.deleteNamespacedIngress("servicea-wiremock-ingress", NAMESPACE_1, null, null, null, null, null,
null);
networkingApi.deleteNamespacedIngress("servicea-wiremock-ingress", NAMESPACE_2, null, null, null, null, null,
null);
}
private void testLoadBalancer() {
// Check to make sure the controller deployment is ready
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME, NAMESPACE);
WebClient.Builder builder = builder();
WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/services").build();
String[] result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String[].class)
.retryWhen(retrySpec()).block();
LOG.info("Services: " + Arrays.toString(result));
assertThat(result).containsAnyOf("servicea-wiremock");
// ServiceInstance
WebClient serviceInstanceClient = builder
.baseUrl("http://localhost:80/discoveryclient-it/service/servicea-wiremock").build();
List<KubernetesServiceInstance> serviceInstances = serviceInstanceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<KubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
assertThat(serviceInstances.size()).isEqualTo(1);
assertThat(serviceInstances.get(0).getNamespace()).isEqualTo(NAMESPACE_1);
}
@SuppressWarnings("unchecked")
void testHealth() {
WebClient.Builder builder = builder();
WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build();
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class);
@SuppressWarnings("unchecked")
Map<String, Object> health = (Map<String, Object>) serviceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec())
.block();
Map<String, Object> components = (Map<String, Object>) health.get("components");
Map<String, Object> discoveryComposite = (Map<String, Object>) components.get("discoveryComposite");
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
}
private void deployDiscoveryIt() throws Exception {
appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryItDeployment(), null, null, null);
api.createNamespacedService(NAMESPACE, getDiscoveryService(), null, null, null);
V1Ingress ingress = getDiscoveryItIngress();
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
}
private static V1Deployment getDiscoveryItDeployment() throws Exception {
V1Deployment deployment = (V1Deployment) k8SUtils
.readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml");
// add namespaces filter property for namespace1
var env = new V1EnvVarBuilder().withName("JAVA_OPTS")
.withValue("-Dspring.cloud.kubernetes.discovery.namespaces[0]=" + NAMESPACE_1).build();
var container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
container.setEnv(List.of(env));
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
+ getPomVersion();
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
return deployment;
}
private static void deployDiscoveryServer() throws Exception {
V1ClusterRoleBinding clusterRoleBinding = getClusterRoleBinding();
authApi.createClusterRoleBinding(clusterRoleBinding, null, null, null);
appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryServerDeployment(), null, null, null);
api.createNamespacedService(NAMESPACE, getDiscoveryServerService(), null, null, null);
V1Ingress ingress = getDiscoveryServerIngress();
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
}
private static void deploySampleAppInNamespace(final String namespace) throws Exception {
V1Namespace namespace1 = new V1Namespace();
V1ObjectMeta meta = new V1ObjectMeta();
meta.setName(namespace);
namespace1.setMetadata(meta);
api.createNamespace(namespace1, null, null, null);
V1Deployment deployment = getMockServiceDeployment();
deployment.getMetadata().setNamespace(namespace);
appsApi.createNamespacedDeployment(namespace, deployment, null, null, null);
V1Service service = getMockServiceService();
service.getMetadata().setNamespace(namespace);
api.createNamespacedService(namespace, service, null, null, null);
V1Ingress ingress = getMockIngress();
ingress.getMetadata().setNamespace(namespace);
ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0).setPath("/wiremock-" + namespace);
networkingApi.createNamespacedIngress(namespace, ingress, null, null, null);
k8SUtils.waitForIngress(ingress.getMetadata().getName(), namespace);
}
private static V1Deployment getDiscoveryServerDeployment() throws Exception {
V1Deployment deployment = (V1Deployment) k8SUtils
.readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-deployment.yaml");
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
+ getPomVersion();
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
// setup all-namespaces property
V1EnvVar env = new V1EnvVarBuilder().withName("JAVA_OPTS")
.withValue("-Dspring.cloud.kubernetes.discovery.all-namespaces=true").build();
V1Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
container.setEnv(List.of(env));
return deployment;
}
private static V1Ingress getDiscoveryServerIngress() throws Exception {
return (V1Ingress) K8SUtils
.readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-ingress.yaml");
}
private static V1Service getDiscoveryServerService() throws Exception {
return (V1Service) K8SUtils
.readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-service.yaml");
}
private static V1Ingress getDiscoveryItIngress() throws Exception {
return (V1Ingress) K8SUtils
.readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml");
}
private static V1Service getDiscoveryService() throws Exception {
return (V1Service) K8SUtils
.readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-service.yaml");
}
private static V1ClusterRoleBinding getClusterRoleBinding() throws Exception {
return (V1ClusterRoleBinding) K8SUtils
.readYamlFromClasspath("namespace-filter/cluster-admin-serviceaccount-role.yaml");
}
private static V1Deployment getMockServiceDeployment() throws Exception {
return (V1Deployment) k8SUtils.readYamlFromClasspath("wiremock/discovery-wiremock-deployment.yaml");
}
private static V1Service getMockServiceService() throws Exception {
return (V1Service) K8SUtils.readYamlFromClasspath("wiremock/discovery-wiremock-service.yaml");
}
private static V1Ingress getMockIngress() throws Exception {
return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/discovery-wiremock-ingress.yaml");
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
}
}

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: null
name: admin-default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-serviceaccount
namespace: default

View File

@@ -0,0 +1,27 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: servicea-wiremock-deployment
spec:
selector:
matchLabels:
app: servicea-wiremock
template:
metadata:
labels:
app: servicea-wiremock
spec:
containers:
- name: servicea-wiremock
image: wiremock/wiremock:2.32.0
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 8080
path: /__admin/mappings
livenessProbe:
httpGet:
port: 8080
path: /__admin/mappings
ports:
- containerPort: 8080

View File

@@ -0,0 +1,15 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: servicea-wiremock-ingress
spec:
rules:
- http:
paths:
- path: /wiremock
pathType: Prefix
backend:
service:
name: servicea-wiremock
port:
number: 8080

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: servicea-wiremock
name: servicea-wiremock
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: servicea-wiremock
type: ClusterIP

View File

@@ -18,8 +18,11 @@ package org.springframework.cloud.kubernetes.fabric8.configmap;
import java.util.List;
import io.fabric8.kubernetes.api.model.Endpoints;
import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -39,4 +42,9 @@ public class Fabric8DiscoveryController {
return discoveryClient.getServices();
}
@GetMapping("/endpoints/{serviceId}")
public List<Endpoints> getEndPointsList(@PathVariable("serviceId") String serviceId) {
return discoveryClient.getEndPointsList(serviceId);
}
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.fabric8.configmap;
import java.io.InputStream;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils;
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
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;
/**
* @author mbialkowski1
*/
class Fabric8DiscoveryNamespaceFilterIT {
private static final String NAMESPACE = "default";
private static final String NAMESPACE_1 = "namespace1";
private static final String NAMESPACE_2 = "namespace2";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static KubernetesClient client;
private static String deploymentName;
private static String serviceName;
private static String ingressName;
private static String mockServiceName;
private static String mockDeploymentName;
private static String mockDeploymentImage;
private static final K3sContainer K3S = Commons.container();
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(IMAGE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml());
client = new DefaultKubernetesClient(config);
Fabric8Utils.setUp(client, NAMESPACE);
deployManifests();
deployMockManifests();
}
@AfterAll
static void after() throws Exception {
deleteManifests();
Commons.cleanUp(IMAGE_NAME, K3S);
Commons.cleanUpDownloadedImage(mockDeploymentImage);
}
@Test
void test() {
WebClient clientServices = builder().baseUrl("localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("servicea-wiremock"));
WebClient clientEndpoints = builder().baseUrl("localhost/endpoints/servicea-wiremock").build();
List<Endpoints> endpoints = clientEndpoints.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Endpoints>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(endpoints.size(), 1);
Assertions.assertEquals(endpoints.get(0).getMetadata().getNamespace(), NAMESPACE_1);
}
private static void deleteManifests() {
try {
client.apps().deployments().inNamespace(NAMESPACE).withName(deploymentName).delete();
client.services().inNamespace(NAMESPACE).withName(serviceName).delete();
client.network().v1().ingresses().inNamespace(NAMESPACE).withName(ingressName).delete();
client.services().inNamespace(NAMESPACE_1).withName(mockServiceName).delete();
client.apps().deployments().inNamespace(NAMESPACE_1).withName(mockDeploymentName).delete();
client.services().inNamespace(NAMESPACE_2).withName(mockServiceName).delete();
client.apps().deployments().inNamespace(NAMESPACE_2).withName(mockDeploymentName).delete();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void deployManifests() {
try {
Deployment deployment = client.apps().deployments().load(getDeployment()).get();
String version = K8SUtils.getPomVersion();
String currentImage = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(currentImage + ":" + version);
List<EnvVar> env = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv();
env.add(new EnvVar("JAVA_OPTS", "-Dspring.cloud.kubernetes.discovery.namespaces[0]=" + NAMESPACE_1, null));
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(env);
client.apps().deployments().inNamespace(NAMESPACE).create(deployment);
deploymentName = deployment.getMetadata().getName();
Service service = client.services().load(getService()).get();
serviceName = service.getMetadata().getName();
client.services().inNamespace(NAMESPACE).create(service);
Ingress ingress = client.network().v1().ingresses().load(getIngress()).get();
ingressName = ingress.getMetadata().getName();
client.network().v1().ingresses().inNamespace(NAMESPACE).create(ingress);
client.rbac().clusterRoleBindings().create(client.rbac().clusterRoleBindings().load(getAdminRole()).get());
Fabric8Utils.waitForDeployment(client, "spring-cloud-kubernetes-fabric8-client-discovery-deployment",
NAMESPACE, 2, 600);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void deployMockManifests() {
try {
deployInMockInNamespace(NAMESPACE_1);
deployInMockInNamespace(NAMESPACE_2);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void deployInMockInNamespace(String namespace) throws Exception {
Namespace namespaceDef = new Namespace();
ObjectMeta meta = new ObjectMeta();
meta.setName(namespace);
meta.setNamespace(namespace);
namespaceDef.setMetadata(meta);
client.namespaces().create(namespaceDef);
Deployment deployment = client.apps().deployments().load(getMockDeployment()).get();
String[] image = K8SUtils.getImageFromDeployment(deployment).split(":");
Commons.pullImage(image[0], image[1], K3S);
Commons.loadImage(image[0], image[1], "wiremock", K3S);
client.apps().deployments().inNamespace(namespace).create(deployment);
mockDeploymentName = deployment.getMetadata().getName();
mockDeploymentImage = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
Service service = client.services().load(getMockService()).get();
mockServiceName = service.getMetadata().getName();
client.services().inNamespace(namespace).create(service);
Fabric8Utils.waitForDeployment(client, "servicea-wiremock-deployment", namespace, 2, 600);
}
private static InputStream getService() {
return Fabric8Utils.inputStream("fabric8-discovery-service.yaml");
}
private static InputStream getDeployment() {
return Fabric8Utils.inputStream("fabric8-discovery-deployment.yaml");
}
private static InputStream getAdminRole() {
return Fabric8Utils.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml");
}
private static InputStream getIngress() {
return Fabric8Utils.inputStream("fabric8-discovery-ingress.yaml");
}
private static InputStream getMockService() {
return Fabric8Utils.inputStream("wiremock/fabric8-discovery-wiremock-service.yaml");
}
private static InputStream getMockDeployment() {
return Fabric8Utils.inputStream("wiremock/fabric8-discovery-wiremock-deployment.yaml");
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull);
}
}

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: null
name: admin-default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-serviceaccount
namespace: default