Skip to content

Commit cc4e7fc

Browse files
committed
fix issue 2008
Signed-off-by: wind57 <[email protected]>
1 parent f2a573b commit cc4e7fc

File tree

4 files changed

+223
-10
lines changed

4 files changed

+223
-10
lines changed

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
2828
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
2929
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
30-
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
3130
import org.springframework.core.env.CompositePropertySource;
3231
import org.springframework.core.env.ConfigurableEnvironment;
3332
import org.springframework.core.env.MapPropertySource;
@@ -111,10 +110,6 @@ else if (source instanceof MountConfigMapPropertySource mountConfigMapPropertySo
111110
// we know that the type is correct here
112111
managedSources.add((S) mountConfigMapPropertySource);
113112
}
114-
else if (source instanceof SecretsPropertySource secretsPropertySource) {
115-
// we know that the type is correct here
116-
managedSources.add((S) secretsPropertySource);
117-
}
118113
else if (source instanceof BootstrapPropertySource<?> bootstrapPropertySource) {
119114
PropertySource<?> propertySource = bootstrapPropertySource.getDelegate();
120115
LOG.debug(() -> "bootstrap delegate class : " + propertySource.getClass());
@@ -125,10 +120,6 @@ else if (propertySource instanceof MountConfigMapPropertySource mountConfigMapPr
125120
// we know that the type is correct here
126121
managedSources.add((S) mountConfigMapPropertySource);
127122
}
128-
else if (propertySource instanceof SecretsPropertySource secretsPropertySource) {
129-
// we know that the type is correct here
130-
managedSources.add((S) secretsPropertySource);
131-
}
132123
}
133124
}
134125

spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/example/App.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626
@EnableConfigurationProperties(GreetingProperties.class)
2727
@SpringBootApplication
28-
class App {
28+
public class App {
2929

3030
public static void main(String[] args) {
3131
SpringApplication.run(App.class, args);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Copyright 2012-present 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.config.reload_it;
18+
19+
import java.nio.charset.StandardCharsets;
20+
import java.time.Duration;
21+
import java.util.Base64;
22+
import java.util.Collections;
23+
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
import java.util.stream.Collectors;
27+
28+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
29+
import io.fabric8.kubernetes.api.model.SecretBuilder;
30+
import io.fabric8.kubernetes.client.Config;
31+
import io.fabric8.kubernetes.client.KubernetesClient;
32+
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
33+
import org.awaitility.Awaitility;
34+
import org.junit.jupiter.api.BeforeAll;
35+
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.api.extension.ExtendWith;
37+
38+
import org.springframework.beans.factory.annotation.Autowired;
39+
import org.springframework.boot.test.context.SpringBootTest;
40+
import org.springframework.boot.test.context.TestConfiguration;
41+
import org.springframework.boot.test.system.CapturedOutput;
42+
import org.springframework.boot.test.system.OutputCaptureExtension;
43+
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
44+
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
45+
import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector;
46+
import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource;
47+
import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator;
48+
import org.springframework.cloud.kubernetes.fabric8.config.example.App;
49+
import org.springframework.context.annotation.Bean;
50+
import org.springframework.context.annotation.Primary;
51+
import org.springframework.core.env.AbstractEnvironment;
52+
import org.springframework.core.env.ConfigurableEnvironment;
53+
import org.springframework.core.env.PropertySource;
54+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
55+
56+
import static org.assertj.core.api.Assertions.assertThat;
57+
58+
/**
59+
* Proves that
60+
* <a href="https://github.com/spring-cloud/spring-cloud-kubernetes/issues/2008">this</a>
61+
* issue is fixed.
62+
*
63+
* @author wind57
64+
*/
65+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
66+
properties = { "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.config.enabled=true",
67+
"spring.cloud.bootstrap.name=polling-reload-configmap-and-secret",
68+
"spring.main.cloud-platform=KUBERNETES", "spring.application.name=polling-reload-configmap-and-secret",
69+
"spring.main.allow-bean-definition-overriding=true",
70+
"spring.cloud.kubernetes.client.namespace=spring-k8s",
71+
"logging.level.org.springframework.cloud.kubernetes.commons.config.reload=debug" },
72+
classes = { PollingReloadConfigMapAndSecretTest.TestConfig.class, App.class })
73+
@EnableKubernetesMockClient(crud = true, https = false)
74+
@ExtendWith(OutputCaptureExtension.class)
75+
class PollingReloadConfigMapAndSecretTest {
76+
77+
private static final String NAMESPACE = "spring-k8s";
78+
79+
private static final AtomicBoolean STRATEGY_FOR_SECRET_CALLED = new AtomicBoolean(false);
80+
81+
private static KubernetesClient mockClient;
82+
83+
@Autowired
84+
private ConfigurableEnvironment environment;
85+
86+
@BeforeAll
87+
static void beforeAll() {
88+
// Configure the kubernetes master url to point to the mock server
89+
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
90+
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
91+
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
92+
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
93+
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
94+
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
95+
96+
// namespace: spring-k8s, name: secret-a
97+
Map<String, String> secretA = Collections.singletonMap("one",
98+
Base64.getEncoder().encodeToString("a".getBytes(StandardCharsets.UTF_8)));
99+
createSecret("secret-a", secretA);
100+
101+
// namespace: spring-k8s, name: secret-b
102+
Map<String, String> secretB = Collections.singletonMap("two",
103+
Base64.getEncoder().encodeToString("b".getBytes(StandardCharsets.UTF_8)));
104+
createSecret("secret-b", secretB);
105+
106+
// namespace: spring-k8s, name: configmap-a
107+
Map<String, String> configMapA = Collections.singletonMap("one", "a");
108+
createConfigMap("configmap-a", configMapA);
109+
110+
// namespace: spring-k8s, name: configmap-b
111+
Map<String, String> configMapB = Collections.singletonMap("two", "b");
112+
createConfigMap("configmap-b", configMapB);
113+
114+
}
115+
116+
@Test
117+
void test(CapturedOutput output) {
118+
119+
Set<String> sources = environment.getPropertySources()
120+
.stream()
121+
.map(PropertySource::getName)
122+
.collect(Collectors.toSet());
123+
assertThat(sources).contains("bootstrapProperties-configmap.configmap-b.spring-k8s",
124+
"bootstrapProperties-configmap.configmap-a.spring-k8s",
125+
"bootstrapProperties-secret.secret-b.spring-k8s", "bootstrapProperties-secret.secret-a.spring-k8s");
126+
127+
// 1. first, wait for a cycle where we see the configmaps as being the same
128+
Awaitility.await()
129+
.atMost(Duration.ofSeconds(10))
130+
.pollInterval(Duration.ofSeconds(1))
131+
.until(() -> output.getOut()
132+
.contains("Reloadable condition was not satisfied, reload will not be triggered"));
133+
134+
// 2. then change a configmap, so the cycle seems them as different and triggers a
135+
// reload
136+
Map<String, String> configMapA = Collections.singletonMap("one", "aa");
137+
replaceConfigMap("configmap-a", configMapA);
138+
139+
// 3. reload is triggered
140+
Awaitility.await()
141+
.atMost(Duration.ofSeconds(10))
142+
.pollInterval(Duration.ofSeconds(1))
143+
.until(STRATEGY_FOR_SECRET_CALLED::get);
144+
145+
}
146+
147+
private static void createSecret(String name, Map<String, String> data) {
148+
mockClient.secrets()
149+
.inNamespace(NAMESPACE)
150+
.resource(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build())
151+
.create();
152+
}
153+
154+
private static void createConfigMap(String name, Map<String, String> data) {
155+
mockClient.configMaps()
156+
.inNamespace(NAMESPACE)
157+
.resource(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build())
158+
.create();
159+
}
160+
161+
private static void replaceConfigMap(String name, Map<String, String> data) {
162+
mockClient.configMaps()
163+
.inNamespace(NAMESPACE)
164+
.resource(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build())
165+
.createOrReplace();
166+
}
167+
168+
@TestConfiguration
169+
static class TestConfig {
170+
171+
@Bean
172+
@Primary
173+
PollingConfigMapChangeDetector pollingConfigMapChangeDetector(AbstractEnvironment environment,
174+
ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
175+
Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) {
176+
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
177+
scheduler.initialize();
178+
return new PollingConfigMapChangeDetector(environment, configReloadProperties, configurationUpdateStrategy,
179+
Fabric8ConfigMapPropertySource.class, fabric8ConfigMapPropertySourceLocator, scheduler);
180+
}
181+
182+
@Bean
183+
@Primary
184+
ConfigReloadProperties configReloadProperties() {
185+
return new ConfigReloadProperties(true, true, true, ConfigReloadProperties.ReloadStrategy.REFRESH,
186+
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(200), Set.of(NAMESPACE),
187+
false, Duration.ofSeconds(2));
188+
}
189+
190+
@Bean
191+
@Primary
192+
ConfigurationUpdateStrategy secretConfigurationUpdateStrategy() {
193+
return new ConfigurationUpdateStrategy("to-console", () -> STRATEGY_FOR_SECRET_CALLED.set(true));
194+
}
195+
196+
}
197+
198+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
spring:
2+
application:
3+
name: polling-reload-configmap-and-secret
4+
cloud:
5+
kubernetes:
6+
reload:
7+
enabled: true
8+
monitoring-config-maps: true
9+
monitoring-secrets: true
10+
mode: polling
11+
config:
12+
namespace: spring-k8s
13+
sources:
14+
- name: configmap-a
15+
- name: configmap-b
16+
enable-api: true
17+
include-profile-specific-sources: false
18+
secrets:
19+
namespace: spring-k8s
20+
sources:
21+
- name: secret-a
22+
- name: secret-b
23+
enable-api: true
24+
include-profile-specific-sources: false

0 commit comments

Comments
 (0)