Skip to content

Commit 1413504

Browse files
committed
2 parents 71b8259 + 574c0ac commit 1413504

File tree

14 files changed

+251
-52
lines changed

14 files changed

+251
-52
lines changed

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
# if: github.repository_owner == 'spring-cloud'
1717
steps:
1818
- name: Checkout
19-
uses: actions/checkout@v4
19+
uses: actions/checkout@v5
2020
with:
2121
ref: docs-build
2222
fetch-depth: 1

.github/workflows/maven.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
steps:
2828

2929
- name: checkout project
30-
uses: actions/checkout@v4
30+
uses: actions/checkout@v5
3131

3232
- name: set env variables
3333
uses: ./.github/workflows/composites/env-variables
@@ -103,7 +103,7 @@ jobs:
103103
steps:
104104

105105
- name: checkout project
106-
uses: actions/checkout@v4
106+
uses: actions/checkout@v5
107107

108108
- name: clean space
109109
uses: ./.github/workflows/composites/clean-space
@@ -147,7 +147,7 @@ jobs:
147147
steps:
148148

149149
- name: checkout project
150-
uses: actions/checkout@v4
150+
uses: actions/checkout@v5
151151

152152
- name: clean space
153153
uses: ./.github/workflows/composites/clean-space
@@ -184,7 +184,7 @@ jobs:
184184
steps:
185185

186186
- name: checkout project
187-
uses: actions/checkout@v4
187+
uses: actions/checkout@v5
188188

189189
- name: compute and save running time of tests
190190
uses: ./.github/workflows/composites/test-times
@@ -196,7 +196,7 @@ jobs:
196196
steps:
197197

198198
- name: checkout project
199-
uses: actions/checkout@v4
199+
uses: actions/checkout@v5
200200

201201
- name: compute and save running time of tests
202202
uses: ./.github/workflows/composites/test-times

spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesEndpointSlicesCatalogWatch.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.Objects;
2324
import java.util.function.Function;
2425
import java.util.stream.Stream;
2526

@@ -73,13 +74,18 @@ else if (!context.properties().namespaces().isEmpty()) {
7374
endpointSlices = namespacedEndpointSlices(api, namespace, context.properties().serviceLabels());
7475
}
7576

77+
return generateState(endpointSlices);
78+
79+
}
80+
81+
List<EndpointNameAndNamespace> generateState(List<V1EndpointSlice> endpointSlices) {
7682
Stream<V1ObjectReference> references = endpointSlices.stream()
7783
.map(V1EndpointSlice::getEndpoints)
84+
.filter(Objects::nonNull)
7885
.flatMap(List::stream)
7986
.map(V1Endpoint::getTargetRef);
8087

8188
return KubernetesCatalogWatchContext.state(references);
82-
8389
}
8490

8591
private List<V1EndpointSlice> endpointSlices(DiscoveryV1Api api, Map<String, String> labels) {

spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesEndpointsCatalogWatch.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,21 @@ else if (!context.properties().namespaces().isEmpty()) {
7272
endpoints = namespacedEndpoints(coreV1Api, namespace, context.properties().serviceLabels());
7373
}
7474

75-
/**
76-
* <pre>
77-
* - An "V1Endpoints" holds a List of V1EndpointSubset.
78-
* - A single V1EndpointSubset holds a List of V1EndpointAddress
79-
*
80-
* - (The union of all V1EndpointSubsets is the Set of all V1Endpoints)
81-
* - Set of V1Endpoints is the cartesian product of :
82-
* V1EndpointSubset::getAddresses and V1EndpointSubset::getPorts (each is a List)
83-
* </pre>
84-
*/
75+
return generateState(endpoints);
76+
77+
}
78+
79+
/**
80+
* <pre>
81+
* - An "V1Endpoints" holds a List of V1EndpointSubset.
82+
* - A single V1EndpointSubset holds a List of V1EndpointAddress
83+
*
84+
* - (The union of all V1EndpointSubsets is the Set of all V1Endpoints)
85+
* - Set of V1Endpoints is the cartesian product of :
86+
* V1EndpointSubset::getAddresses and V1EndpointSubset::getPorts (each is a List)
87+
* </pre>
88+
*/
89+
List<EndpointNameAndNamespace> generateState(List<V1Endpoints> endpoints) {
8590
Stream<V1ObjectReference> references = endpoints.stream()
8691
.map(V1Endpoints::getSubsets)
8792
.filter(Objects::nonNull)
@@ -92,7 +97,6 @@ else if (!context.properties().namespaces().isEmpty()) {
9297
.map(V1EndpointAddress::getTargetRef);
9398

9499
return KubernetesCatalogWatchContext.state(references);
95-
96100
}
97101

98102
private List<V1Endpoints> endpoints(CoreV1Api client, Map<String, String> labels) {

spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchEndpointSlicesTests.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.github.tomakehurst.wiremock.client.WireMock;
2626
import io.kubernetes.client.openapi.ApiClient;
2727
import io.kubernetes.client.openapi.JSON;
28+
import io.kubernetes.client.openapi.models.V1EndpointSlice;
29+
import io.kubernetes.client.openapi.models.V1EndpointSliceList;
2830
import io.kubernetes.client.util.ClientBuilder;
2931
import org.junit.jupiter.api.AfterAll;
3032
import org.junit.jupiter.api.AfterEach;
@@ -38,6 +40,7 @@
3840
import static com.github.tomakehurst.wiremock.client.WireMock.get;
3941
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
4042
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
43+
import static org.assertj.core.api.Assertions.assertThat;
4144

4245
/**
4346
* Test cases for the Endpoint Slices support
@@ -191,4 +194,36 @@ void testInOneNamespaceWithDoubleLabel() {
191194
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("a", "b")));
192195
}
193196

197+
@Test
198+
@Override
199+
void testWithoutSubsetsOrEndpoints() {
200+
201+
// even though we set Endpoints here to null, when
202+
// deserializing, it will be an empty List.
203+
// I'm going to leave the test here in case the client changes in this regard.
204+
// 'generateStateEndpointsWithoutEndpoints' test covers the null Subsets anyway
205+
V1EndpointSliceList endpointSlices = endpointSlicesNoEndpoints();
206+
207+
stubFor(get("/apis/discovery.k8s.io/v1/namespaces/b/endpointslices?labelSelector=key%3Dvalue%2Ckey1%3Dvalue1")
208+
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(endpointSlices))));
209+
// otherwise the stub might fail
210+
LinkedHashMap<String, String> map = new LinkedHashMap<>();
211+
map.put("key", "value");
212+
map.put("key1", "value1");
213+
KubernetesCatalogWatch watch = createWatcherInSpecificNamespaceWithLabels("b", map, null, apiClient,
214+
USE_ENDPOINT_SLICES);
215+
216+
invokeAndAssert(watch, List.of());
217+
}
218+
219+
@Test
220+
void generateStateEndpointsWithoutEndpoints() {
221+
222+
KubernetesEndpointSlicesCatalogWatch catalogWatch = new KubernetesEndpointSlicesCatalogWatch();
223+
List<V1EndpointSlice> endpointSlicesNoEndpoints = endpointSlicesNoEndpoints().getItems();
224+
225+
// even if Endpoints are missing, we do not fail
226+
assertThat(catalogWatch.generateState(endpointSlicesNoEndpoints)).isEmpty();
227+
}
228+
194229
}

spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchEndpointsTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.github.tomakehurst.wiremock.client.WireMock;
2626
import io.kubernetes.client.openapi.JSON;
2727
import io.kubernetes.client.openapi.apis.CoreV1Api;
28+
import io.kubernetes.client.openapi.models.V1Endpoints;
2829
import io.kubernetes.client.util.ClientBuilder;
2930
import org.junit.jupiter.api.AfterAll;
3031
import org.junit.jupiter.api.AfterEach;
@@ -38,6 +39,7 @@
3839
import static com.github.tomakehurst.wiremock.client.WireMock.get;
3940
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
4041
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
42+
import static org.assertj.core.api.Assertions.assertThat;
4143

4244
/**
4345
* Test cases for the Endpoints support
@@ -191,4 +193,30 @@ void testInOneNamespaceWithDoubleLabel() {
191193
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("a", "b")));
192194
}
193195

196+
@Test
197+
@Override
198+
void testWithoutSubsetsOrEndpoints() {
199+
stubFor(get("/api/v1/namespaces/b/endpoints?labelSelector=key%3Dvalue%2Ckey1%3Dvalue1")
200+
.willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(endpointsNoSubsets()))));
201+
// otherwise the stub might fail
202+
LinkedHashMap<String, String> map = new LinkedHashMap<>();
203+
map.put("key", "value");
204+
map.put("key1", "value1");
205+
KubernetesCatalogWatch watch = createWatcherInSpecificNamespaceWithLabels("b", map, coreV1Api, null,
206+
USE_ENDPOINT_SLICES);
207+
208+
invokeAndAssert(watch, List.of());
209+
}
210+
211+
@Test
212+
void generateStateEndpointsWithoutSubsets() {
213+
214+
KubernetesEndpointsCatalogWatch catalogWatch = new KubernetesEndpointsCatalogWatch();
215+
List<V1Endpoints> endpoints = endpointsNoSubsets().getItems();
216+
217+
// we do not fail, even if Subsets are not present
218+
List<EndpointNameAndNamespace> result = catalogWatch.generateState(endpoints);
219+
assertThat(result).isEmpty();
220+
}
221+
194222
}

spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesEndpointsAndEndpointSlicesTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import io.kubernetes.client.openapi.ApiClient;
2424
import io.kubernetes.client.openapi.apis.CoreV1Api;
25+
import io.kubernetes.client.openapi.models.V1Endpoint;
2526
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
2627
import io.kubernetes.client.openapi.models.V1EndpointBuilder;
2728
import io.kubernetes.client.openapi.models.V1EndpointSliceBuilder;
@@ -104,6 +105,8 @@ abstract class KubernetesEndpointsAndEndpointSlicesTests {
104105
*/
105106
abstract void testInOneNamespaceWithDoubleLabel();
106107

108+
abstract void testWithoutSubsetsOrEndpoints();
109+
107110
KubernetesCatalogWatch createWatcherInAllNamespacesWithLabels(Map<String, String> labels, Set<String> namespaces,
108111
CoreV1Api coreV1Api, ApiClient apiClient, boolean endpointSlices) {
109112

@@ -174,6 +177,12 @@ V1EndpointsList endpoints(String name, String namespace) {
174177
.build();
175178
}
176179

180+
V1EndpointsList endpointsNoSubsets() {
181+
return new V1EndpointsListBuilder().addToItems(new V1EndpointsBuilder().build())
182+
.withKind("EndpointsList")
183+
.build();
184+
}
185+
177186
V1EndpointSliceList endpointSlices(String name, String namespace) {
178187
return new V1EndpointSliceListBuilder()
179188
.addToItems(new V1EndpointSliceBuilder().addToEndpoints(new V1EndpointBuilder()
@@ -182,6 +191,13 @@ V1EndpointSliceList endpointSlices(String name, String namespace) {
182191
.build();
183192
}
184193

194+
V1EndpointSliceList endpointSlicesNoEndpoints() {
195+
List<V1Endpoint> endpoints = null;
196+
return new V1EndpointSliceListBuilder().withKind("V1EndpointSliceList")
197+
.addToItems(new V1EndpointSliceBuilder().withEndpoints(endpoints).build())
198+
.build();
199+
}
200+
185201
static void invokeAndAssert(KubernetesCatalogWatch watch, List<EndpointNameAndNamespace> state) {
186202
watch.catalogServicesWatch();
187203

spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ProfileActivationAwareYamlPropertiesFactoryBean.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,35 +50,38 @@
5050
* care to override profile-based collections and maps.
5151
*
5252
* We can't use the same functionality of loading yaml files that spring-boot does :
53-
* {@link org.springframework.boot.env.YamlPropertySourceLoader} and thus OriginTrackedYamlLoader,
54-
* because spring-boot loads every single yaml document (all in a file) into a separate PropertySource.
55-
* So each yaml document ends up in a separate PropertySource. We, on the other hand, have to load all yaml documents
56-
* into a single Properties file, that ends up being a single PropertySource.
57-
* This happens because we first have to read configmaps / secrets
58-
* and only at that point do we know if a yaml contains more than one document.
53+
* {@link org.springframework.boot.env.YamlPropertySourceLoader} and thus
54+
* OriginTrackedYamlLoader, because spring-boot loads every single yaml document (all in a
55+
* file) into a separate PropertySource. So each yaml document ends up in a separate
56+
* PropertySource. We, on the other hand, have to load all yaml documents into a single
57+
* Properties file, that ends up being a single PropertySource. This happens because we
58+
* first have to read configmaps / secrets and only at that point do we know if a yaml
59+
* contains more than one document.
5960
*
60-
* As such, we mimic the same things that spring-boot achieves by creating our own yaml reader, that is neavily based
61-
* on the YamlPropertiesFactoryBean.
61+
* As such, we mimic the same things that spring-boot achieves by creating our own yaml
62+
* reader, that is neavily based on the YamlPropertiesFactoryBean.
6263
*
6364
* This is how it does things:
6465
*
6566
* <ul>
66-
* <li>read all the documents in a yaml file</li>
67-
* <li>flatten all properties besides collection and maps,
68-
* YamlPropertiesFactoryBean does not do that and starts flattening everything</li>
69-
* <li>take only those that match the document matchers</li>
70-
* <li>split them in two : those that have profile activation and those that don't</li>
71-
* <li>override properties in the non-profile based yamls with the ones from profile based ones.
72-
* This achieves the same result as a plain spring-boot app, where profile based properties have a higher
73-
* precedence.</li>
74-
* <li>once the overriding happened, we do another flattening, this time including collection and maps</li>
67+
* <li>read all the documents in a yaml file</li>
68+
* <li>flatten all properties besides collection and maps, YamlPropertiesFactoryBean does
69+
* not do that and starts flattening everything</li>
70+
* <li>take only those that match the document matchers</li>
71+
* <li>split them in two : those that have profile activation and those that don't</li>
72+
* <li>override properties in the non-profile based yamls with the ones from profile based
73+
* ones. This achieves the same result as a plain spring-boot app, where profile based
74+
* properties have a higher precedence.</li>
75+
* <li>once the overriding happened, we do another flattening, this time including
76+
* collection and maps</li>
7577
* </ul>
7678
*
7779
* @author wind57
7880
*/
7981
final class ProfileActivationAwareYamlPropertiesFactoryBean {
8082

81-
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(ProfileActivationAwareYamlPropertiesFactoryBean.class));
83+
private static final LogAccessor LOG = new LogAccessor(
84+
LogFactory.getLog(ProfileActivationAwareYamlPropertiesFactoryBean.class));
8285

8386
private List<DocumentMatcher> documentMatchers = Collections.emptyList();
8487

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 generateState(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> generateState(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 generateState(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> generateState(List<Endpoints> endpoints) {
5764
Stream<ObjectReference> references = endpoints.stream()
5865
.map(Endpoints::getSubsets)
5966
.filter(Objects::nonNull)

0 commit comments

Comments
 (0)