Fix 1317 (#1318)
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.commons;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* {@link Conditional @Conditional} that matches when either or both of
|
||||
* {@link ConditionalOnKubernetesConfigEnabled @ConditionalOnKubernetesConfigEnabled} and
|
||||
* {@link ConditionalOnKubernetesSecretsEnabled @ConditionalOnKubernetesSecretsEnabled}.
|
||||
*
|
||||
* @author wind57
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Conditional(ConditionalOnKubernetesConfigMapsOrSecretsEnabled.OnKubernetesConfigMapsOrSecretesEnabled.class)
|
||||
public @interface ConditionalOnKubernetesConfigMapsOrSecretsEnabled {
|
||||
|
||||
class OnKubernetesConfigMapsOrSecretesEnabled extends AnyNestedCondition {
|
||||
|
||||
OnKubernetesConfigMapsOrSecretesEnabled() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnKubernetesConfigEnabled
|
||||
static class OnConfigMapPropertiesRetryEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnKubernetesSecretsEnabled
|
||||
static class OnSecretsPropertiesRetryEnabled {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,13 +23,15 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfi
|
||||
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
|
||||
import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration;
|
||||
import org.springframework.cloud.commons.util.TaskSchedulerWrapper;
|
||||
import org.springframework.cloud.context.refresh.ContextRefresher;
|
||||
import org.springframework.cloud.context.restart.RestartEndpoint;
|
||||
import org.springframework.cloud.kubernetes.commons.config.ConditionalOnKubernetesAndConfigEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesConfigMapsOrSecretsEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.config.reload.condition.ConditionalOnKubernetesReloadEnabled;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -41,7 +43,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnKubernetesAndConfigEnabled
|
||||
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
|
||||
@ConditionalOnKubernetesConfigMapsOrSecretsEnabled
|
||||
@ConditionalOnKubernetesReloadEnabled
|
||||
@ConditionalOnClass({ EndpointAutoConfiguration.class, RestartEndpoint.class, ContextRefresher.class })
|
||||
@AutoConfigureAfter({ InfoEndpointAutoConfiguration.class, RefreshEndpointAutoConfiguration.class,
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.commons.reload;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
|
||||
import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration;
|
||||
import org.springframework.cloud.commons.util.TaskSchedulerWrapper;
|
||||
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
|
||||
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadPropertiesAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class ConfigReloadAutoConfigurationApplicationContextTests {
|
||||
|
||||
private ApplicationContextRunner applicationContextRunner;
|
||||
|
||||
/**
|
||||
* no special properties provided.
|
||||
*/
|
||||
@Test
|
||||
void testDefault() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true");
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* reload is disabled.
|
||||
*/
|
||||
@Test
|
||||
void testReloadDisabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=false");
|
||||
applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* config maps support is enabled.
|
||||
*/
|
||||
@Test
|
||||
void testConfigMapsSupportEnabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true",
|
||||
"spring.cloud.kubernetes.config.enabled=true");
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* secrets support is enabled.
|
||||
*/
|
||||
@Test
|
||||
void testSecretsSupportEnabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true",
|
||||
"spring.cloud.kubernetes.secrets.enabled=true");
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* config maps and secrets support is enabled.
|
||||
*/
|
||||
@Test
|
||||
void testConfigMapsAndSecretsSupportEnabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true",
|
||||
"spring.cloud.kubernetes.config.enabled=true", "spring.cloud.kubernetes.secrets.enabled=true");
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* config maps and secrets support is disabled.
|
||||
*/
|
||||
@Test
|
||||
void testConfigMapsAndSecretsSupportDisabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true",
|
||||
"spring.cloud.kubernetes.config.enabled=false", "spring.cloud.kubernetes.secrets.enabled=false");
|
||||
applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* config maps support disabled and secrets support is enabled.
|
||||
*/
|
||||
@Test
|
||||
void testConfigMapsDisabledAndSecretsSupportEnabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true",
|
||||
"spring.cloud.kubernetes.config.enabled=false", "spring.cloud.kubernetes.secrets.enabled=true");
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* config maps support enabled and secrets support is disable.
|
||||
*/
|
||||
@Test
|
||||
void testConfigMapsEnabledAndSecretsSupportDisabled() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.reload.enabled=true",
|
||||
"spring.cloud.kubernetes.config.enabled=true", "spring.cloud.kubernetes.secrets.enabled=false");
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(TaskSchedulerWrapper.class));
|
||||
applicationContextRunner.run(context -> assertThat(context).hasSingleBean(ConfigurationUpdateStrategy.class));
|
||||
}
|
||||
|
||||
private void setup(String... properties) {
|
||||
applicationContextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(InfoEndpointAutoConfiguration.class,
|
||||
RefreshEndpointAutoConfiguration.class, ConfigReloadPropertiesAutoConfiguration.class,
|
||||
RefreshAutoConfiguration.class, ConfigReloadAutoConfiguration.class))
|
||||
.withUserConfiguration(RebinderConfig.class).withPropertyValues(properties);
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class RebinderConfig {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesRebinder rebinder() {
|
||||
return Mockito.mock(ConfigurationPropertiesRebinder.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,8 +17,11 @@
|
||||
package org.springframework.cloud.kubernetes.client.configmap.event.reload;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
@@ -28,6 +31,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1ConfigMap;
|
||||
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1EnvVar;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
@@ -94,7 +98,7 @@ class ConfigMapEventReloadIT {
|
||||
*/
|
||||
@Test
|
||||
void testInformFromOneNamespaceEventNotTriggered() throws Exception {
|
||||
manifests("one", Phase.CREATE);
|
||||
manifests("one", Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
@@ -125,7 +129,7 @@ class ConfigMapEventReloadIT {
|
||||
// left configmap has not changed, no restart of app has happened
|
||||
Assertions.assertEquals("left-initial", result);
|
||||
|
||||
manifests("one", Phase.DELETE);
|
||||
manifests("one", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +142,7 @@ class ConfigMapEventReloadIT {
|
||||
*/
|
||||
@Test
|
||||
void testInformFromOneNamespaceEventTriggered() throws Exception {
|
||||
manifests("two", Phase.CREATE);
|
||||
manifests("two", Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
@@ -166,7 +170,7 @@ class ConfigMapEventReloadIT {
|
||||
});
|
||||
Assertions.assertEquals("right-after-change", resultAfterChange[0]);
|
||||
|
||||
manifests("two", Phase.DELETE);
|
||||
manifests("two", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +184,7 @@ class ConfigMapEventReloadIT {
|
||||
*/
|
||||
@Test
|
||||
void testInform() throws Exception {
|
||||
manifests("three", Phase.CREATE);
|
||||
manifests("three", Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
@@ -236,10 +240,51 @@ class ConfigMapEventReloadIT {
|
||||
.block();
|
||||
Assertions.assertEquals("right-after-change", rightResult);
|
||||
|
||||
manifests("three", Phase.DELETE);
|
||||
manifests("three", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
private static void manifests(String deploymentRoot, Phase phase) {
|
||||
/**
|
||||
* <pre>
|
||||
* - there are two namespaces : left and right
|
||||
* - each of the namespaces has one configmap
|
||||
* - we watch the "right" namespace and make a change in the configmap in the same namespace
|
||||
* - as such, event is triggered and we see the updated value
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testInformFromOneNamespaceEventTriggeredSecretsDisabled() throws Exception {
|
||||
manifests("two", Phase.CREATE, true);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
// read the value from the right-configmap
|
||||
WebClient webClient = builder().baseUrl("http://localhost/right").build();
|
||||
String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
Assertions.assertEquals("right-initial", result);
|
||||
|
||||
// then deploy a new version of right-configmap
|
||||
V1ConfigMap rightConfigMapAfterChange = new V1ConfigMapBuilder()
|
||||
.withMetadata(new V1ObjectMeta().namespace("right").name("right-configmap"))
|
||||
.withData(Map.of("right.value", "right-after-change")).build();
|
||||
|
||||
replaceConfigMap(rightConfigMapAfterChange, "right-configmap");
|
||||
|
||||
String[] resultAfterChange = new String[1];
|
||||
await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> {
|
||||
WebClient innerWebClient = builder().baseUrl("http://localhost/right").build();
|
||||
String innerResult = innerWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
resultAfterChange[0] = innerResult;
|
||||
return innerResult != null;
|
||||
});
|
||||
Assertions.assertEquals("right-after-change", resultAfterChange[0]);
|
||||
|
||||
manifests("two", Phase.DELETE, true);
|
||||
}
|
||||
|
||||
private static void manifests(String deploymentRoot, Phase phase, boolean secretsDisabled) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -251,6 +296,16 @@ class ConfigMapEventReloadIT {
|
||||
V1Service service = (V1Service) util.yaml("service.yaml");
|
||||
V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml");
|
||||
|
||||
List<V1EnvVar> envVars = new ArrayList<>(
|
||||
Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv())
|
||||
.orElse(List.of()));
|
||||
|
||||
if (secretsDisabled) {
|
||||
V1EnvVar secretsDisabledEnvVar = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED").value("FALSE");
|
||||
envVars.add(secretsDisabledEnvVar);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
}
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
util.createAndWait("left", leftConfigMap, null);
|
||||
util.createAndWait("right", rightConfigMap, null);
|
||||
|
||||
@@ -17,16 +17,19 @@
|
||||
package org.springframework.cloud.kubernetes.client.secrets.event.reload;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1EnvVar;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Secret;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
@@ -75,17 +78,22 @@ class SecretsEventReloadIT {
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() {
|
||||
configK8sClientIt(Phase.DELETE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSecretReload() throws Exception {
|
||||
configK8sClientIt(Phase.CREATE);
|
||||
configK8sClientIt(Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added secret informer for namespace",
|
||||
"added configmap informer for namespace", IMAGE_NAME);
|
||||
testSecretEventReload();
|
||||
configK8sClientIt(Phase.DELETE, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSecretReloadConfigDisabled() throws Exception {
|
||||
configK8sClientIt(Phase.CREATE, true);
|
||||
Commons.assertReloadLogStatements("added secret informer for namespace",
|
||||
"added configmap informer for namespace", IMAGE_NAME);
|
||||
testSecretEventReload();
|
||||
configK8sClientIt(Phase.DELETE, true);
|
||||
}
|
||||
|
||||
void testSecretEventReload() throws Exception {
|
||||
@@ -108,12 +116,22 @@ class SecretsEventReloadIT {
|
||||
.retryWhen(retrySpec()).block().equals("after-change"));
|
||||
}
|
||||
|
||||
private void configK8sClientIt(Phase phase) {
|
||||
private void configK8sClientIt(Phase phase, boolean configDisabled) {
|
||||
V1Deployment deployment = (V1Deployment) util.yaml("deployment.yaml");
|
||||
V1Service service = (V1Service) util.yaml("service.yaml");
|
||||
V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml");
|
||||
V1Secret secret = (V1Secret) util.yaml("secret.yaml");
|
||||
|
||||
List<V1EnvVar> envVars = new ArrayList<>(
|
||||
Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv())
|
||||
.orElse(List.of()));
|
||||
|
||||
if (configDisabled) {
|
||||
V1EnvVar disableConfig = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CONFIG_ENABLED").value("FALSE");
|
||||
envVars.add(disableConfig);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
}
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
util.createAndWait(NAMESPACE, null, secret);
|
||||
|
||||
@@ -99,7 +99,7 @@ class ConfigMapEventReloadIT {
|
||||
*/
|
||||
@Test
|
||||
void testInformFromOneNamespaceEventNotTriggered() {
|
||||
manifests("one", Phase.CREATE);
|
||||
manifests("one", Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
@@ -130,7 +130,7 @@ class ConfigMapEventReloadIT {
|
||||
// left configmap has not changed, no restart of app has happened
|
||||
Assertions.assertEquals("left-initial", result);
|
||||
|
||||
manifests("one", Phase.DELETE);
|
||||
manifests("one", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +143,7 @@ class ConfigMapEventReloadIT {
|
||||
*/
|
||||
@Test
|
||||
void testInformFromOneNamespaceEventTriggered() {
|
||||
manifests("two", Phase.CREATE);
|
||||
manifests("two", Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
@@ -170,7 +170,7 @@ class ConfigMapEventReloadIT {
|
||||
});
|
||||
Assertions.assertEquals("right-after-change", resultAfterChange[0]);
|
||||
|
||||
manifests("two", Phase.DELETE);
|
||||
manifests("two", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +184,7 @@ class ConfigMapEventReloadIT {
|
||||
*/
|
||||
@Test
|
||||
void testInform() {
|
||||
manifests("three", Phase.CREATE);
|
||||
manifests("three", Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
@@ -241,10 +241,51 @@ class ConfigMapEventReloadIT {
|
||||
.block();
|
||||
Assertions.assertEquals("right-after-change", rightResult);
|
||||
|
||||
manifests("three", Phase.DELETE);
|
||||
manifests("three", Phase.DELETE, false);
|
||||
}
|
||||
|
||||
private static void manifests(String activeProfile, Phase phase) {
|
||||
/**
|
||||
* <pre>
|
||||
* - there are two namespaces : left and right
|
||||
* - each of the namespaces has one configmap
|
||||
* - secrets are disabled
|
||||
* - we watch the "right" namespace and make a change in the configmap in the same namespace
|
||||
* - as such, event is triggered and we see the updated value
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testInformFromOneNamespaceEventTriggeredSecretsDisabled() {
|
||||
manifests("two", Phase.CREATE, true);
|
||||
Commons.assertReloadLogStatements("added configmap informer for namespace",
|
||||
"added secret informer for namespace", IMAGE_NAME);
|
||||
|
||||
// read the value from the right-configmap
|
||||
WebClient webClient = builder().baseUrl("http://localhost/right").build();
|
||||
String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
Assertions.assertEquals("right-initial", result);
|
||||
|
||||
// then deploy a new version of right-configmap
|
||||
ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder()
|
||||
.withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build())
|
||||
.withData(Map.of("right.value", "right-after-change")).build();
|
||||
|
||||
replaceConfigMap(rightConfigMapAfterChange);
|
||||
|
||||
String[] resultAfterChange = new String[1];
|
||||
await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> {
|
||||
WebClient innerWebClient = builder().baseUrl("http://localhost/right").build();
|
||||
String innerResult = innerWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
resultAfterChange[0] = innerResult;
|
||||
return innerResult != null;
|
||||
});
|
||||
Assertions.assertEquals("right-after-change", resultAfterChange[0]);
|
||||
|
||||
manifests("two", Phase.DELETE, true);
|
||||
}
|
||||
|
||||
private static void manifests(String activeProfile, Phase phase, boolean secretsDisabled) {
|
||||
|
||||
InputStream deploymentStream = util.inputStream("deployment.yaml");
|
||||
InputStream serviceStream = util.inputStream("service.yaml");
|
||||
@@ -261,6 +302,13 @@ class ConfigMapEventReloadIT {
|
||||
.build();
|
||||
envVars.add(activeProfileProperty);
|
||||
|
||||
if (secretsDisabled) {
|
||||
EnvVar secretsDisabledEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED")
|
||||
.withValue("FALSE").build();
|
||||
envVars.add(secretsDisabledEnvVar);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
}
|
||||
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
|
||||
Service service = client.services().load(serviceStream).get();
|
||||
|
||||
@@ -18,10 +18,14 @@ package org.springframework.cloud.kubernetes.fabric8.secrets.event.reload;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.api.model.SecretBuilder;
|
||||
@@ -71,17 +75,16 @@ class SecretsEventsReloadIT {
|
||||
util = new Util(K3S);
|
||||
client = util.client();
|
||||
util.setUp(NAMESPACE);
|
||||
manifests(Phase.CREATE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void after() throws Exception {
|
||||
manifests(Phase.DELETE);
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
void testSimple() {
|
||||
manifests(Phase.CREATE, false);
|
||||
Commons.assertReloadLogStatements("added secret informer for namespace",
|
||||
"added configmap informer for namespace", IMAGE_NAME);
|
||||
WebClient webClient = builder().baseUrl("http://localhost/key").build();
|
||||
@@ -104,9 +107,39 @@ class SecretsEventsReloadIT {
|
||||
await().timeout(Duration.ofSeconds(120)).until(() -> webClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).retryWhen(retrySpec()).block().equals("after-change"));
|
||||
|
||||
manifests(Phase.DELETE, false);
|
||||
}
|
||||
|
||||
private static void manifests(Phase phase) {
|
||||
@Test
|
||||
void testSimpleConfigMapsDisabled() {
|
||||
manifests(Phase.CREATE, true);
|
||||
Commons.assertReloadLogStatements("added secret informer for namespace",
|
||||
"added configmap informer for namespace", IMAGE_NAME);
|
||||
WebClient webClient = builder().baseUrl("http://localhost/key").build();
|
||||
String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
// we first read the initial value from the secret
|
||||
Assertions.assertEquals("initial", result);
|
||||
|
||||
// then deploy a new version of the secret
|
||||
// since we poll and have reload in place, the new property must be visible
|
||||
Secret secret = new SecretBuilder()
|
||||
.withMetadata(new ObjectMetaBuilder().withNamespace("default").withName("event-reload").build())
|
||||
.withData(Map.of("application.properties",
|
||||
Base64.getEncoder().encodeToString("from.properties.key=after-change".getBytes())))
|
||||
.build();
|
||||
|
||||
client.secrets().inNamespace("default").resource(secret).createOrReplace();
|
||||
|
||||
await().timeout(Duration.ofSeconds(120)).until(() -> webClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(String.class).retryWhen(retrySpec()).block().equals("after-change"));
|
||||
|
||||
manifests(Phase.DELETE, true);
|
||||
|
||||
}
|
||||
|
||||
private static void manifests(Phase phase, boolean configMapsDisabled) {
|
||||
|
||||
InputStream deploymentStream = util.inputStream("deployment.yaml");
|
||||
InputStream serviceStream = util.inputStream("service.yaml");
|
||||
@@ -118,6 +151,15 @@ class SecretsEventsReloadIT {
|
||||
Ingress ingress = client.network().v1().ingresses().load(ingressStream).get();
|
||||
Secret secret = client.secrets().load(secretStream).get();
|
||||
|
||||
if (configMapsDisabled) {
|
||||
List<EnvVar> envVars = new ArrayList<>(
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
|
||||
EnvVar configMapsDisabledEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_CONFIG_ENABLED")
|
||||
.withValue("FALSE").build();
|
||||
envVars.add(configMapsDisabledEnvVar);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
}
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
util.createAndWait(NAMESPACE, null, secret);
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
|
||||
Reference in New Issue
Block a user