Use endpoint slices for fabric8 catalog watcher (#1149)
This commit is contained in:
@@ -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"]
|
||||
```
|
||||
|
||||
@@ -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<V1Service> serviceLister = setupServiceLister(testServiceSecuredAnnotation1);
|
||||
Lister<V1Endpoints> 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<V1Service> serviceLister = setupServiceLister(testServiceSecuredLabel1);
|
||||
Lister<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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<V1Endpoints> 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);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
Lister<V1Endpoints> 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<V1Endpoints> 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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Integer> knownSecurePorts,
|
||||
@DefaultValue Map<String, String> 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
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EndpointNameAndNamespace> state(Stream<ObjectReference> references) {
|
||||
return references.filter(Objects::nonNull).map(x -> new EndpointNameAndNamespace(x.getName(), x.getNamespace()))
|
||||
.sorted(Comparator.comparing(EndpointNameAndNamespace::endpointName, String::compareTo)).toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Fabric8CatalogWatchContext, List<EndpointNameAndNamespace>> {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8EndpointSliceV1CatalogWatch.class));
|
||||
|
||||
@Override
|
||||
public List<EndpointNameAndNamespace> apply(Fabric8CatalogWatchContext context) {
|
||||
// take only pods that have endpoints
|
||||
List<EndpointSlice> 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<ObjectReference> references = endpointSlices.stream().map(EndpointSlice::getEndpoints)
|
||||
.flatMap(List::stream).map(Endpoint::getTargetRef);
|
||||
|
||||
return Fabric8CatalogWatchContext.state(references);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Fabric8CatalogWatchContext, List<EndpointNameAndNamespace>> {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8EndpointsCatalogWatch.class));
|
||||
|
||||
@Override
|
||||
public List<EndpointNameAndNamespace> apply(Fabric8CatalogWatchContext context) {
|
||||
// take only pods that have endpoints
|
||||
List<Endpoints> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - 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)
|
||||
* </pre>
|
||||
*/
|
||||
Stream<ObjectReference> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Fabric8CatalogWatchContext, List<EndpointNameAndNamespace>> stateGenerator;
|
||||
|
||||
private volatile List<EndpointNameAndNamespace> 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> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - 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)
|
||||
* </pre>
|
||||
*/
|
||||
List<EndpointNameAndNamespace> 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<EndpointNameAndNamespace> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<HeartbeatEvent> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - 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
|
||||
* </pre>
|
||||
*/
|
||||
@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<EndpointNameAndNamespace> expectedOutput = List.of(new EndpointNameAndNamespace("podB", "namespaceA"));
|
||||
assertThat(event.getValue()).isEqualTo(expectedOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
*
|
||||
* - 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
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@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<EndpointNameAndNamespace> expectedOutput = List.of(new EndpointNameAndNamespace("podA", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podB", "namespaceA"), new EndpointNameAndNamespace("podC", "namespaceA"));
|
||||
assertThat(event.getValue()).isEqualTo(expectedOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
*
|
||||
* - 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
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@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<EndpointNameAndNamespace> expectedOutput = List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podD", "namespaceB"));
|
||||
assertThat(event.getValue()).isEqualTo(expectedOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
*
|
||||
* - 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
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@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<EndpointNameAndNamespace> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - endpoint slices are enabled, but are not supported by the cluster, as such we will fail
|
||||
* with an IllegalArgumentException
|
||||
* - ApiGroups is empty
|
||||
* </pre>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - endpoint slices are enabled, but are not supported by the cluster, as such we will fail
|
||||
* with an IllegalArgumentException
|
||||
* - ApiVersions is empty
|
||||
* </pre>
|
||||
*/
|
||||
@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<String, String> 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<String, String> 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<String, String> 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<Pod> podPodUtils = mock(PodUtils.class);
|
||||
when(podPodUtils.currentPod()).thenReturn(() -> mock(Pod.class));
|
||||
return podPodUtils;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Pod> podPodUtils = mock(PodUtils.class);
|
||||
when(podPodUtils.currentPod()).thenReturn(() -> mock(Pod.class));
|
||||
return podPodUtils;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<String, String>() {
|
||||
@@ -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<ServiceInstance> instances = client.getInstances("existing-service");
|
||||
|
||||
@@ -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<ServicePort> ports = new ArrayList<>();
|
||||
ports.add(new ServicePortBuilder().withPort(443).build());
|
||||
Service service = buildService("test", "abc", ports, null, null);
|
||||
|
||||
@@ -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<ServiceInstance> instances = supplier.get().blockFirst();
|
||||
|
||||
@@ -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
|
||||
* </pre>
|
||||
*/
|
||||
@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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user