From 8a0dfe98c5fcacb1fb0962959dc6a8a610af3936 Mon Sep 17 00:00:00 2001 From: mbialkowski1 <90385522+mbialkowski1@users.noreply.github.com> Date: Thu, 1 Dec 2022 02:01:11 +0100 Subject: [PATCH] Namespace filter for service discovery (issue #1000). (#1113) --- docs/src/main/asciidoc/_configprops.adoc | 3 +- docs/src/main/asciidoc/discovery-client.adoc | 10 + .../KubernetesInformerDiscoveryClient.java | 16 +- ...ubernetesInformerDiscoveryClientTests.java | 50 ++- ...sInformerReactiveDiscoveryClientTests.java | 4 +- ...rnetesClientServicesListSupplierTests.java | 3 +- .../KubernetesDiscoveryProperties.java | 7 +- .../KubernetesDiscoveryPropertiesTests.java | 6 +- .../discovery/KubernetesDiscoveryClient.java | 20 +- .../KubernetesDiscoveryClientProperties.java | 17 + .../KubernetesDiscoveryClientTests.java | 48 ++- .../discovery/KubernetesDiscoveryClient.java | 35 +- .../Fabric8KubernetesCatalogWatchTests.java | 8 +- .../discovery/KubernetesCatalogWatchTest.java | 8 +- ...etesDiscoveryClientFilterMetadataTest.java | 32 +- .../KubernetesDiscoveryClientFilterTest.java | 12 +- .../KubernetesDiscoveryClientTest.java | 124 ++++++- .../ServicePortSecureResolverTest.java | 5 +- ...ubernetesReactiveDiscoveryClientTests.java | 4 +- .../Fabric8ServiceInstanceMapperTests.java | 4 +- .../KubernetesServiceListSupplierTests.java | 4 +- .../it/DiscoveryClientFilterNamespaceIT.java | 350 ++++++++++++++++++ .../cluster-admin-serviceaccount-role.yaml | 13 + .../discovery-wiremock-deployment.yaml | 27 ++ .../wiremock/discovery-wiremock-ingress.yaml | 15 + .../wiremock/discovery-wiremock-service.yaml | 14 + .../configmap/Fabric8DiscoveryController.java | 8 + .../Fabric8DiscoveryNamespaceFilterIT.java | 249 +++++++++++++ ...ic8-cluster-admin-serviceaccount-role.yaml | 13 + 29 files changed, 1017 insertions(+), 92 deletions(-) create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceIT.java create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/namespace-filter/cluster-admin-serviceaccount-role.yaml create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-deployment.yaml create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-ingress.yaml create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-service.yaml create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryNamespaceFilterIT.java create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 2abeca7e..63559ea3 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -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+++` | diff --git a/docs/src/main/asciidoc/discovery-client.adoc b/docs/src/main/asciidoc/discovery-client.adoc index f5132639..e0f2a281 100644 --- a/docs/src/main/asciidoc/discovery-client.adoc +++ b/docs/src/main/asciidoc/discovery-client.adoc @@ -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): ==== diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java index cebb8084..dd34dddc 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java @@ -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 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 getServiceInstanceDetails(V1Service service, String serviceId) { Map 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 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 endpointPorts, String primaryPortName, String serviceId) { diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java index 47d5da97..4ebfd42f 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java @@ -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 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 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 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 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 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 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 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 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 serviceLister = setupServiceLister(testService1, testService2); + Lister 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 setupServiceLister(V1Service... services) { Cache serviceCache = new Cache<>(); Lister serviceLister = new Lister<>(serviceCache); diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java index 33766130..dc9c6fac 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java @@ -101,7 +101,7 @@ public class KubernetesInformerReactiveDiscoveryClientTests { Lister 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 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"); diff --git a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplierTests.java b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplierTests.java index fea9dc97..dcb37b4c 100644 --- a/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplierTests.java +++ b/spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplierTests.java @@ -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); diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryProperties.java index 075ee75b..adb4603c 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryProperties.java @@ -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 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 diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryPropertiesTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryPropertiesTests.java index 15f96729..043731df 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryPropertiesTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryPropertiesTests.java @@ -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(); diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java index 25f23e72..62d11665 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java @@ -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 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); + } + } diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientProperties.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientProperties.java index 43884142..2f944f76 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientProperties.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientProperties.java @@ -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 namespaces = new ArrayList<>(); + public String getDiscoveryServerUrl() { return discoveryServerUrl; } @@ -44,4 +53,12 @@ public class KubernetesDiscoveryClientProperties { this.enabled = enabled; } + List getNamespaces() { + return namespaces; + } + + void setNamespaces(List namespaces) { + this.namespaces = namespaces; + } + } diff --git a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientTests.java b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientTests.java index 60bd9416..16c17404 100644 --- a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientTests.java +++ b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientTests.java @@ -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 namespaces, List 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 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 namespaces, String serviceId, List 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 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"))); + } + } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java index 8e20d859..340a7820 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java @@ -121,11 +121,24 @@ public class KubernetesDiscoveryClient implements DiscoveryClient { } public List 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 findEndPointsFilteredByNamespaces(String serviceId) { + List 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 getNamespaceServiceInstances(EndpointSubsetNS es, String serviceId) { @@ -297,8 +310,16 @@ public class KubernetesDiscoveryClient implements DiscoveryClient { } public List getServices(Predicate 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 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 diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchTests.java index 8536c0d9..f07dfe63 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchTests.java @@ -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 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); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchTest.java index 63c97206..33e942de 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchTest.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchTest.java @@ -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); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterMetadataTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterMetadataTest.java index 773f28f5..67e67bea 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterMetadataTest.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterMetadataTest.java @@ -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); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterTest.java index 1f5b6d0c..4d83c3c9 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterTest.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientFilterTest.java @@ -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); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientTest.java index 08447fb3..3c52d30b 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientTest.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientTest.java @@ -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 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 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 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 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)); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java index 37c57095..28bb9bd2 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java @@ -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); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java index 4f39cb47..6ff73fb3 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java @@ -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 instances = client.getInstances("existing-service"); diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServiceInstanceMapperTests.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServiceInstanceMapperTests.java index f02550a0..d6dc5d9e 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServiceInstanceMapperTests.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/Fabric8ServiceInstanceMapperTests.java @@ -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 ports = new ArrayList<>(); ports.add(new ServicePortBuilder().withPort(443).build()); Service service = buildService("test", "abc", ports, null, null); diff --git a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/KubernetesServiceListSupplierTests.java b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/KubernetesServiceListSupplierTests.java index 75d2ace5..7852d1c6 100644 --- a/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/KubernetesServiceListSupplierTests.java +++ b/spring-cloud-kubernetes-fabric8-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/fabric8/loadbalancer/KubernetesServiceListSupplierTests.java @@ -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 instances = supplier.get().blockFirst(); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceIT.java new file mode 100644 index 00000000..c473ec84 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceIT.java @@ -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 serviceInstances = serviceInstanceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + }).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 health = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + + Map components = (Map) health.get("components"); + + Map discoveryComposite = (Map) 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); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/namespace-filter/cluster-admin-serviceaccount-role.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/namespace-filter/cluster-admin-serviceaccount-role.yaml new file mode 100644 index 00000000..d8eec949 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/namespace-filter/cluster-admin-serviceaccount-role.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-deployment.yaml new file mode 100644 index 00000000..9d28eb62 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-deployment.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-ingress.yaml new file mode 100644 index 00000000..5e191b81 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-ingress.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-service.yaml new file mode 100644 index 00000000..ef30d58a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/wiremock/discovery-wiremock-service.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryController.java index 4f9398f7..7cf7d06e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryController.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryController.java @@ -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 getEndPointsList(@PathVariable("serviceId") String serviceId) { + return discoveryClient.getEndPointsList(serviceId); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryNamespaceFilterIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryNamespaceFilterIT.java new file mode 100644 index 00000000..518ba66c --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryNamespaceFilterIT.java @@ -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 services = (List) 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 = clientEndpoints.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + }).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 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); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml new file mode 100644 index 00000000..d8eec949 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml @@ -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