Skip to content

Commit fd21434

Browse files
authored
Refactor k8s client catalog watcher it (#1836)
* placeholder commit Signed-off-by: wind57 <eugen.rabii@gmail.com> * started work Signed-off-by: wind57 <eugen.rabii@gmail.com> * dirty Signed-off-by: wind57 <eugen.rabii@gmail.com> * refactored Signed-off-by: wind57 <eugen.rabii@gmail.com> --------- Signed-off-by: wind57 <eugen.rabii@gmail.com>
1 parent 1eec4a0 commit fd21434

File tree

8 files changed

+401
-534
lines changed

8 files changed

+401
-534
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.springframework.context.annotation.Bean;
3535
import org.springframework.context.annotation.Primary;
3636

37-
import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchEndpointSlicesFilterIT.TestConfig;
37+
import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchEndpointSlicesIT.TestConfig;
3838
import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.TestAssertions.assertLogStatement;
3939
import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.TestAssertions.invokeAndAssert;
4040

@@ -43,7 +43,7 @@
4343
*/
4444
@SpringBootTest(classes = { KubernetesCatalogWatchAutoConfiguration.class, TestConfig.class, Application.class },
4545
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
46-
class Fabric8CatalogWatchEndpointSlicesFilterIT extends Fabric8CatalogWatchBase {
46+
class Fabric8CatalogWatchEndpointSlicesIT extends Fabric8CatalogWatchBase {
4747

4848
@LocalServerPort
4949
private int port;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2012-2024 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.k8s.client.catalog.watcher;
18+
19+
import java.io.IOException;
20+
import java.io.StringReader;
21+
import java.util.Map;
22+
import java.util.Set;
23+
24+
import io.kubernetes.client.openapi.ApiClient;
25+
import io.kubernetes.client.openapi.apis.CoreV1Api;
26+
import io.kubernetes.client.util.Config;
27+
import org.junit.jupiter.api.BeforeAll;
28+
import org.junit.jupiter.api.extension.ExtendWith;
29+
import org.testcontainers.k3s.K3sContainer;
30+
31+
import org.springframework.boot.test.system.OutputCaptureExtension;
32+
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
33+
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
34+
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
35+
import org.springframework.test.context.TestPropertySource;
36+
37+
/**
38+
* @author wind57
39+
*/
40+
41+
@TestPropertySource(
42+
properties = { "spring.main.cloud-platform=kubernetes", "spring.cloud.config.import-check.enabled=false",
43+
"spring.cloud.kubernetes.discovery.catalogServicesWatchDelay=2000",
44+
"spring.cloud.kubernetes.client.namespace=default",
45+
"logging.level.org.springframework.cloud.kubernetes.client.discovery.catalog=DEBUG" })
46+
@ExtendWith(OutputCaptureExtension.class)
47+
abstract class KubernetesClientCatalogWatchBase {
48+
49+
protected static final String NAMESPACE = "default";
50+
51+
protected static final String NAMESPACE_A = "a";
52+
53+
protected static final String NAMESPACE_B = "b";
54+
55+
protected static final K3sContainer K3S = Commons.container();
56+
57+
protected static Util util;
58+
59+
@BeforeAll
60+
protected static void beforeAll() {
61+
K3S.start();
62+
util = new Util(K3S);
63+
}
64+
65+
protected static KubernetesDiscoveryProperties discoveryProperties(boolean useEndpointSlices,
66+
Set<String> namespaces) {
67+
return new KubernetesDiscoveryProperties(true, false, namespaces, true, 60, false, null, Set.of(443, 8443),
68+
Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, useEndpointSlices, false, null);
69+
}
70+
71+
protected static ApiClient apiClient() {
72+
String kubeConfigYaml = K3S.getKubeConfigYaml();
73+
74+
ApiClient client;
75+
try {
76+
client = Config.fromConfig(new StringReader(kubeConfigYaml));
77+
}
78+
catch (IOException e) {
79+
throw new RuntimeException(e);
80+
}
81+
return new CoreV1Api(client).getApiClient();
82+
}
83+
84+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.k8s.client.catalog.watcher;
18+
19+
import java.util.Set;
20+
21+
import io.kubernetes.client.openapi.ApiClient;
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.boot.test.context.SpringBootTest;
27+
import org.springframework.boot.test.context.TestConfiguration;
28+
import org.springframework.boot.test.system.CapturedOutput;
29+
import org.springframework.boot.test.web.server.LocalServerPort;
30+
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
31+
import org.springframework.cloud.kubernetes.integration.tests.commons.Images;
32+
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Primary;
35+
36+
import static org.springframework.cloud.kubernetes.k8s.client.catalog.watcher.TestAssertions.assertLogStatement;
37+
import static org.springframework.cloud.kubernetes.k8s.client.catalog.watcher.TestAssertions.invokeAndAssert;
38+
39+
@SpringBootTest(classes = { KubernetesClientCatalogWatchEndpointSlicesIT.TestConfig.class, Application.class },
40+
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
41+
class KubernetesClientCatalogWatchEndpointSlicesIT extends KubernetesClientCatalogWatchBase {
42+
43+
@LocalServerPort
44+
private int port;
45+
46+
@BeforeEach
47+
void beforeEach() {
48+
49+
util.createNamespace(NAMESPACE_A);
50+
util.createNamespace(NAMESPACE_B);
51+
52+
Images.loadBusybox(K3S);
53+
54+
util.busybox(NAMESPACE_A, Phase.CREATE);
55+
util.busybox(NAMESPACE_B, Phase.CREATE);
56+
57+
}
58+
59+
@AfterEach
60+
void afterEach() {
61+
// busybox is deleted as part of the assertions, thus not seen here
62+
util.deleteNamespace(NAMESPACE_A);
63+
util.deleteNamespace(NAMESPACE_B);
64+
}
65+
66+
/**
67+
* <pre>
68+
* - we deploy a busybox service with 2 replica pods in two namespaces : a, b
69+
* - we use endpoints
70+
* - we enable namespace filtering for 'default' and 'a'
71+
* - we receive an event from KubernetesCatalogWatcher, assert what is inside it
72+
* - delete the busybox service in 'a' and 'b'
73+
* - assert that we receive an empty response
74+
* </pre>
75+
*/
76+
@Test
77+
void testCatalogWatchWithEndpoints(CapturedOutput output) {
78+
assertLogStatement(output, "stateGenerator is of type: KubernetesEndpointSlicesCatalogWatch");
79+
invokeAndAssert(util, Set.of(NAMESPACE_A, NAMESPACE_B), port, NAMESPACE_A);
80+
}
81+
82+
@TestConfiguration
83+
static class TestConfig {
84+
85+
@Bean
86+
@Primary
87+
ApiClient client() {
88+
return apiClient();
89+
}
90+
91+
@Bean
92+
@Primary
93+
KubernetesDiscoveryProperties kubernetesDiscoveryProperties() {
94+
return discoveryProperties(true, Set.of(NAMESPACE, NAMESPACE_A));
95+
}
96+
97+
}
98+
99+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.k8s.client.catalog.watcher;
18+
19+
import java.util.Set;
20+
21+
import io.kubernetes.client.openapi.ApiClient;
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.boot.test.context.SpringBootTest;
27+
import org.springframework.boot.test.context.TestConfiguration;
28+
import org.springframework.boot.test.system.CapturedOutput;
29+
import org.springframework.boot.test.web.server.LocalServerPort;
30+
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
31+
import org.springframework.cloud.kubernetes.integration.tests.commons.Images;
32+
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Primary;
35+
36+
import static org.springframework.cloud.kubernetes.k8s.client.catalog.watcher.TestAssertions.assertLogStatement;
37+
import static org.springframework.cloud.kubernetes.k8s.client.catalog.watcher.TestAssertions.invokeAndAssert;
38+
39+
@SpringBootTest(classes = { KubernetesClientCatalogWatchEndpointsIT.TestConfig.class, Application.class },
40+
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
41+
class KubernetesClientCatalogWatchEndpointsIT extends KubernetesClientCatalogWatchBase {
42+
43+
@LocalServerPort
44+
private int port;
45+
46+
@BeforeEach
47+
void beforeEach() {
48+
49+
util.createNamespace(NAMESPACE_A);
50+
util.createNamespace(NAMESPACE_B);
51+
52+
Images.loadBusybox(K3S);
53+
54+
util.busybox(NAMESPACE_A, Phase.CREATE);
55+
util.busybox(NAMESPACE_B, Phase.CREATE);
56+
57+
}
58+
59+
@AfterEach
60+
void afterEach() {
61+
// busybox is deleted as part of the assertions, thus not seen here
62+
util.deleteNamespace(NAMESPACE_A);
63+
util.deleteNamespace(NAMESPACE_B);
64+
}
65+
66+
/**
67+
* <pre>
68+
* - we deploy a busybox service with 2 replica pods in two namespaces : a, b
69+
* - we use endpoints
70+
* - we enable namespace filtering for 'default' and 'a'
71+
* - we receive an event from KubernetesCatalogWatcher, assert what is inside it
72+
* - delete the busybox service in 'a' and 'b'
73+
* - assert that we receive an empty response
74+
* </pre>
75+
*/
76+
@Test
77+
void testCatalogWatchWithEndpoints(CapturedOutput output) {
78+
assertLogStatement(output, "stateGenerator is of type: KubernetesEndpointsCatalogWatch");
79+
invokeAndAssert(util, Set.of(NAMESPACE_A, NAMESPACE_B), port, NAMESPACE_A);
80+
}
81+
82+
@TestConfiguration
83+
static class TestConfig {
84+
85+
@Bean
86+
@Primary
87+
ApiClient client() {
88+
return apiClient();
89+
}
90+
91+
@Bean
92+
@Primary
93+
KubernetesDiscoveryProperties kubernetesDiscoveryProperties() {
94+
return discoveryProperties(false, Set.of(NAMESPACE, NAMESPACE_A));
95+
}
96+
97+
}
98+
99+
}

0 commit comments

Comments
 (0)