Skip to content

Commit 691c2ac

Browse files
committed
dirty
1 parent 0c00b75 commit 691c2ac

File tree

7 files changed

+248
-25
lines changed

7 files changed

+248
-25
lines changed

spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientBootstrapConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
3232
import org.springframework.cloud.kubernetes.commons.config.KubernetesBootstrapConfiguration;
3333
import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;
34+
import org.springframework.cloud.util.ConditionalOnBootstrapEnabled;
3435
import org.springframework.context.annotation.Bean;
3536
import org.springframework.context.annotation.Configuration;
3637
import org.springframework.context.annotation.Import;
@@ -41,6 +42,7 @@
4142
@Configuration(proxyBeanMethods = false)
4243
@AutoConfigureAfter(KubernetesBootstrapConfiguration.class)
4344
@Import({ KubernetesCommonsAutoConfiguration.class, KubernetesClientAutoConfiguration.class })
45+
@ConditionalOnBootstrapEnabled
4446
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
4547
public class KubernetesClientBootstrapConfiguration {
4648

spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadConfigMapTest.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,10 @@
6262
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
6363

6464
/**
65-
* set 'spring.cloud.kubernetes.reload.enabled=false' so that auto-configuration does not
66-
* kick in, as we create our own config for the test here.
67-
*
6865
* @author wind57
6966
*/
7067
@SpringBootTest(
71-
properties = { "spring.main.cloud-platform=kubernetes", "spring.main.allow-bean-definition-overriding=true",
72-
"spring.cloud.kubernetes.reload.enabled=false",
68+
properties = {"spring.main.allow-bean-definition-overriding=true",
7369
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
7470
classes = { PollingReloadConfigMapTest.TestConfig.class })
7571
@ExtendWith(OutputCaptureExtension.class)
@@ -108,9 +104,11 @@ static void setup() {
108104
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listOne)))
109105
.inScenario("my-test").willSetStateTo("go-to-fail"));
110106

107+
// first reload call fails
111108
stubFor(get(path).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))
112109
.inScenario("my-test").whenScenarioStateIs("go-to-fail").willSetStateTo("go-to-ok"));
113110

111+
// second reload call passes
114112
V1ConfigMap configMapTwo = configMap(CONFIG_MAP_NAME, Map.of("a", "b"));
115113
V1ConfigMapList listTwo = new V1ConfigMapList().addItemsItem(configMapTwo);
116114
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listTwo)))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* Copyright 2013-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.client.config.reload_it;
18+
19+
import java.time.Duration;
20+
import java.util.Base64;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.stream.Collector;
25+
import java.util.stream.Collectors;
26+
27+
import com.github.tomakehurst.wiremock.WireMockServer;
28+
import com.github.tomakehurst.wiremock.client.WireMock;
29+
import io.kubernetes.client.openapi.ApiClient;
30+
import io.kubernetes.client.openapi.Configuration;
31+
import io.kubernetes.client.openapi.JSON;
32+
import io.kubernetes.client.openapi.apis.CoreV1Api;
33+
import io.kubernetes.client.openapi.models.V1ConfigMap;
34+
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
35+
import io.kubernetes.client.openapi.models.V1ConfigMapList;
36+
import io.kubernetes.client.openapi.models.V1Secret;
37+
import io.kubernetes.client.openapi.models.V1SecretBuilder;
38+
import io.kubernetes.client.openapi.models.V1SecretList;
39+
import io.kubernetes.client.util.ClientBuilder;
40+
41+
import org.awaitility.Awaitility;
42+
import org.junit.jupiter.api.AfterAll;
43+
import org.junit.jupiter.api.BeforeAll;
44+
import org.junit.jupiter.api.Test;
45+
import org.junit.jupiter.api.extension.ExtendWith;
46+
import org.springframework.boot.test.context.SpringBootTest;
47+
import org.springframework.boot.test.context.TestConfiguration;
48+
import org.springframework.boot.test.system.CapturedOutput;
49+
import org.springframework.boot.test.system.OutputCaptureExtension;
50+
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource;
51+
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
52+
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
53+
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
54+
import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
55+
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
56+
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
57+
import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector;
58+
import org.springframework.context.annotation.Bean;
59+
import org.springframework.context.annotation.Primary;
60+
import org.springframework.core.env.AbstractEnvironment;
61+
import org.springframework.core.env.PropertySource;
62+
import org.springframework.mock.env.MockEnvironment;
63+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
64+
65+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
66+
import static com.github.tomakehurst.wiremock.client.WireMock.get;
67+
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
68+
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
69+
70+
/**
71+
* @author wind57
72+
*/
73+
@SpringBootTest(
74+
properties = {"spring.main.allow-bean-definition-overriding=true",
75+
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
76+
classes = { PollingReloadConfigMapTest.TestConfig.class })
77+
@ExtendWith(OutputCaptureExtension.class)
78+
class PollingReloadSecretTest {
79+
80+
private static WireMockServer wireMockServer;
81+
82+
private static final boolean FAIL_FAST = false;
83+
84+
private static final String SECRET_NAME = "mine";
85+
86+
private static final String NAMESPACE = "spring-k8s";
87+
88+
private static final boolean[] strategyCalled = new boolean[] { false };
89+
90+
private static CoreV1Api coreV1Api;
91+
92+
@BeforeAll
93+
static void setup() {
94+
wireMockServer = new WireMockServer(options().dynamicPort());
95+
96+
wireMockServer.start();
97+
WireMock.configureFor("localhost", wireMockServer.port());
98+
99+
ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
100+
client.setDebugging(true);
101+
Configuration.setDefaultApiClient(client);
102+
coreV1Api = new CoreV1Api();
103+
104+
String path = "/api/v1/namespaces/spring-k8s/secrets";
105+
V1Secret secretOne = secret(SECRET_NAME, Map.of());
106+
V1SecretList listOne = new V1SecretList().addItemsItem(secretOne);
107+
108+
// needed so that our environment is populated with 'something'
109+
// this call is done in the method that returns the AbstractEnvironment
110+
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listOne)))
111+
.inScenario("my-test").willSetStateTo("go-to-fail"));
112+
113+
// first reload call fails
114+
stubFor(get(path).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))
115+
.inScenario("my-test").whenScenarioStateIs("go-to-fail").willSetStateTo("go-to-ok"));
116+
117+
V1Secret secretTwo = secret(SECRET_NAME, Map.of("a", "b"));
118+
V1SecretList listTwo = new V1SecretList().addItemsItem(secretTwo);
119+
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listTwo)))
120+
.inScenario("my-test").whenScenarioStateIs("go-to-ok"));
121+
122+
}
123+
124+
@AfterAll
125+
static void after() {
126+
wireMockServer.stop();
127+
}
128+
129+
/**
130+
* <pre>
131+
* - we have a PropertySource in the environment
132+
* - first polling cycle tries to read the sources from k8s and fails
133+
* - second polling cycle reads sources from k8s and finds a change
134+
* </pre>
135+
*/
136+
@Test
137+
void test(CapturedOutput output) {
138+
// we fail while reading 'configMapOne'
139+
Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> {
140+
boolean one = output.getOut().contains("failure in reading named sources");
141+
boolean two = output.getOut()
142+
.contains("there was an error while reading config maps/secrets, no reload will happen");
143+
boolean three = output.getOut()
144+
.contains("reloadable condition was not satisfied, reload will not be triggered");
145+
boolean updateStrategyNotCalled = !strategyCalled[0];
146+
return one && two && three && updateStrategyNotCalled;
147+
});
148+
149+
// it passes while reading 'configMapTwo'
150+
Awaitility.await()
151+
.atMost(Duration.ofSeconds(10))
152+
.pollInterval(Duration.ofSeconds(1))
153+
.until(() -> strategyCalled[0]);
154+
}
155+
156+
private static V1Secret secret(String name, Map<String, String> data) {
157+
158+
Map<String, byte[]> encoded = data.entrySet().stream().collect(
159+
Collectors.toMap(e -> e.getKey(), e -> Base64.getEncoder().encode(e.getValue().getBytes()))
160+
);
161+
162+
return new V1SecretBuilder().withNewMetadata().withName(name).endMetadata()
163+
.withData(encoded).build();
164+
}
165+
166+
@TestConfiguration
167+
static class TestConfig {
168+
169+
@Bean
170+
@Primary
171+
PollingConfigMapChangeDetector pollingConfigMapChangeDetector(AbstractEnvironment environment,
172+
ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
173+
KubernetesClientConfigMapPropertySourceLocator kubernetesClientConfigMapPropertySourceLocator) {
174+
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
175+
scheduler.initialize();
176+
return new PollingConfigMapChangeDetector(environment, configReloadProperties, configurationUpdateStrategy,
177+
KubernetesClientConfigMapPropertySource.class, kubernetesClientConfigMapPropertySourceLocator, scheduler);
178+
}
179+
180+
@Bean
181+
@Primary
182+
AbstractEnvironment environment() {
183+
MockEnvironment mockEnvironment = new MockEnvironment();
184+
mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);
185+
186+
// simulate that environment already has a Fabric8ConfigMapPropertySource,
187+
// otherwise we can't properly test reload functionality
188+
ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties(true, List.of(),
189+
List.of(), Map.of(), true, SECRET_NAME, NAMESPACE, false, true, true, RetryProperties.DEFAULT);
190+
KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(mockEnvironment);
191+
192+
PropertySource<?> propertySource = new KubernetesClientConfigMapPropertySourceLocator(coreV1Api,
193+
configMapConfigProperties, namespaceProvider)
194+
.locate(mockEnvironment);
195+
196+
mockEnvironment.getPropertySources().addFirst(propertySource);
197+
return mockEnvironment;
198+
}
199+
200+
@Bean
201+
@Primary
202+
ConfigReloadProperties configReloadProperties() {
203+
return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
204+
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("non-default"),
205+
false, Duration.ofSeconds(2));
206+
}
207+
208+
@Bean
209+
@Primary
210+
ConfigMapConfigProperties configMapConfigProperties() {
211+
return new ConfigMapConfigProperties(true, List.of(), List.of(), Map.of(), true, SECRET_NAME, NAMESPACE,
212+
false, true, FAIL_FAST, RetryProperties.DEFAULT);
213+
}
214+
215+
@Bean
216+
@Primary
217+
KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
218+
return new KubernetesNamespaceProvider(environment);
219+
}
220+
221+
@Bean
222+
@Primary
223+
ConfigurationUpdateStrategy configurationUpdateStrategy() {
224+
return new ConfigurationUpdateStrategy("to-console", () -> {
225+
strategyCalled[0] = true;
226+
});
227+
}
228+
229+
@Bean
230+
@Primary
231+
KubernetesClientConfigMapPropertySourceLocator kubernetesClientConfigMapPropertySourceLocator(
232+
ConfigMapConfigProperties configMapConfigProperties, KubernetesNamespaceProvider namespaceProvider) {
233+
return new KubernetesClientConfigMapPropertySourceLocator(coreV1Api, configMapConfigProperties,
234+
namespaceProvider);
235+
}
236+
237+
}
238+
239+
}

spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,10 @@
5454
import org.springframework.mock.env.MockEnvironment;
5555

5656
/**
57-
* set 'spring.cloud.kubernetes.reload.enabled=false' so that auto-configuration does not
58-
* kick in, as we create our own config for the test here.
59-
*
6057
* @author wind57
6158
*/
6259
@SpringBootTest(
63-
properties = { "spring.main.cloud-platform=kubernetes", "spring.main.allow-bean-definition-overriding=true",
64-
"spring.cloud.kubernetes.reload.enabled=false",
60+
properties = { "spring.main.allow-bean-definition-overriding=true",
6561
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
6662
classes = { EventReloadConfigMapTest.TestConfig.class })
6763
@EnableKubernetesMockClient(crud = true)

spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,10 @@
5656
import org.springframework.mock.env.MockEnvironment;
5757

5858
/**
59-
* set 'spring.cloud.kubernetes.reload.enabled=false' so that auto-configuration does not
60-
* kick in, as we create our own config for the test here.
61-
*
6259
* @author wind57
6360
*/
6461
@SpringBootTest(
65-
properties = { "spring.main.cloud-platform=kubernetes", "spring.main.allow-bean-definition-overriding=true",
66-
"spring.cloud.kubernetes.reload.enabled=false",
62+
properties = { "spring.main.allow-bean-definition-overriding=true",
6763
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
6864
classes = { EventReloadSecretTest.TestConfig.class })
6965
@EnableKubernetesMockClient(crud = true)

spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,10 @@
5353
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
5454

5555
/**
56-
* set 'spring.cloud.kubernetes.reload.enabled=false' so that auto-configuration does not
57-
* kick in, as we create our own config for the test here.
58-
*
5956
* @author wind57
6057
*/
6158
@SpringBootTest(
62-
properties = { "spring.main.cloud-platform=kubernetes", "spring.main.allow-bean-definition-overriding=true",
63-
"spring.cloud.kubernetes.reload.enabled=false",
59+
properties = { "spring.main.allow-bean-definition-overriding=true",
6460
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
6561
classes = { PollingReloadConfigMapTest.TestConfig.class })
6662
@EnableKubernetesMockClient

spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,10 @@
5555
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
5656

5757
/**
58-
* set 'spring.cloud.kubernetes.reload.enabled=false' so that auto-configuration does not
59-
* kick in, as we create our own config for the test here.
60-
*
6158
* @author wind57
6259
*/
6360
@SpringBootTest(
64-
properties = { "spring.main.cloud-platform=kubernetes", "spring.main.allow-bean-definition-overriding=true",
65-
"spring.cloud.kubernetes.reload.enabled=false",
61+
properties = { "spring.main.allow-bean-definition-overriding=true",
6662
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
6763
classes = { PollingReloadSecretTest.TestConfig.class })
6864
@EnableKubernetesMockClient

0 commit comments

Comments
 (0)