Skip to content

Commit b5e1513

Browse files
committed
fabric8
Signed-off-by: wind57 <[email protected]>
1 parent bd8d127 commit b5e1513

File tree

7 files changed

+201
-11
lines changed

7 files changed

+201
-11
lines changed

spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointSliceV1CatalogWatch.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.Objects;
2122
import java.util.function.Function;
2223
import java.util.stream.Stream;
2324

@@ -72,13 +73,21 @@ else if (!context.properties().namespaces().isEmpty()) {
7273
endpointSlices = endpointSlices(context, namespace, client);
7374
}
7475

76+
return state(endpointSlices);
77+
}
78+
79+
/**
80+
* This one is visible for testing, especially since fabric8 mock client will save
81+
* null subsets as empty lists, thus blocking some unit test.
82+
*/
83+
List<EndpointNameAndNamespace> state(List<EndpointSlice> endpointSlices) {
7584
Stream<ObjectReference> references = endpointSlices.stream()
7685
.map(EndpointSlice::getEndpoints)
86+
.filter(Objects::nonNull)
7787
.flatMap(List::stream)
7888
.map(Endpoint::getTargetRef);
7989

8090
return Fabric8CatalogWatchContext.state(references);
81-
8291
}
8392

8493
private List<EndpointSlice> endpointSlices(Fabric8CatalogWatchContext context, String namespace,

spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointsCatalogWatch.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,23 @@ public List<EndpointNameAndNamespace> apply(Fabric8CatalogWatchContext context)
4444
List<Endpoints> endpoints = endpoints(context.properties(), context.kubernetesClient(),
4545
context.namespaceProvider(), "catalog-watcher", null, ALWAYS_TRUE);
4646

47-
/**
48-
* <pre>
49-
* - An "Endpoints" holds a List of EndpointSubset.
50-
* - A single EndpointSubset holds a List of EndpointAddress
51-
*
52-
* - (The union of all EndpointSubsets is the Set of all Endpoints)
53-
* - Set of Endpoints is the cartesian product of :
54-
* EndpointSubset::getAddresses and EndpointSubset::getPorts (each is a List)
55-
* </pre>
56-
*/
47+
return state(endpoints);
48+
}
49+
50+
/**
51+
* This one is visible for testing, especially since fabric8 mock client will save
52+
* null subsets as empty lists, thus blocking some unit test.
53+
*
54+
* <pre>
55+
* - An "Endpoints" holds a List of EndpointSubset.
56+
* - A single EndpointSubset holds a List of EndpointAddress
57+
*
58+
* - (The union of all EndpointSubsets is the Set of all Endpoints)
59+
* - Set of Endpoints is the cartesian product of :
60+
* EndpointSubset::getAddresses and EndpointSubset::getPorts (each is a List)
61+
* </pre>
62+
*/
63+
List<EndpointNameAndNamespace> state(List<Endpoints> endpoints) {
5764
Stream<ObjectReference> references = endpoints.stream()
5865
.map(Endpoints::getSubsets)
5966
.filter(Objects::nonNull)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.kubernetes.fabric8.discovery;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
23+
import io.fabric8.kubernetes.api.model.discovery.v1.Endpoint;
24+
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSlice;
25+
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSliceBuilder;
26+
import org.junit.jupiter.api.Test;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* @author wind57
32+
*/
33+
class Fabric8EndpointSliceV1CatalogWatchTests {
34+
35+
private final Fabric8EndpointSliceV1CatalogWatch catalogWatch = new Fabric8EndpointSliceV1CatalogWatch();
36+
37+
@Test
38+
void stateEndpointsWithoutSubsets() {
39+
40+
List<Endpoint> endpoints = null;
41+
42+
EndpointSlice slice = new EndpointSliceBuilder()
43+
.withMetadata(new ObjectMetaBuilder().withNamespace("default")
44+
.withName("slice-no-endpoints")
45+
.withLabels(Map.of())
46+
.build())
47+
.withEndpoints(endpoints)
48+
.build();
49+
50+
// even if Endpoints are missing, we do not fail
51+
assertThat(catalogWatch.state(List.of(slice))).isEmpty();
52+
}
53+
54+
}

spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8EndpointsAndEndpointSlicesTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ void afterEach() {
235235
*/
236236
abstract void testTwoNamespacesOutOfThree();
237237

238+
/**
239+
* <pre>
240+
* - in the old API (plain Endpoints), tests that subsets are missing
241+
* and we do not fail.
242+
* - in the new API (EndpointSlices), tests that Endpoints are missing
243+
* and we do not fail.
244+
* </pre>
245+
*/
246+
abstract void testWithoutSubsetsOrEndpoints();
247+
238248
KubernetesCatalogWatch createWatcherInAllNamespacesWithLabels(Map<String, String> labels, Set<String> namespaces,
239249
boolean endpointSlices) {
240250

@@ -309,6 +319,20 @@ void endpoints(String namespace, Map<String, String> labels, String podName) {
309319
mockClient().endpoints().inNamespace(namespace).resource(endpoints).create();
310320
}
311321

322+
void endpointsWithoutSubsets(String namespace, Map<String, String> labels, String podName) {
323+
324+
// though we set it to null here, the mock client when creating it
325+
// will set it to an empty list. I will keep it like this, may be client changes
326+
// in the future and we have the case still covered by a test
327+
List<EndpointSubset> endpointSubsets = null;
328+
329+
Endpoints endpoints = new EndpointsBuilder()
330+
.withMetadata(new ObjectMetaBuilder().withLabels(labels).withName("endpoints-" + podName).build())
331+
.withSubsets(endpointSubsets)
332+
.build();
333+
mockClient().endpoints().inNamespace(namespace).resource(endpoints).create();
334+
}
335+
312336
void service(String namespace, Map<String, String> labels, String podName) {
313337

314338
Service service = new ServiceBuilder()
@@ -335,6 +359,22 @@ static void endpointSlice(String namespace, Map<String, String> labels, String p
335359

336360
}
337361

362+
static void endpointSliceWithoutEndpoints(String namespace, Map<String, String> labels, String podName) {
363+
364+
List<Endpoint> endpoints = null;
365+
366+
EndpointSlice slice = new EndpointSliceBuilder()
367+
.withMetadata(new ObjectMetaBuilder().withNamespace(namespace)
368+
.withName("slice-" + podName)
369+
.withLabels(labels)
370+
.build())
371+
.withEndpoints(endpoints)
372+
.build();
373+
374+
mockClient().discovery().v1().endpointSlices().inNamespace(namespace).resource(slice).create();
375+
376+
}
377+
338378
static void invokeAndAssert(KubernetesCatalogWatch watch, List<EndpointNameAndNamespace> state) {
339379
watch.catalogServicesWatch();
340380

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.kubernetes.fabric8.discovery;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import io.fabric8.kubernetes.api.model.EndpointSubset;
23+
import io.fabric8.kubernetes.api.model.Endpoints;
24+
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
25+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
26+
import org.junit.jupiter.api.Test;
27+
28+
import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* @author wind57
34+
*/
35+
class Fabric8EndpointsCatalogWatchTests {
36+
37+
private final Fabric8EndpointsCatalogWatch catalogWatch = new Fabric8EndpointsCatalogWatch();
38+
39+
@Test
40+
void stateEndpointsWithoutSubsets() {
41+
42+
// though we set it to null here, the mock client when creating it
43+
// will set it to an empty list. I will keep it like this, may be client changes
44+
// in the future and we have the case still covered by a test
45+
List<EndpointSubset> endpointSubsets = null;
46+
47+
Endpoints endpoints = new EndpointsBuilder()
48+
.withMetadata(new ObjectMetaBuilder().withLabels(Map.of()).withName("endpoints-no-subsets").build())
49+
.withSubsets(endpointSubsets)
50+
.build();
51+
52+
// we do not fail, even if Subsets are not present
53+
List<EndpointNameAndNamespace> result = catalogWatch.state(List.of(endpoints));
54+
assertThat(result).isEmpty();
55+
}
56+
57+
}

spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchEndpointSlicesTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ void testTwoNamespacesOutOfThree() {
178178
new EndpointNameAndNamespace("podF", "namespaceB")));
179179
}
180180

181+
@Test
182+
@Override
183+
void testWithoutSubsetsOrEndpoints() {
184+
KubernetesCatalogWatch watch = createWatcherInSpecificNamespacesWithLabels(Set.of("namespaceA"),
185+
Map.of("color", "blue"), ENDPOINT_SLICES);
186+
187+
endpointSliceWithoutEndpoints("namespaceA", Map.of("color", "blue"), "podA");
188+
189+
// we do not fail here, even if Endpoints are not present.
190+
invokeAndAssert(watch, List.of());
191+
}
192+
181193
// work-around for : https://github.com/fabric8io/kubernetes-client/issues/4649
182194
static KubernetesClient endpointSlicesMockClient() {
183195
return mockClient;

spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesCatalogWatchEndpointsTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ void testTwoNamespacesOutOfThree() {
235235
new EndpointNameAndNamespace("podF", "namespaceB")));
236236
}
237237

238+
@Test
239+
@Override
240+
void testWithoutSubsetsOrEndpoints() {
241+
KubernetesCatalogWatch watch = createWatcherInSpecificNamespacesWithLabels(Set.of("namespaceA"),
242+
Map.of("color", "blue"), ENDPOINT_SLICES);
243+
244+
endpointsWithoutSubsets("namespaceA", Map.of("color", "blue"), "podA");
245+
// we do not fail here, even if Subsets are not present
246+
invokeAndAssert(watch, List.of());
247+
}
248+
238249
// work-around for : https://github.com/fabric8io/kubernetes-client/issues/4649
239250
static KubernetesClient endpointsMockClient() {
240251
return mockClient;

0 commit comments

Comments
 (0)