From cd24fda5bc1d19243ca3ca0e08ad2101d974484f Mon Sep 17 00:00:00 2001 From: erabii Date: Fri, 28 Apr 2023 16:56:19 +0300 Subject: [PATCH] Fix 1323 part 1 (#1327) --- .../reload/ConfigurationUpdateStrategy.java | 7 ++ ...ConfigUpdateStrategyAutoConfiguration.java | 45 +++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../watcher/ActuatorRefreshIT.java | 81 ++++++++++++++++++- .../watcher/ActuatorRefreshRabbitMQIT.java | 2 +- 5 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigUpdateStrategyAutoConfiguration.java diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigurationUpdateStrategy.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigurationUpdateStrategy.java index 459ea2e6..f484bae6 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigurationUpdateStrategy.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigurationUpdateStrategy.java @@ -26,6 +26,13 @@ import java.util.Objects; */ public record ConfigurationUpdateStrategy(String name, Runnable reloadProcedure) { + /** + * Strategy that does nothing. + */ + public static final ConfigurationUpdateStrategy NOOP = new ConfigurationUpdateStrategy("no-op", () -> { + + }); + public ConfigurationUpdateStrategy(String name, Runnable reloadProcedure) { this.name = Objects.requireNonNull(name, "name cannot be null"); this.reloadProcedure = Objects.requireNonNull(reloadProcedure, "reloadProcedure cannot be null"); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigUpdateStrategyAutoConfiguration.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigUpdateStrategyAutoConfiguration.java new file mode 100644 index 00000000..765af4b7 --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigUpdateStrategyAutoConfiguration.java @@ -0,0 +1,45 @@ +/* + * 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.configuration.watcher; + +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.log.LogAccessor; + +/** + * @author wind57 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "spring.cloud.kubernetes.reload.enabled", havingValue = "false", matchIfMissing = true) +class ConfigUpdateStrategyAutoConfiguration { + + private static final LogAccessor LOG = new LogAccessor( + LogFactory.getLog(ConfigUpdateStrategyAutoConfiguration.class)); + + @Bean + @ConditionalOnMissingBean + ConfigurationUpdateStrategy noopConfigurationUpdateStrategy() { + LOG.debug(() -> "creating NOOP strategy because reload is disabled"); + return ConfigurationUpdateStrategy.NOOP; + } + +} diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1e9ae2d6..01fb25bc 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherAutoConfiguration +org.springframework.cloud.kubernetes.configuration.watcher.ConfigUpdateStrategyAutoConfiguration diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java index 7bbffb6e..61303368 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java @@ -17,17 +17,23 @@ package org.springframework.cloud.kubernetes.configuration.watcher; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import com.github.tomakehurst.wiremock.client.WireMock; 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.V1Service; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Container; import org.testcontainers.k3s.K3sContainer; import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; @@ -71,13 +77,11 @@ class ActuatorRefreshIT { @BeforeEach void setup() { - configWatcher(Phase.CREATE); util.wiremock(NAMESPACE, "/", Phase.CREATE); } @AfterEach void after() { - configWatcher(Phase.DELETE); util.wiremock(NAMESPACE, "/", Phase.DELETE); } @@ -91,6 +95,8 @@ class ActuatorRefreshIT { // curl :8080/__admin/mappings @Test void testActuatorRefresh() { + configWatcher(Phase.CREATE, false); + WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT, WIREMOCK_PATH); await().timeout(Duration.ofSeconds(60)) .until(() -> WireMock @@ -109,13 +115,68 @@ class ActuatorRefreshIT { WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); util.deleteAndWait(NAMESPACE, configMap, null); + + configWatcher(Phase.DELETE, false); } - private void configWatcher(Phase phase) { + /* + * same test as above, but reload is disabled. + */ + @Test + void testActuatorRefreshReloadDisabled() { + configWatcher(Phase.CREATE, true); + + WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT, WIREMOCK_PATH); + await().timeout(Duration.ofSeconds(60)) + .until(() -> WireMock + .stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh")) + .willReturn(WireMock.aResponse().withBody("{}").withStatus(200))) + .getResponse().wasConfigured()); + + // Create new configmap to trigger controller to signal app to refresh + V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName("service-wiremock") + .addToLabels("spring.cloud.kubernetes.config", "true").endMetadata().addToData("foo", "bar").build(); + util.createAndWait(NAMESPACE, configMap, null); + + // Wait a bit before we verify + await().atMost(Duration.ofSeconds(30)).until( + () -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))).isEmpty()); + + Assertions.assertTrue(logs().contains("creating NOOP strategy because reload is disabled")); + // nothing related to 'ConfigReloadUtil' is present in logs + // this proves that once we disable reload everything still works + Assertions.assertFalse(logs().contains("ConfigReloadUtil")); + + WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); + util.deleteAndWait(NAMESPACE, configMap, null); + + configWatcher(Phase.DELETE, true); + } + + private void configWatcher(Phase phase, boolean disableReload) { V1ConfigMap configMap = (V1ConfigMap) util .yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); V1Deployment deployment = (V1Deployment) util .yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml"); + + List envVars = new ArrayList<>( + Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()) + .orElse(new ArrayList<>())); + + V1EnvVar commonsDebug = new V1EnvVar() + .name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG_RELOAD").value("DEBUG"); + V1EnvVar watcherDebug = new V1EnvVar() + .name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CONFIGURATION_WATCHER").value("DEBUG"); + + envVars.add(commonsDebug); + envVars.add(watcherDebug); + + if (disableReload) { + V1EnvVar disableReloadEnvVar = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_RELOAD_ENABLED").value("FALSE"); + envVars.add(disableReloadEnvVar); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); + } + V1Service service = (V1Service) util .yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); @@ -130,4 +191,18 @@ class ActuatorRefreshIT { } + private String logs() { + try { + String appPodName = K3S.execInContainer("sh", "-c", "kubectl get pods -l app=" + + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME + " -o=name --no-headers | tr -d '\n'").getStdout(); + + Container.ExecResult execResult = K3S.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim()); + return execResult.getStdout(); + } + catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java index 830dc635..9095198a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java @@ -104,7 +104,7 @@ class ActuatorRefreshRabbitMQIT { WebClient serviceClient = builder.baseUrl("http://localhost:80/it").build(); Boolean[] value = new Boolean[1]; - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(180)).until(() -> { value[0] = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class).retryWhen(retrySpec()) .block(); return value[0];