diff --git a/docs/src/main/asciidoc/discovery-client.adoc b/docs/src/main/asciidoc/discovery-client.adoc index e0f2a281..f81533f4 100644 --- a/docs/src/main/asciidoc/discovery-client.adoc +++ b/docs/src/main/asciidoc/discovery-client.adoc @@ -134,8 +134,19 @@ should not rely on the details. Instead, they should see if there are difference The endpoints will be queried in either all namespaces (enabled via `spring.cloud.kubernetes.discovery.all-namespaces=true`), or we will use: xref:property-source-config.adoc#namespace-resolution[Namespace Resolution]. - - - In order to enable this functionality you need to add `@EnableScheduling` on a configuration class in your application. + +By default, we use the `Endpoints`(see https://kubernetes.io/docs/concepts/services-networking/service/#endpoints) API to find out the current state of services. There is another way though, via `EndpointSlices` (https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/). Such support can be enabled via a property: `spring.cloud.kubernetes.discovery.use-endpoint-slices=true` (by default it is `false`). Of course, your cluster has to support it also. As a matter of fact, if you enable this property, but your cluster does not support it, we will fail starting the application. If you decide to enable such support, you also need proper Role/ClusterRole set-up. For example: + +``` +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: namespace-reader +rules: + - apiGroups: ["discovery.k8s.io"] + resources: ["endpointslices"] + verbs: ["get", "list", "watch"] +``` 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 b3e251e9..fd0f94b5 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 @@ -124,7 +124,7 @@ public class KubernetesInformerDiscoveryClientTests { KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, false, null, Set.of(), Map.of(), null, - KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -157,7 +157,7 @@ public class KubernetesInformerDiscoveryClientTests { labels.put("spring", "true"); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("", sharedInformerFactory, serviceLister, null, null, null, kubernetesDiscoveryProperties); @@ -176,7 +176,7 @@ public class KubernetesInformerDiscoveryClientTests { labels.put("spring", "true"); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -192,7 +192,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister serviceLister = setupServiceLister(testServiceSecuredAnnotation1); Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0); + Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0, false); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); assertThat(discoveryClient.getServices().toArray()) @@ -207,7 +207,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister serviceLister = setupServiceLister(testServiceSecuredLabel1); Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0); + Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0, false); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -235,7 +235,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -250,7 +250,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -277,7 +277,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithoutReadyAddresses); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, true, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, true, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -318,7 +318,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -337,7 +337,7 @@ public class KubernetesInformerDiscoveryClientTests { testEndpointWithMultiplePortsWithoutSupportedPortNames); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -353,7 +353,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0); + Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -369,7 +369,7 @@ public class KubernetesInformerDiscoveryClientTests { testEndpointWithMultiplePortsWithoutSupportedPortNames); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0); + Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -384,7 +384,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -399,7 +399,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePortsWithoutHttps); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -415,7 +415,7 @@ public class KubernetesInformerDiscoveryClientTests { testEndpointWithMultiplePortsWithoutSupportedPortNames); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -430,7 +430,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1, testEndpoints2); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(null, sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); 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 dc9c6fac..704f82df 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, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false); 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, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0); + Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false); 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 dcb37b4c..b27e1386 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 @@ -147,7 +147,7 @@ class KubernetesClientServicesListSupplierTests { when(kubernetesNamespaceProvider.getNamespace()).thenReturn("default"); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, false, null, Set.of(), Map.of(), null, - KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false); 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 adb4603c..6001e79d 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 @@ -42,6 +42,7 @@ import static org.springframework.cloud.client.discovery.DiscoveryClient.DEFAULT * fetched from the Kubernetes API server. * @param primaryPortName If set then the port with a given name is used as primary when * multiple ports are defined for a service. + * @param useEndpointSlices use EndpointSlice instead of Endpoints */ // @formatter:off @ConfigurationProperties("spring.cloud.kubernetes.discovery") @@ -54,14 +55,15 @@ public record KubernetesDiscoveryProperties( @DefaultValue({"443", "8443"}) Set knownSecurePorts, @DefaultValue Map serviceLabels, String primaryPortName, @DefaultValue Metadata metadata, - @DefaultValue("" + DEFAULT_ORDER) int order) { + @DefaultValue("" + DEFAULT_ORDER) int order, + boolean useEndpointSlices) { // @formatter:on /** * Default instance. */ public static final KubernetesDiscoveryProperties DEFAULT = new KubernetesDiscoveryProperties(true, false, Set.of(), - true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false); /** * @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 043731df..55c29f64 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 @@ -52,6 +52,7 @@ class KubernetesDiscoveryPropertiesTests { assertThat(props.serviceLabels()).isEmpty(); assertThat(props.primaryPortName()).isNull(); assertThat(props.order()).isZero(); + assertThat(props.useEndpointSlices()).isFalse(); }); } @@ -61,6 +62,7 @@ class KubernetesDiscoveryPropertiesTests { .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.use-endpoint-slices=true", "spring.cloud.kubernetes.discovery.namespaces[0]=ns1", "spring.cloud.kubernetes.discovery.namespaces[1]=ns2") .run(context -> { @@ -81,6 +83,7 @@ class KubernetesDiscoveryPropertiesTests { assertThat(props.serviceLabels()).isEmpty(); assertThat(props.primaryPortName()).isNull(); assertThat(props.order()).isZero(); + assertThat(props.useEndpointSlices()).isTrue(); }); } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8CatalogWatchContext.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8CatalogWatchContext.java new file mode 100644 index 00000000..9d3fc70f --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8CatalogWatchContext.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import io.fabric8.kubernetes.api.model.ObjectReference; +import io.fabric8.kubernetes.client.KubernetesClient; + +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; + +/** + * A simple holder for some instances needed for either Endpoints or EndpointSlice catalog + * implementations. + * + * @author wind57 + */ +record Fabric8CatalogWatchContext(KubernetesClient kubernetesClient, KubernetesDiscoveryProperties properties, + KubernetesNamespaceProvider namespaceProvider) { + + static List state(Stream references) { + return references.filter(Objects::nonNull).map(x -> new EndpointNameAndNamespace(x.getName(), x.getNamespace())) + .sorted(Comparator.comparing(EndpointNameAndNamespace::endpointName, String::compareTo)).toList(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointSliceV1CatalogWatch.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointSliceV1CatalogWatch.java new file mode 100644 index 00000000..bfc60934 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointSliceV1CatalogWatch.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import io.fabric8.kubernetes.api.model.ObjectReference; +import io.fabric8.kubernetes.api.model.discovery.v1.Endpoint; +import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSlice; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.cloud.kubernetes.fabric8.Fabric8Utils; +import org.springframework.core.log.LogAccessor; + +/** + * Implementation that is based on EndpointSlice V1. + * + * @author wind57 + */ +final class Fabric8EndpointSliceV1CatalogWatch + implements Function> { + + private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8EndpointSliceV1CatalogWatch.class)); + + @Override + public List apply(Fabric8CatalogWatchContext context) { + // take only pods that have endpoints + List endpointSlices; + if (context.properties().allNamespaces()) { + LOG.debug(() -> "discovering endpoints in all namespaces"); + + try (KubernetesClient client = context.kubernetesClient()) { + endpointSlices = client.discovery().v1().endpointSlices().inAnyNamespace() + .withLabels(context.properties().serviceLabels()).list().getItems(); + } + + } + else { + String namespace = Fabric8Utils.getApplicationNamespace(context.kubernetesClient(), null, "catalog-watcher", + context.namespaceProvider()); + LOG.debug(() -> "fabric8 catalog watcher will use namespace : " + namespace); + try (KubernetesClient client = context.kubernetesClient()) { + endpointSlices = client.discovery().v1().endpointSlices().inNamespace(namespace) + .withLabels(context.properties().serviceLabels()).list().getItems(); + } + } + + Stream references = endpointSlices.stream().map(EndpointSlice::getEndpoints) + .flatMap(List::stream).map(Endpoint::getTargetRef); + + return Fabric8CatalogWatchContext.state(references); + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointsCatalogWatch.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointsCatalogWatch.java new file mode 100644 index 00000000..1b313ac4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointsCatalogWatch.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import io.fabric8.kubernetes.api.model.EndpointAddress; +import io.fabric8.kubernetes.api.model.EndpointSubset; +import io.fabric8.kubernetes.api.model.Endpoints; +import io.fabric8.kubernetes.api.model.ObjectReference; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.cloud.kubernetes.fabric8.Fabric8Utils; +import org.springframework.core.log.LogAccessor; + +/** + * Implementation that is based on Endpoints. + * + * @author wind57 + */ +final class Fabric8EndpointsCatalogWatch + implements Function> { + + private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8EndpointsCatalogWatch.class)); + + @Override + public List apply(Fabric8CatalogWatchContext context) { + // take only pods that have endpoints + List endpoints; + if (context.properties().allNamespaces()) { + LOG.debug(() -> "discovering endpoints in all namespaces"); + + try (KubernetesClient client = context.kubernetesClient()) { + endpoints = client.endpoints().inAnyNamespace().withLabels(context.properties().serviceLabels()).list() + .getItems(); + } + + } + else { + String namespace = Fabric8Utils.getApplicationNamespace(context.kubernetesClient(), null, "catalog-watcher", + context.namespaceProvider()); + LOG.debug(() -> "fabric8 catalog watcher will use namespace : " + namespace); + try (KubernetesClient client = context.kubernetesClient()) { + endpoints = client.endpoints().inNamespace(namespace).withLabels(context.properties().serviceLabels()) + .list().getItems(); + } + } + + /** + *
+		 *   - An "Endpoints" holds a List of EndpointSubset.
+		 *   - A single EndpointSubset holds a List of EndpointAddress
+		 *
+		 *   - (The union of all EndpointSubsets is the Set of all Endpoints)
+		 *   - Set of Endpoints is the cartesian product of :
+		 *     EndpointSubset::getAddresses and EndpointSubset::getPorts (each is a List)
+		 * 
+ */ + Stream references = endpoints.stream().map(Endpoints::getSubsets).filter(Objects::nonNull) + .flatMap(List::stream).map(EndpointSubset::getAddresses).filter(Objects::nonNull).flatMap(List::stream) + .map(EndpointAddress::getTargetRef); + + return Fabric8CatalogWatchContext.state(references); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java index 90b8d556..c9994ba7 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java @@ -16,21 +16,20 @@ package org.springframework.cloud.kubernetes.fabric8.discovery; -import java.util.Comparator; import java.util.List; -import java.util.Objects; +import java.util.function.Function; -import io.fabric8.kubernetes.api.model.EndpointAddress; -import io.fabric8.kubernetes.api.model.EndpointSubset; -import io.fabric8.kubernetes.api.model.Endpoints; +import io.fabric8.kubernetes.api.model.APIResource; +import io.fabric8.kubernetes.api.model.APIResourceList; +import io.fabric8.kubernetes.api.model.GroupVersionForDiscovery; import io.fabric8.kubernetes.client.KubernetesClient; +import jakarta.annotation.PostConstruct; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; -import org.springframework.cloud.kubernetes.fabric8.Fabric8Utils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.log.LogAccessor; @@ -41,13 +40,15 @@ import org.springframework.scheduling.annotation.Scheduled; */ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware { + private static final String DISCOVERY_GROUP_VERSION = "discovery.k8s.io/v1"; + + private static final String ENDPOINT_SLICE = "EndpointSlice"; + private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(KubernetesCatalogWatch.class)); - private final KubernetesClient kubernetesClient; + private final Fabric8CatalogWatchContext context; - private final KubernetesDiscoveryProperties properties; - - private final KubernetesNamespaceProvider namespaceProvider; + private Function> stateGenerator; private volatile List catalogEndpointsState = null; @@ -55,9 +56,7 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware { public KubernetesCatalogWatch(KubernetesClient kubernetesClient, KubernetesDiscoveryProperties properties, KubernetesNamespaceProvider namespaceProvider) { - this.kubernetesClient = kubernetesClient; - this.properties = properties; - this.namespaceProvider = namespaceProvider; + context = new Fabric8CatalogWatchContext(kubernetesClient, properties, namespaceProvider); } @Override @@ -69,37 +68,7 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware { public void catalogServicesWatch() { try { - // not all pods participate in the service discovery. only those that have - // endpoints. - List endpoints; - if (properties.allNamespaces()) { - LOG.debug(() -> "discovering endpoints in all namespaces"); - endpoints = kubernetesClient.endpoints().inAnyNamespace().withLabels(properties.serviceLabels()).list() - .getItems(); - } - else { - String namespace = Fabric8Utils.getApplicationNamespace(kubernetesClient, null, "catalog-watcher", - namespaceProvider); - LOG.debug(() -> "fabric8 catalog watcher will use namespace : " + namespace); - endpoints = kubernetesClient.endpoints().inNamespace(namespace).withLabels(properties.serviceLabels()) - .list().getItems(); - } - - /** - *
-			 *   - An "Endpoints" holds a List of EndpointSubset.
-			 *   - A single EndpointSubset holds a List of EndpointAddress
-			 *
-			 *   - (The union of all EndpointSubsets is the Set of all Endpoints)
-			 *   - Set of Endpoints is the cartesian product of :
-			 *     EndpointSubset::getAddresses and EndpointSubset::getPorts (each is a List)
-			 * 
- */ - List currentState = endpoints.stream().map(Endpoints::getSubsets) - .filter(Objects::nonNull).flatMap(List::stream).map(EndpointSubset::getAddresses) - .filter(Objects::nonNull).flatMap(List::stream).map(EndpointAddress::getTargetRef) - .filter(Objects::nonNull).map(x -> new EndpointNameAndNamespace(x.getName(), x.getNamespace())) - .sorted(Comparator.comparing(EndpointNameAndNamespace::endpointName, String::compareTo)).toList(); + List currentState = stateGenerator.apply(context); if (!currentState.equals(catalogEndpointsState)) { LOG.debug(() -> "Received endpoints update from kubernetesClient: " + currentState); @@ -113,4 +82,31 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware { } } + @PostConstruct + void postConstruct() { + + if (context.properties().useEndpointSlices()) { + try (KubernetesClient client = context.kubernetesClient()) { + // this emulates : 'kubectl api-resources | grep -i EndpointSlice' + boolean found = client.getApiGroups().getGroups().stream().flatMap(x -> x.getVersions().stream()) + .map(GroupVersionForDiscovery::getGroupVersion).filter(DISCOVERY_GROUP_VERSION::equals) + .findFirst().map(client::getApiResources).map(APIResourceList::getResources) + .map(x -> x.stream().map(APIResource::getKind)) + .flatMap(x -> x.filter(y -> y.equals(ENDPOINT_SLICE)).findFirst()).isPresent(); + + if (!found) { + throw new IllegalArgumentException("EndpointSlices are not supported on the cluster"); + } + else { + stateGenerator = new Fabric8EndpointSliceV1CatalogWatch(); + } + } + } + else { + stateGenerator = new Fabric8EndpointsCatalogWatch(); + } + + LOG.debug(() -> "stateGenerator is of type: " + stateGenerator.getClass().getSimpleName()); + } + } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchEndpointSlicesTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchEndpointSlicesTests.java new file mode 100644 index 00000000..1042cb0b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchEndpointSlicesTests.java @@ -0,0 +1,377 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.fabric8.kubernetes.api.model.APIGroup; +import io.fabric8.kubernetes.api.model.APIGroupBuilder; +import io.fabric8.kubernetes.api.model.APIGroupList; +import io.fabric8.kubernetes.api.model.APIGroupListBuilder; +import io.fabric8.kubernetes.api.model.APIResource; +import io.fabric8.kubernetes.api.model.APIResourceBuilder; +import io.fabric8.kubernetes.api.model.APIResourceList; +import io.fabric8.kubernetes.api.model.APIResourceListBuilder; +import io.fabric8.kubernetes.api.model.GroupVersionForDiscovery; +import io.fabric8.kubernetes.api.model.GroupVersionForDiscoveryBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; +import io.fabric8.kubernetes.api.model.discovery.v1.Endpoint; +import io.fabric8.kubernetes.api.model.discovery.v1.EndpointBuilder; +import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSlice; +import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSliceBuilder; +import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSliceList; +import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSliceListBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import org.springframework.cloud.client.discovery.event.HeartbeatEvent; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.context.ApplicationEventPublisher; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Some tests that use the fabric8 mock client, using EndpointSlices + * + * @author wind57 + */ +@EnableKubernetesMockClient +class Fabric8KubernetesCatalogWatchEndpointSlicesTests { + + private final KubernetesNamespaceProvider namespaceProvider = Mockito.mock(KubernetesNamespaceProvider.class); + + private static final ArgumentCaptor HEARTBEAT_EVENT_ARGUMENT_CAPTOR = ArgumentCaptor + .forClass(HeartbeatEvent.class); + + private static final ApplicationEventPublisher APPLICATION_EVENT_PUBLISHER = Mockito + .mock(ApplicationEventPublisher.class); + + private static KubernetesClient mockClient; + + private static KubernetesMockServer mockServer; + + @BeforeAll + static void setUp() { + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + } + + @AfterEach + void beforeEach() { + mockServer.clearExpectations(); + } + + @AfterEach + void afterEach() { + Mockito.reset(APPLICATION_EVENT_PUBLISHER); + } + + /** + *
+	 *     - we have 2 pods involved in this test
+	 *     - podB in namespaceA with labels {color=blue}
+	 *     - podD in namespaceB with labels {color=blue}
+	 *
+	 *     We set the namespace to be "namespaceA" and search for labels {color=blue}
+	 *     As a result only one pod is taken: podB
+	 * 
+ */ + @Test + void testEndpointSlicesInSpecificNamespaceWithServiceLabels() { + + KubernetesCatalogWatch watch = createWatcherInSpecificNamespaceAndLabels("namespaceA", Map.of("color", "blue")); + + EndpointSlice sliceB = createSingleEndpointWithEndpointSlices("namespaceA", Map.of("color", "blue"), "podB"); + EndpointSliceList listInNamespaceA = new EndpointSliceListBuilder().withItems(sliceB).build(); + + mockServer.expect() + .withPath("/apis/discovery.k8s.io/v1/namespaces/namespaceA/endpointslices?labelSelector=color%3Dblue") + .andReturn(200, listInNamespaceA).once(); + + // this is mocked, but never supposed to be called + EndpointSlice sliceD = createSingleEndpointWithEndpointSlices("namespaceB", Map.of("color", "blue"), "podD"); + EndpointSliceList listInNamespaceB = new EndpointSliceListBuilder().withItems(sliceD).build(); + mockServer.expect() + .withPath("/apis/discovery.k8s.io/v1/namespaces/namespaceB/endpointslices?labelSelector=color%3Dblue") + .andReturn(200, listInNamespaceB); + + watch.catalogServicesWatch(); + + verify(APPLICATION_EVENT_PUBLISHER).publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture()); + + HeartbeatEvent event = HEARTBEAT_EVENT_ARGUMENT_CAPTOR.getValue(); + assertThat(event.getValue()).isInstanceOf(List.class); + + List expectedOutput = List.of(new EndpointNameAndNamespace("podB", "namespaceA")); + assertThat(event.getValue()).isEqualTo(expectedOutput); + } + + /** + *
+	 *
+	 *     - we have 5 pods involved in this test
+	 *     - podA in namespaceA with no labels
+	 *     - podB in namespaceA with labels {color=blue}
+	 *     - podC in namespaceA with labels {color=red}
+	 *     - podD in namespaceB with labels {color=blue}
+	 *     - podE in namespaceB with no labels
+	 *
+	 *     We set the namespace to be "namespaceA" and search without labels
+	 *     As a result we get three pods:
+	 *       - podA in namespaceA
+	 *       - podB in namespaceA
+	 *       - pocC in namespaceA
+	 *
+	 * 
+ */ + @Test + void testEndpointsInSpecificNamespaceWithoutServiceLabels() { + + KubernetesCatalogWatch watch = createWatcherInSpecificNamespaceAndLabels("namespaceA", Map.of()); + + EndpointSlice sliceA = createSingleEndpointWithEndpointSlices("namespaceA", Map.of(), "podA"); + EndpointSlice sliceB = createSingleEndpointWithEndpointSlices("namespaceA", Map.of("color", "blue"), "podB"); + EndpointSlice sliceC = createSingleEndpointWithEndpointSlices("namespaceA", Map.of("color", "red"), "podC"); + EndpointSliceList listInNamespaceA = new EndpointSliceListBuilder().withItems(sliceA, sliceB, sliceC).build(); + mockServer.expect().withPath("/apis/discovery.k8s.io/v1/namespaces/namespaceA/endpointslices") + .andReturn(200, listInNamespaceA).once(); + + // this is mocked, but never supposed to be called + EndpointSlice sliceD = createSingleEndpointWithEndpointSlices("namespaceB", Map.of("color", "blue"), "podD"); + EndpointSlice sliceE = createSingleEndpointWithEndpointSlices("namespaceB", Map.of(), "podE"); + EndpointSliceList listInNamespaceB = new EndpointSliceListBuilder().withItems(sliceD, sliceE).build(); + mockServer.expect().withPath("/apis/discovery.k8s.io/v1/namespaces/namespaceB/endpointslices") + .andReturn(200, listInNamespaceB).once(); + + watch.catalogServicesWatch(); + + verify(APPLICATION_EVENT_PUBLISHER).publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture()); + + HeartbeatEvent event = HEARTBEAT_EVENT_ARGUMENT_CAPTOR.getValue(); + assertThat(event.getValue()).isInstanceOf(List.class); + + List expectedOutput = List.of(new EndpointNameAndNamespace("podA", "namespaceA"), + new EndpointNameAndNamespace("podB", "namespaceA"), new EndpointNameAndNamespace("podC", "namespaceA")); + assertThat(event.getValue()).isEqualTo(expectedOutput); + } + + /** + *
+	 *
+	 *     - we have 2 pods involved in this test
+	 *     - podB in namespaceA with labels {color=blue}
+	 *     - podD in namespaceB with labels {color=blue}
+	 *
+	 *     We search in all namespaces with labels {color=blue}
+	 *     As a result two pods are taken:
+	 *       - podB in namespaceA
+	 *       - podD in namespaceB
+	 *
+	 * 
+ */ + @Test + void testEndpointsInAllNamespacesWithServiceLabels() { + + KubernetesCatalogWatch watch = createWatcherInAllNamespacesAndLabels(Map.of("color", "blue")); + + EndpointSlice sliceB = createSingleEndpointWithEndpointSlices("namespaceA", Map.of("color", "blue"), "podB"); + EndpointSlice sliceD = createSingleEndpointWithEndpointSlices("namespaceB", Map.of("color", "blue"), "podD"); + EndpointSliceList listInAllNamespaces = new EndpointSliceListBuilder().withItems(sliceB, sliceD).build(); + mockServer.expect().withPath("/apis/discovery.k8s.io/v1/endpointslices?labelSelector=color%3Dblue") + .andReturn(200, listInAllNamespaces).once(); + + watch.catalogServicesWatch(); + + verify(APPLICATION_EVENT_PUBLISHER).publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture()); + + HeartbeatEvent event = HEARTBEAT_EVENT_ARGUMENT_CAPTOR.getValue(); + assertThat(event.getValue()).isInstanceOf(List.class); + + List expectedOutput = List.of(new EndpointNameAndNamespace("podB", "namespaceA"), + new EndpointNameAndNamespace("podD", "namespaceB")); + assertThat(event.getValue()).isEqualTo(expectedOutput); + } + + /** + *
+	 *
+	 *     - we have 5 pods involved in this test
+	 *     - podA in namespaceA with no labels
+	 *     - podB in namespaceA with labels {color=blue}
+	 *     - podC in namespaceA with labels {color=red}
+	 *     - podD in namespaceB with labels {color=blue}
+	 *     - podE in namespaceB with no labels
+	 *
+	 *     We search in all namespaces without labels
+	 *     As a result we get all 5 pods
+	 *
+	 * 
+ */ + @Test + void testEndpointsInAllNamespacesWithoutServiceLabels() { + + KubernetesCatalogWatch watch = createWatcherInAllNamespacesAndLabels(Map.of()); + + EndpointSlice sliceA = createSingleEndpointWithEndpointSlices("namespaceA", Map.of(), "podA"); + EndpointSlice sliceB = createSingleEndpointWithEndpointSlices("namespaceA", Map.of("color", "blue"), "podB"); + EndpointSlice sliceC = createSingleEndpointWithEndpointSlices("namespaceA", Map.of("color", "red"), "podC"); + EndpointSlice sliceD = createSingleEndpointWithEndpointSlices("namespaceB", Map.of("color", "blue"), "podD"); + EndpointSlice sliceE = createSingleEndpointWithEndpointSlices("namespaceB", Map.of(), "podE"); + EndpointSliceList listInAllNamespaces = new EndpointSliceListBuilder() + .withItems(sliceA, sliceB, sliceC, sliceD, sliceE).build(); + mockServer.expect().withPath("/apis/discovery.k8s.io/v1/endpointslices").andReturn(200, listInAllNamespaces) + .once(); + + watch.catalogServicesWatch(); + + verify(APPLICATION_EVENT_PUBLISHER).publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture()); + + HeartbeatEvent event = HEARTBEAT_EVENT_ARGUMENT_CAPTOR.getValue(); + assertThat(event.getValue()).isInstanceOf(List.class); + + List expectedOutput = List.of(new EndpointNameAndNamespace("podA", "namespaceA"), + new EndpointNameAndNamespace("podB", "namespaceA"), new EndpointNameAndNamespace("podC", "namespaceA"), + new EndpointNameAndNamespace("podD", "namespaceB"), new EndpointNameAndNamespace("podE", "namespaceB")); + assertThat(event.getValue()).isEqualTo(expectedOutput); + } + + /** + *
+	 *     - endpoint slices are enabled, but are not supported by the cluster, as such we will fail
+	 *       with an IllegalArgumentException
+	 *     - ApiGroups is empty
+	 * 
+ */ + @Test + void testEndpointSlicesEnabledButNotSupportedViaApiGroups() { + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, + false, "", Set.of(), Map.of(), "", null, 0, true); + + APIGroupList groupList = new APIGroupListBuilder().build(); + mockServer.expect().withPath("/apis").andReturn(200, groupList).always(); + + KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider); + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, watch::postConstruct); + Assertions.assertEquals("EndpointSlices are not supported on the cluster", ex.getMessage()); + } + + /** + *
+	 *     - endpoint slices are enabled, but are not supported by the cluster, as such we will fail
+	 *       with an IllegalArgumentException
+	 *     - ApiVersions is empty
+	 * 
+ */ + @Test + void testEndpointSlicesEnabledButNotSupportedViaApiVersions() { + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, + false, "", Set.of(), Map.of(), "", null, 0, true); + + GroupVersionForDiscovery forDiscovery = new GroupVersionForDiscoveryBuilder() + .withGroupVersion("discovery.k8s.io/v1").build(); + APIGroup apiGroup = new APIGroupBuilder().withApiVersion("v1").withVersions(forDiscovery).build(); + APIGroupList groupList = new APIGroupListBuilder().withGroups(apiGroup).build(); + mockServer.expect().withPath("/apis").andReturn(200, groupList).always(); + + APIResourceList apiResourceList = new APIResourceListBuilder().build(); + mockServer.expect().withPath("/apis/discovery.k8s.io/v1").andReturn(200, apiResourceList).always(); + + KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider); + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, watch::postConstruct); + Assertions.assertEquals("EndpointSlices are not supported on the cluster", ex.getMessage()); + } + + private KubernetesCatalogWatch createWatcherInSpecificNamespaceAndLabels(String namespace, + Map labels) { + + createEndpointSlicesApiGroup(); + + when(namespaceProvider.getNamespace()).thenReturn(namespace); + + // all-namespaces = false + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, + false, "", Set.of(), labels, "", null, 0, true); + + KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider); + watch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER); + watch.postConstruct(); + return watch; + + } + + private KubernetesCatalogWatch createWatcherInAllNamespacesAndLabels(Map labels) { + + createEndpointSlicesApiGroup(); + + // all-namespaces = true + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, + false, "", Set.of(), labels, "", null, 0, true); + + KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider); + watch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER); + watch.postConstruct(); + return watch; + + } + + private static EndpointSlice createSingleEndpointWithEndpointSlices(String namespace, Map labels, + String podName) { + + Endpoint endpoint = new EndpointBuilder() + .withTargetRef(new ObjectReferenceBuilder().withName(podName).withNamespace(namespace).build()).build(); + + return new EndpointSliceBuilder().withMetadata(new ObjectMetaBuilder().withLabels(labels).build()) + .withEndpoints(endpoint).build(); + + } + + // mock KubernetesCatalogWatch::postConstruct + private static void createEndpointSlicesApiGroup() { + + GroupVersionForDiscovery forDiscovery = new GroupVersionForDiscoveryBuilder() + .withGroupVersion("discovery.k8s.io/v1").build(); + APIGroup apiGroup = new APIGroupBuilder().withApiVersion("v1").withVersions(forDiscovery).build(); + APIGroupList groupList = new APIGroupListBuilder().withGroups(apiGroup).build(); + mockServer.expect().withPath("/apis").andReturn(200, groupList).always(); + + APIResource apiResource = new APIResourceBuilder().withKind("EndpointSlice").build(); + APIResourceList apiResourceList = new APIResourceListBuilder().withResources(apiResource).build(); + mockServer.expect().withPath("/apis/discovery.k8s.io/v1").andReturn(200, apiResourceList).always(); + + } + +} 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/Fabric8KubernetesCatalogWatchEndpointsTests.java similarity index 97% rename from spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchTests.java rename to spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchEndpointsTests.java index f07dfe63..d6b20eea 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/Fabric8KubernetesCatalogWatchEndpointsTests.java @@ -48,12 +48,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * Some tests that use the fabric8 mock client. + * Some tests that use the fabric8 mock client, using Endpoints * * @author wind57 */ @EnableKubernetesMockClient(crud = true, https = false) -class Fabric8KubernetesCatalogWatchTests { +class Fabric8KubernetesCatalogWatchEndpointsTests { private final KubernetesNamespaceProvider namespaceProvider = Mockito.mock(KubernetesNamespaceProvider.class); @@ -246,10 +246,10 @@ class Fabric8KubernetesCatalogWatchTests { // all-namespaces = false KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, "", Set.of(), labels, "", null, 0); - + false, "", Set.of(), labels, "", null, 0, false); KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider); watch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER); + watch.postConstruct(); return watch; } @@ -258,10 +258,10 @@ class Fabric8KubernetesCatalogWatchTests { // all-namespaces = true KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, - false, "", Set.of(), labels, "", null, 0); - + false, "", Set.of(), labels, "", null, 0, false); KubernetesCatalogWatch watch = new KubernetesCatalogWatch(mockClient, properties, namespaceProvider); watch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER); + watch.postConstruct(); return watch; } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogServicesWatchConfigurationTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogServicesWatchConfigurationTest.java index dc7ed8b3..14090fd5 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogServicesWatchConfigurationTest.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogServicesWatchConfigurationTest.java @@ -22,8 +22,8 @@ import java.util.List; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -45,34 +45,34 @@ public class KubernetesCatalogServicesWatchConfigurationTest { private ConfigurableApplicationContext context; - @After - public void close() { + @AfterEach + void close() { if (this.context != null) { this.context.close(); } } @Test - public void kubernetesCatalogWatchDisabled() throws Exception { + void kubernetesCatalogWatchDisabled() { setup("spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false"); assertThat(this.context.containsBean("kubernetesCatalogWatch")).isFalse(); } @Test - public void kubernetesCatalogWatchWhenKubernetesDisabled() throws Exception { + void kubernetesCatalogWatchWhenKubernetesDisabled() { setup(); assertThat(this.context.containsBean("kubernetesCatalogWatch")).isFalse(); } @Test - public void kubernetesCatalogWatchWhenServiceDiscoveryDisabled() throws Exception { + void kubernetesCatalogWatchWhenServiceDiscoveryDisabled() { setup("spring.cloud.discovery.enabled=false"); assertThat(this.context.containsBean("kubernetesCatalogWatch")).isFalse(); } @Test - public void kubernetesCatalogWatchDefaultEnabled() throws Exception { - setup("spring.main.cloud-platform=KUBERNETES"); + void kubernetesCatalogWatchDefaultEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.discovery.use-endpoint-slices=false"); assertThat(this.context.containsBean("kubernetesCatalogWatch")).isTrue(); } @@ -93,8 +93,9 @@ public class KubernetesCatalogServicesWatchConfigurationTest { return mock(KubernetesClient.class); } + @SuppressWarnings("unchecked") @Bean - PodUtils podUtils() { + PodUtils podUtils() { PodUtils podPodUtils = mock(PodUtils.class); when(podPodUtils.currentPod()).thenReturn(() -> mock(Pod.class)); return podPodUtils; 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 33e942de..a9396706 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 @@ -366,20 +366,22 @@ class KubernetesCatalogWatchTest { // all-namespaces = true KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, - false, "", Set.of(), Map.of(), "", null, 0); + false, "", Set.of(), Map.of(), "", null, 0, false); kubernetesCatalogWatch = new KubernetesCatalogWatch(CLIENT, properties, namespaceProvider); kubernetesCatalogWatch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER); + kubernetesCatalogWatch.postConstruct(); } private void createInSpecificNamespaceWatcher() { // all-namespaces = false KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, "", Set.of(), Map.of(), "", null, 0); + false, "", Set.of(), Map.of(), "", null, 0, false); kubernetesCatalogWatch = new KubernetesCatalogWatch(CLIENT, properties, namespaceProvider); kubernetesCatalogWatch.setApplicationEventPublisher(APPLICATION_EVENT_PUBLISHER); + kubernetesCatalogWatch.postConstruct(); when(namespaceProvider.getNamespace()).thenReturn("catalog-watcher-namespace"); when(CLIENT.endpoints()).thenReturn(MIXED_OPERATION); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationPropertiesTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationPropertiesTests.java index 8f5ff2ad..f6668668 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationPropertiesTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationPropertiesTests.java @@ -22,8 +22,8 @@ import java.util.List; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -44,34 +44,34 @@ public class KubernetesDiscoveryClientAutoConfigurationPropertiesTests { private ConfigurableApplicationContext context; - @After - public void close() { + @AfterEach + void close() { if (this.context != null) { this.context.close(); } } @Test - public void kubernetesDiscoveryDisabled() throws Exception { + void kubernetesDiscoveryDisabled() { setup("spring.cloud.kubernetes.discovery.enabled=false", "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false"); assertThat(this.context.getBeanNamesForType(KubernetesDiscoveryClient.class)).isEmpty(); } @Test - public void kubernetesDiscoveryWhenKubernetesDisabled() throws Exception { + void kubernetesDiscoveryWhenKubernetesDisabled() { setup(); assertThat(this.context.getBeanNamesForType(KubernetesDiscoveryClient.class)).isEmpty(); } @Test - public void kubernetesDiscoveryWhenDiscoveryDisabled() throws Exception { + void kubernetesDiscoveryWhenDiscoveryDisabled() { setup("spring.cloud.discovery.enabled=false"); assertThat(this.context.getBeanNamesForType(KubernetesDiscoveryClient.class)).isEmpty(); } @Test - public void kubernetesDiscoveryDefaultEnabled() throws Exception { + void kubernetesDiscoveryDefaultEnabled() { setup("spring.main.cloud-platform=KUBERNETES"); assertThat(this.context.getBeanNamesForType(KubernetesDiscoveryClient.class)).hasSize(1); } @@ -93,8 +93,9 @@ public class KubernetesDiscoveryClientAutoConfigurationPropertiesTests { return mock(KubernetesClient.class); } + @SuppressWarnings("unchecked") @Bean - PodUtils podUtils() { + PodUtils podUtils() { PodUtils podPodUtils = mock(PodUtils.class); when(podPodUtils.currentPod()).thenReturn(() -> mock(Pod.class)); return podPodUtils; diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java index 26c0e1b1..4f8a53b9 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java @@ -16,8 +16,7 @@ package org.springframework.cloud.kubernetes.fabric8.discovery; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -25,20 +24,18 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient; -import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, - properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false" }) -public class KubernetesDiscoveryClientAutoConfigurationTests { +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.config.enabled=false", "spring.cloud.kubernetes.discovery.use-endpoint-slices=false" }) +class KubernetesDiscoveryClientAutoConfigurationTests { @Autowired(required = false) private DiscoveryClient discoveryClient; @Test - public void kubernetesDiscoveryClientCreated() { + void kubernetesDiscoveryClientCreated() { assertThat(this.discoveryClient).isNotNull().isInstanceOf(CompositeDiscoveryClient.class); CompositeDiscoveryClient composite = (CompositeDiscoveryClient) this.discoveryClient; diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java index 6bc5796a..456971e3 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java @@ -18,8 +18,9 @@ package org.springframework.cloud.kubernetes.fabric8.discovery; import java.util.Collections; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; @@ -34,7 +35,6 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import static junit.framework.TestCase.assertEquals; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; @@ -43,11 +43,11 @@ import static org.mockito.Mockito.verify; /** * @author Zhanwei Wang */ -public class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests { +class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests { private AnnotationConfigApplicationContext context; - @After + @AfterEach public void close() { if (this.context != null) { if (this.context.getParent() != null) { @@ -58,15 +58,15 @@ public class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests { } @Test - public void onWhenRequested() throws Exception { + void onWhenRequested() { setup("server.port=7000", "spring.cloud.config.discovery.enabled=true", "spring.cloud.kubernetes.discovery.enabled:true", "spring.application.name:test", "spring.cloud.config.discovery.service-id:configserver"); - assertEquals(1, this.context.getParent().getBeanNamesForType(DiscoveryClient.class).length); + Assertions.assertEquals(1, this.context.getParent().getBeanNamesForType(DiscoveryClient.class).length); DiscoveryClient client = this.context.getParent().getBean(DiscoveryClient.class); verify(client, atLeast(2)).getInstances("configserver"); ConfigClientProperties locator = this.context.getBean(ConfigClientProperties.class); - assertEquals("http://fake:8888/", locator.getUri()[0]); + Assertions.assertEquals("http://fake:8888/", locator.getUri()[0]); } private void setup(String... env) { 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 67e67bea..2b2e8f0e 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 @@ -80,7 +80,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(false, null, false, null, false, null); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -110,7 +110,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(true, null, false, null, false, null); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -141,7 +141,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(true, "l_", false, null, false, null); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -172,7 +172,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(false, null, true, null, false, null); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -203,7 +203,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(false, null, true, "a_", false, null); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -234,7 +234,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(false, null, false, null, true, null); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -265,7 +265,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(false, null, false, null, true, "p_"); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(CLIENT, properties, a -> null); @@ -296,7 +296,7 @@ public class KubernetesDiscoveryClientFilterMetadataTest { Metadata metadata = new Metadata(true, "l_", true, "a_", true, "p_"); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, null, Set.of(), Map.of(), null, metadata, 0); + false, null, Set.of(), Map.of(), null, metadata, 0, true); 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 4d83c3c9..b9cd8b65 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 @@ -68,7 +68,7 @@ public class KubernetesDiscoveryClientFilterTest { KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, false, "metadata.additionalProperties['spring-boot']", Set.of(), Map.of(), null, - KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true); KubernetesDiscoveryClient client = new KubernetesDiscoveryClient(this.kubernetesClient, properties, this.kubernetesClientServicesFunction); @@ -96,7 +96,7 @@ public class KubernetesDiscoveryClientFilterTest { KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, false, "metadata.name.startsWith('service')", Set.of(), Map.of(), null, - KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true); KubernetesDiscoveryClient client = new KubernetesDiscoveryClient(this.kubernetesClient, properties, this.kubernetesClientServicesFunction); @@ -116,7 +116,7 @@ public class KubernetesDiscoveryClientFilterTest { when(this.kubernetesClient.services()).thenReturn(this.serviceOperation); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60, - false, "", Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + false, "", Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true); 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 3c52d30b..3f65f792 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 @@ -112,7 +112,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test").create(service); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, - 60, false, null, Set.of(), labels, "http_tcp", Metadata.DEFAULT, 0); + 60, false, null, Set.of(), labels, "http_tcp", Metadata.DEFAULT, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -161,7 +161,7 @@ public class KubernetesDiscoveryClientTest { 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); + 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false); final KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -191,7 +191,7 @@ public class KubernetesDiscoveryClientTest { final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(namespace1, namespace3), true, 60, false, null, Set.of(), Map.of(), null, - KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false); final KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -223,7 +223,7 @@ public class KubernetesDiscoveryClientTest { Metadata metadata = new Metadata(false, null, false, null, true, "port."); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, - 60, false, null, Set.of(443, 8443), labels, null, metadata, 0); + 60, false, null, Set.of(443, 8443), labels, null, metadata, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -310,7 +310,7 @@ public class KubernetesDiscoveryClientTest { final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(nameSpace1, nameSpace2), true, 60, false, null, Set.of(), Map.of(), null, - KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -343,7 +343,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test2").create(service2); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, - 60, false, null, Set.of(), Map.of(), null, Metadata.DEFAULT, 0); + 60, false, null, Set.of(), Map.of(), null, Metadata.DEFAULT, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -395,7 +395,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test").create(service); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, - 60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0); + 60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -426,7 +426,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test").create(service); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, - 60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0); + 60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -456,7 +456,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test").create(service); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, - 60, false, null, Set.of(443, 8443), Map.of(), "oops", Metadata.DEFAULT, 0); + 60, false, null, Set.of(443, 8443), Map.of(), "oops", Metadata.DEFAULT, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -485,7 +485,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test").create(service); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, - 60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0); + 60, false, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0, true); final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties, KubernetesClient::services, new ServicePortSecureResolver(properties)); @@ -542,7 +542,7 @@ public class KubernetesDiscoveryClientTest { mockClient.services().inNamespace("test").create(service); final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, - 60, true, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0); + 60, true, null, Set.of(443, 8443), Map.of(), null, Metadata.DEFAULT, 0, true); 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 28bb9bd2..d390195b 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 @@ -52,7 +52,7 @@ class ServicePortSecureResolverTest { void testPortNumbersOnly() { KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, false, null, Set.of(443, 8443, 12345), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, - 0); + 0, true); ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties); diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java index 1b3ce81a..f2353443 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java @@ -36,14 +36,14 @@ import static org.assertj.core.api.Assertions.assertThat; */ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class, ReactiveCommonsClientAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class, KubernetesDiscoveryClientAutoConfiguration.class, KubernetesReactiveDiscoveryClientAutoConfiguration.class)); @Test - public void shouldWorkWithDefaults() { + void shouldWorkWithDefaults() { contextRunner.withPropertyValues("spring.main.cloud-platform=KUBERNETES").run(context -> { assertThat(context).hasSingleBean(ReactiveDiscoveryClient.class); assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class); @@ -51,7 +51,7 @@ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { } @Test - public void shouldNotHaveDiscoveryClientWhenDiscoveryDisabled() { + void shouldNotHaveDiscoveryClientWhenDiscoveryDisabled() { contextRunner.withPropertyValues("spring.cloud.discovery.enabled=false").run(context -> { assertThat(context).doesNotHaveBean("kubernetesReactiveDiscoveryClient"); assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class); @@ -60,7 +60,7 @@ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { } @Test - public void shouldNotHaveDiscoveryClientWhenReactiveDiscoveryDisabled() { + void shouldNotHaveDiscoveryClientWhenReactiveDiscoveryDisabled() { contextRunner.withPropertyValues("spring.cloud.discovery.reactive.enabled=false").run(context -> { assertThat(context).doesNotHaveBean("kubernetesReactiveDiscoveryClient"); assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class); @@ -69,7 +69,7 @@ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { } @Test - public void shouldNotHaveDiscoveryClientWhenKubernetesDisabled() { + void shouldNotHaveDiscoveryClientWhenKubernetesDisabled() { contextRunner.run(context -> { assertThat(context).doesNotHaveBean("kubernetesReactiveDiscoveryClient"); assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class); @@ -78,7 +78,7 @@ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { } @Test - public void shouldNotHaveDiscoveryClientWhenKubernetesDiscoveryDisabled() { + void shouldNotHaveDiscoveryClientWhenKubernetesDiscoveryDisabled() { contextRunner.withPropertyValues("spring.cloud.kubernetes.discovery.enabled=false").run(context -> { assertThat(context).doesNotHaveBean("kubernetesReactiveDiscoveryClient"); assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class); @@ -87,7 +87,7 @@ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { } @Test - public void worksWithoutWebflux() { + void worksWithoutWebflux() { contextRunner.withClassLoader(new FilteredClassLoader("org.springframework.web.reactive")).run(context -> { assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class); assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); @@ -95,7 +95,7 @@ class KubernetesReactiveDiscoveryClientAutoConfigurationTests { } @Test - public void worksWithoutActuator() { + void worksWithoutActuator() { contextRunner.withPropertyValues("spring.main.cloud-platform=KUBERNETES") .withClassLoader(new FilteredClassLoader("org.springframework.boot.actuate")).run(context -> { assertThat(context).hasSingleBean(ReactiveDiscoveryClient.class); 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 6ff73fb3..be9c96bc 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 @@ -53,7 +53,7 @@ import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesD class KubernetesReactiveDiscoveryClientTests { @BeforeEach - public void setup(@KubernetesExtension.Client KubernetesClient kubernetesClient) { + void setup(@KubernetesExtension.Client KubernetesClient kubernetesClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, kubernetesClient.getConfiguration().getMasterUrl()); @@ -64,7 +64,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void verifyDefaults(@KubernetesExtension.Client KubernetesClient kubernetesClient) { + void verifyDefaults(@KubernetesExtension.Client KubernetesClient kubernetesClient) { ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(kubernetesClient, KubernetesDiscoveryProperties.DEFAULT, KubernetesClient::services); assertThat(client.description()).isEqualTo("Kubernetes Reactive Discovery Client"); @@ -72,7 +72,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnFluxOfServices(@KubernetesExtension.Client KubernetesClient kubernetesClient, + void shouldReturnFluxOfServices(@KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services") .andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("s1") @@ -96,7 +96,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnEmptyFluxOfServicesWhenNoInstancesFound( + void shouldReturnEmptyFluxOfServicesWhenNoInstancesFound( @KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services") @@ -109,8 +109,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnEmptyFluxForNonExistingService( - @KubernetesExtension.Client KubernetesClient kubernetesClient, + void shouldReturnEmptyFluxForNonExistingService(@KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get() .withPath("/api/v1/namespaces/test/endpoints?fieldSelector=metadata.name%3Dnonexistent-service") @@ -123,8 +122,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnEmptyFluxWhenServiceHasNoSubsets( - @KubernetesExtension.Client KubernetesClient kubernetesClient, + void shouldReturnEmptyFluxWhenServiceHasNoSubsets(@KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services") .andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service") @@ -146,7 +144,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnFlux(@KubernetesExtension.Client KubernetesClient kubernetesClient, + void shouldReturnFlux(@KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { ServiceList services = new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service") .withNamespace("test").withLabels(new HashMap() { @@ -184,7 +182,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnFluxWithPrefixedMetadata(@KubernetesExtension.Client KubernetesClient kubernetesClient, + void shouldReturnFluxWithPrefixedMetadata(@KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services") .andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service") @@ -226,7 +224,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnFluxWhenServiceHasMultiplePortsAndPrimaryPortNameIsSet( + void shouldReturnFluxWhenServiceHasMultiplePortsAndPrimaryPortNameIsSet( @KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services") @@ -269,8 +267,7 @@ class KubernetesReactiveDiscoveryClientTests { } @Test - public void shouldReturnFluxOfServicesAcrossAllNamespaces( - @KubernetesExtension.Client KubernetesClient kubernetesClient, + void shouldReturnFluxOfServicesAcrossAllNamespaces(@KubernetesExtension.Client KubernetesClient kubernetesClient, @KubernetesExtension.Server KubernetesServer kubernetesServer) { kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services") .andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata().withName("existing-service") @@ -302,7 +299,7 @@ class KubernetesReactiveDiscoveryClientTests { .once(); KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60, - false, null, Set.of(), Map.of(), "https_tcp", Metadata.DEFAULT, 0); + false, null, Set.of(), Map.of(), "https_tcp", Metadata.DEFAULT, 0, true); 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 d6dc5d9e..1cc0d702 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 @@ -79,7 +79,8 @@ class Fabric8ServiceInstanceMapperTests { void testMapperSecureNullLabelsAndAnnotations() { KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties(); KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, true, Set.of(), - true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, + false); 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 7852d1c6..6da2fe82 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 @@ -96,7 +96,8 @@ class KubernetesServiceListSupplierTests { serviceList.getItems().add(buildService("test-service", 8080)); when(this.multiDeletable.list()).thenReturn(serviceList); KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(true, true, Set.of(), - true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0); + true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, + false); KubernetesServicesListSupplier supplier = new Fabric8ServicesListSupplier(environment, client, mapper, discoveryProperties); List instances = supplier.get().blockFirst(); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/CatalogWatchIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/CatalogWatchIT.java index 7cb38816..250d6dc6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/CatalogWatchIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/CatalogWatchIT.java @@ -27,11 +27,11 @@ 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.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; @@ -58,8 +58,7 @@ class CatalogWatchIT { private static final String NAMESPACE = "default"; - private static final K3sContainer K3S = Commons.container() - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(CatalogWatchIT.class))); + private static final K3sContainer K3S = Commons.container(); private static KubernetesClient client; @@ -83,9 +82,16 @@ class CatalogWatchIT { Commons.loadSpringCloudKubernetesImage(APP_NAME, K3S); Fabric8Utils.setUp(client, "default"); + } + @BeforeEach + void beforeEach() throws Exception { deployBusyboxManifests(); - deployApp(); + } + + @AfterEach + void afterEach() { + deleteApp(); } /** @@ -96,9 +102,40 @@ class CatalogWatchIT { * - assert that we receive only spring-cloud-kubernetes-fabric8-client-catalog-watcher pod * */ - @SuppressWarnings("unchecked") @Test - void testCatalogWatch() { + void testCatalogWatchWithEndpoints() throws Exception { + deployApp(false); + assertLogStatement("stateGenerator is of type: Fabric8EndpointsCatalogWatch"); + test(); + } + + @Test + void testCatalogWatchWithEndpointSlices() throws Exception { + deployApp(true); + assertLogStatement("stateGenerator is of type: Fabric8EndpointSliceV1CatalogWatch"); + test(); + } + + /** + * we log in debug mode the type of the StateGenerator we use, be that Endpoints or + * EndpointSlices. Here we make sure that in the test we actually use the correct + * type. + */ + private void assertLogStatement(String log) throws Exception { + String appPodName = K3S + .execInContainer("kubectl", "get", "pods", "-l", + "app=spring-cloud-kubernetes-fabric8-client-catalog-watcher", "-o=name", "--no-headers") + .getStdout(); + String allLogs = K3S.execInContainer("kubectl", "logs", appPodName.trim()).getStdout(); + Assertions.assertTrue(allLogs.contains(log)); + } + + /** + * the test is the same for both endpoints and endpoint slices, the set-up for them is + * different. + */ + @SuppressWarnings("unchecked") + private void test() { WebClient client = builder().baseUrl("localhost/result").build(); EndpointNameAndNamespace[] holder = new EndpointNameAndNamespace[2]; @@ -142,9 +179,17 @@ class CatalogWatchIT { .retrieve().bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) .retryWhen(retrySpec()).block(); + // we need to get the event from KubernetesCatalogWatch, but that happens + // on periodic bases. So in order to be sure we got the event we care about + // we wait until the result has a single entry, which means busybox was + // deleted + // + KubernetesCatalogWatch received the new update. + if (result != null && result.size() != 1) { + return false; + } + // we will only receive one pod here, our own if (result != null) { - Assertions.assertEquals(1, result.size()); afterDelete[0] = result.get(0); return true; } @@ -155,11 +200,9 @@ class CatalogWatchIT { Assertions.assertTrue(afterDelete[0].endpointName().contains(APP_NAME)); Assertions.assertEquals("default", afterDelete[0].namespace()); - deleteApp(); - } - private static void deployBusyboxManifests() throws Exception { + private void deployBusyboxManifests() throws Exception { Deployment deployment = client.apps().deployments().load(getBusyboxDeployment()).get(); @@ -178,9 +221,10 @@ class CatalogWatchIT { } - private static void deployApp() { + private static void deployApp(boolean useEndpointSlices) { - Deployment appDeployment = client.apps().deployments().load(getAppDeployment()).get(); + InputStream deployment = useEndpointSlices ? getEndpointSlicesAppDeployment() : getEndpointsAppDeployment(); + Deployment appDeployment = client.apps().deployments().load(deployment).get(); String version = K8SUtils.getPomVersion(); String currentImage = appDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); @@ -211,7 +255,7 @@ class CatalogWatchIT { private void deleteApp() { Fabric8Utils.deleteDeployment(client, NAMESPACE, appDeploymentName); Fabric8Utils.deleteService(client, NAMESPACE, appServiceName); - client.network().v1().ingresses().withName(appIngressName).delete(); + Fabric8Utils.deleteIngress(client, NAMESPACE, appIngressName); } private static InputStream getBusyboxService() { @@ -222,8 +266,15 @@ class CatalogWatchIT { return Fabric8Utils.inputStream("busybox/deployment.yaml"); } - private static InputStream getAppDeployment() { - return Fabric8Utils.inputStream("app/watcher-deployment.yaml"); + /** + * deployment where support for endpoint slices is equal to false + */ + private static InputStream getEndpointsAppDeployment() { + return Fabric8Utils.inputStream("app/watcher-endpoints-deployment.yaml"); + } + + private static InputStream getEndpointSlicesAppDeployment() { + return Fabric8Utils.inputStream("app/watcher-endpoint-slices-deployment.yaml"); } private static InputStream getAppIngress() { diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoint-slices-deployment.yaml similarity index 91% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoint-slices-deployment.yaml index a8ae8920..531a1447 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoint-slices-deployment.yaml @@ -29,3 +29,5 @@ spec: env: - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY value: DEBUG + - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_USE_ENDPOINT_SLICES + value: true diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoints-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoints-deployment.yaml new file mode 100644 index 00000000..d2002401 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoints-deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-cloud-kubernetes-fabric8-client-catalog-watcher +spec: + selector: + matchLabels: + app: spring-cloud-kubernetes-fabric8-client-catalog-watcher + template: + metadata: + labels: + app: spring-cloud-kubernetes-fabric8-client-catalog-watcher + spec: + serviceAccountName: spring-cloud-kubernetes-serviceaccount + containers: + - name: spring-cloud-kubernetes-fabric8-client-catalog-watcher + image: docker.io/springcloud/spring-cloud-kubernetes-fabric8-client-catalog-watcher + imagePullPolicy: IfNotPresent + readinessProbe: + httpGet: + port: 8080 + path: /actuator/health/readiness + livenessProbe: + httpGet: + port: 8080 + path: /actuator/health/liveness + ports: + - containerPort: 8080 + env: + - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY + value: DEBUG + - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_USE_ENDPOINT_SLICES + value: false diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java index 1b07b018..d7f4e300 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java @@ -213,6 +213,19 @@ public final class Fabric8Utils { }); } + /** + * delete ingress and wait for it to be deleted. + */ + public static void deleteIngress(KubernetesClient client, String namespace, String name) { + client.network().v1().ingresses().inNamespace(namespace).withName(name).delete(); + + await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> { + Ingress ingress = client.network().v1().ingresses().inNamespace(namespace).withName(name).get(); + return ingress == null; + }); + + } + private static void innerSetup(KubernetesClient client, String namespace, InputStream serviceAccountAsStream, InputStream roleBindingAsStream, InputStream roleAsStream) { ServiceAccount serviceAccountFromStream = client.serviceAccounts().load(serviceAccountAsStream).get(); diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml index 83ba8006..4a397a69 100644 --- a/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml @@ -4,6 +4,6 @@ metadata: namespace: default name: namespace-reader rules: - - apiGroups: ["", "extensions", "apps"] - resources: ["configmaps", "pods", "services", "endpoints", "secrets"] + - apiGroups: ["", "extensions", "apps", "discovery.k8s.io"] + resources: ["configmaps", "pods", "services", "endpoints", "secrets", "endpointslices"] verbs: ["get", "list", "watch"]