-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Refactor k8s client configuration watcher #1849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1d5f46d
d1e555f
5c0be41
58029b8
d5859ff
1e3fabd
e0986b3
6d932b7
3695218
7bc17c7
25befe2
38037c7
870ae5a
247c275
e4c48b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,8 +22,8 @@ | |
| import io.fabric8.kubernetes.api.model.Service; | ||
| import io.fabric8.kubernetes.api.model.apps.Deployment; | ||
| import io.fabric8.kubernetes.client.utils.Serialization; | ||
| import org.assertj.core.api.Assertions; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. using assertj assertions |
||
| import org.junit.jupiter.api.AfterAll; | ||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.BeforeAll; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.testcontainers.containers.Container; | ||
|
|
@@ -106,7 +106,7 @@ void test() { | |
| .block(); | ||
|
|
||
| // istio profile is present | ||
| Assertions.assertTrue(result.contains("istio")); | ||
| Assertions.assertThat(result).contains("istio"); | ||
| } | ||
|
|
||
| private static void appManifests(Phase phase) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * Copyright 2013-2020 the original author or authors. | ||
| * Copyright 2013-2025 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. | ||
|
|
@@ -16,48 +16,38 @@ | |
|
|
||
| package org.springframework.cloud.kubernetes.configuration.watcher; | ||
|
|
||
| import java.net.SocketTimeoutException; | ||
| import java.time.Duration; | ||
| import java.util.List; | ||
|
|
||
| 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; | ||
| import org.springframework.cloud.kubernetes.integration.tests.commons.Images; | ||
| import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; | ||
| import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util; | ||
|
|
||
| import static org.awaitility.Awaitility.await; | ||
| import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME; | ||
| import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.configureWireMock; | ||
| import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.createConfigMap; | ||
| import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.deleteConfigMap; | ||
| import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.verifyActuatorCalled; | ||
|
|
||
| /** | ||
| * @author Ryan Baxter | ||
| */ | ||
| class ActuatorRefreshIT { | ||
|
|
||
| private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher"; | ||
|
|
||
| private static final String WIREMOCK_HOST = "localhost"; | ||
|
|
||
| private static final String WIREMOCK_PATH = "/"; | ||
|
|
||
| private static final int WIREMOCK_PORT = 80; | ||
|
|
||
| private static final String NAMESPACE = "default"; | ||
|
|
||
| private static final String DOCKER_IMAGE = "docker.io/springcloud/" + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME + ":" | ||
| + Commons.pomVersion(); | ||
|
|
||
| private static final K3sContainer K3S = Commons.container(); | ||
|
|
||
| private static Util util; | ||
|
|
@@ -67,7 +57,6 @@ static void beforeAll() throws Exception { | |
| K3S.start(); | ||
| Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); | ||
| Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); | ||
|
|
||
| Images.loadWiremock(K3S); | ||
|
|
||
| util = new Util(K3S); | ||
|
|
@@ -92,129 +81,45 @@ void after() { | |
| } | ||
|
|
||
| /* | ||
| * this test loads uses two services: wiremock on port 8080 and configuration-watcher | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried to simplify tests a bit here, but did not change the logical behind them at all. I did drop one overlapping tests here though :) |
||
| * on port 8888. we deploy configuration-watcher first and configure it via a | ||
| * configmap with the same name. then, we mock the call to actuator/refresh endpoint | ||
| * and deploy a new configmap: "service-wiremock", this in turn will trigger that | ||
| * This test loads two services: wiremock on port 8080 and configuration-watcher on | ||
| * port 8888. We deploy configuration-watcher first and configure its env variables | ||
| * that we need for this test. Then, we mock the call to actuator/refresh endpoint and | ||
| * deploy a new configmap: "service-wiremock". Because This in turn will trigger a | ||
| * refresh that we capture and assert for. | ||
| */ | ||
| // curl <WIREMOCK_POD_IP>:8080/__admin/mappings | ||
| @Test | ||
| void testActuatorRefresh() { | ||
|
|
||
| WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT); | ||
| await().timeout(Duration.ofSeconds(60)) | ||
| .ignoreException(SocketTimeoutException.class) | ||
| .until(() -> WireMock | ||
| .stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh")) | ||
| .willReturn(WireMock.aResponse().withBody("{}").withStatus(200))) | ||
| .getResponse() | ||
| .wasConfigured()); | ||
|
|
||
| createConfigMap(); | ||
|
|
||
| // Wait a bit before we verify | ||
| await().atMost(Duration.ofSeconds(30)) | ||
| .until(() -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))) | ||
| .isEmpty()); | ||
| WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); | ||
|
|
||
| deleteConfigMap(); | ||
|
|
||
| // the other test | ||
| testActuatorRefreshReloadDisabled(); | ||
|
|
||
| } | ||
|
|
||
| /* | ||
| * same test as above, but reload is disabled. | ||
| */ | ||
| void testActuatorRefreshReloadDisabled() { | ||
|
|
||
| TestUtil.patchForDisabledReload(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, DOCKER_IMAGE); | ||
|
|
||
| WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT); | ||
| await().timeout(Duration.ofSeconds(60)) | ||
| .until(() -> WireMock | ||
| .stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh")) | ||
| .willReturn(WireMock.aResponse().withBody("{}").withStatus(200))) | ||
| .getResponse() | ||
| .wasConfigured()); | ||
|
|
||
| createConfigMap(); | ||
|
|
||
| // Wait a bit before we verify | ||
| await().atMost(Duration.ofSeconds(30)) | ||
| .until(() -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))) | ||
| .isEmpty()); | ||
| configureWireMock(); | ||
| createConfigMap(util, NAMESPACE); | ||
| verifyActuatorCalled(1); | ||
|
|
||
| Commons.waitForLogStatement("creating NOOP strategy because reload is disabled", K3S, | ||
| SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME); | ||
|
|
||
| // 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"))); | ||
|
|
||
| deleteConfigMap(); | ||
|
|
||
| deleteConfigMap(util, NAMESPACE); | ||
| } | ||
|
|
||
| private static void configWatcher(Phase phase) { | ||
| 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-deployment.yaml"); | ||
| V1Service service = (V1Service) util | ||
| .yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); | ||
|
|
||
| List<V1EnvVar> envVars = List.of( | ||
| new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_REFRESHDELAY").value("0"), | ||
| new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_RELOAD_ENABLED").value("FALSE"), | ||
| new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CONFIGURATION_WATCHER") | ||
| .value("DEBUG")); | ||
|
|
||
| deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); | ||
|
|
||
| if (phase.equals(Phase.CREATE)) { | ||
| util.createAndWait(NAMESPACE, configMap, null); | ||
| util.createAndWait(NAMESPACE, null, deployment, service, null, true); | ||
| } | ||
| else { | ||
| util.deleteAndWait(NAMESPACE, configMap, null); | ||
| util.deleteAndWait(NAMESPACE, deployment, service, null); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| // Create new configmap to trigger controller to signal app to refresh | ||
| private void createConfigMap() { | ||
| V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata() | ||
| .withName("service-wiremock") | ||
| .addToLabels("spring.cloud.kubernetes.config", "true") | ||
| .endMetadata() | ||
| .addToData("foo", "bar") | ||
| .build(); | ||
| util.createAndWait(NAMESPACE, configMap, null); | ||
| } | ||
|
|
||
| private void deleteConfigMap() { | ||
| V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata() | ||
| .withName("service-wiremock") | ||
| .addToLabels("spring.cloud.kubernetes.config", "true") | ||
| .endMetadata() | ||
| .addToData("foo", "bar") | ||
| .build(); | ||
| util.deleteAndWait(NAMESPACE, configMap, null); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor upgrade, safe since it is used only in tests