diff --git a/spring-cloud-kubernetes-dependencies/pom.xml b/spring-cloud-kubernetes-dependencies/pom.xml index 43778f0d60..cbde7c83b0 100644 --- a/spring-cloud-kubernetes-dependencies/pom.xml +++ b/spring-cloud-kubernetes-dependencies/pom.xml @@ -34,7 +34,7 @@ 6.9.2 19.0.2 - 3.4.2 + 3.9.1 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/istio/Fabric8IstioIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/istio/Fabric8IstioIT.java index 10f05cf098..6b03f24e46 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/istio/Fabric8IstioIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/istio/Fabric8IstioIT.java @@ -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; 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) { diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml index 4d01f8f512..71a967e49c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/pom.xml @@ -10,10 +10,10 @@ 4.0.0 spring-cloud-kubernetes-k8s-client-configuration-watcher - jar - 3.4.2 + true + true @@ -40,7 +40,6 @@ org.wiremock wiremock-standalone - ${wiremock.version} test @@ -67,29 +66,5 @@ true - - - org.springframework.boot - spring-boot-maven-plugin - - true - - - - build-image - - true - - - - repackage - - true - - - - - - diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java index 26f7301c1b..da62ad2a08 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java @@ -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,21 +16,16 @@ 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; @@ -38,26 +33,21 @@ 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 - * 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 :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 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); - } - } - } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshMultipleNamespacesIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshMultipleNamespacesIT.java index d09a9028bb..b74dbc03ce 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshMultipleNamespacesIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshMultipleNamespacesIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 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,21 +16,11 @@ package org.springframework.cloud.kubernetes.configuration.watcher; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Base64; import java.util.List; -import java.util.Map; import java.util.Set; -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.V1Secret; -import io.kubernetes.client.openapi.models.V1SecretBuilder; import io.kubernetes.client.openapi.models.V1Service; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -41,18 +31,17 @@ 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.configureWireMock; +import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.createConfigMap; +import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.createSecret; +import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.deleteConfigMap; +import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.deleteSecret; +import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.verifyActuatorCalled; class ActuatorRefreshMultipleNamespacesIT { 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 DEFAULT_NAMESPACE = "default"; private static final String LEFT_NAMESPACE = "left"; @@ -88,124 +77,50 @@ static void afterAll() { /** *
 	 *     - deploy config-watcher in default namespace
-	 *     - deploy wiremock in default namespace (so that we could assert calls to the actuator path)
-	 *     - deploy configmap-left in left namespaces with proper label and "service-wiremock" name. Because of the
-	 *       label, this will trigger a reload; because of the name this will trigger a reload against that name.
-	 *       This is a http refresh against the actuator.
-	 *     - same as above for the configmap-right.
+	 *     - deploy wiremock in default namespace
+	 *     - deploy 'service-wiremock' configmap/secret in 'left' namespace.
+	 *     - deploy 'service-wiremock' configmap/secret in 'right' namespace.
+	 *     - each of the above triggers configuration watcher to issue
+	 *       calls to /actuator/refresh
 	 * 
*/ @Test void testConfigMapActuatorRefreshMultipleNamespaces() { - 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()); - - // left-config-map - V1ConfigMap leftConfigMap = new V1ConfigMapBuilder().editOrNewMetadata() - .withLabels(Map.of("spring.cloud.kubernetes.config", "true")) - .withName("service-wiremock") - .withNamespace(LEFT_NAMESPACE) - .endMetadata() - .addToData("color", "purple") - .build(); - util.createAndWait(LEFT_NAMESPACE, leftConfigMap, null); - - // right-config-map - V1ConfigMap rightConfigMap = new V1ConfigMapBuilder().editOrNewMetadata() - .withLabels(Map.of("spring.cloud.kubernetes.config", "true")) - .withName("service-wiremock") - .withNamespace(RIGHT_NAMESPACE) - .endMetadata() - .addToData("color", "green") - .build(); - util.createAndWait(RIGHT_NAMESPACE, rightConfigMap, null); - - // comes from handler::onAdd (and as such from "onEvent") - Commons.assertReloadLogStatements("ConfigMap service-wiremock was added in namespace left", "", - "spring-cloud-kubernetes-configuration-watcher"); - - // comes from handler::onAdd (and as such from "onEvent") - Commons.assertReloadLogStatements("ConfigMap service-wiremock was added in namespace right", "", - "spring-cloud-kubernetes-configuration-watcher"); - - await().atMost(Duration.ofSeconds(30)) - .until(() -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))) - .isEmpty()); - WireMock.verify(WireMock.exactly(2), WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); - - testSecretActuatorRefreshMultipleNamespaces(); + configureWireMock(); - } + createConfigMap(util, LEFT_NAMESPACE); + createConfigMap(util, RIGHT_NAMESPACE); - /** - *
-	 *     - deploy config-watcher in default namespace
-	 *     - deploy wiremock in default namespace (so that we could assert calls to the actuator path)
-	 *     - deploy secret-left in left namespaces with proper label and "service-wiremock". Because of the
-	 *       label, this will trigger a reload; because of the name this will trigger a reload against that name.
-	 *       This is a http refresh against the actuator.
-	 *     - same as above for the secret-right.
-	 * 
- */ - void testSecretActuatorRefreshMultipleNamespaces() { - 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()); - - // left-secret - V1Secret leftSecret = new V1SecretBuilder().editOrNewMetadata() - .withLabels(Map.of("spring.cloud.kubernetes.secret", "true")) - .withName("service-wiremock") - .withNamespace(LEFT_NAMESPACE) - .endMetadata() - .addToData("color", Base64.getEncoder().encode("purple".getBytes(StandardCharsets.UTF_8))) - .build(); - util.createAndWait(LEFT_NAMESPACE, null, leftSecret); - - // right-secret - V1Secret rightSecret = new V1SecretBuilder().editOrNewMetadata() - .withLabels(Map.of("spring.cloud.kubernetes.secret", "true")) - .withName("service-wiremock") - .withNamespace(RIGHT_NAMESPACE) - .endMetadata() - .addToData("color", Base64.getEncoder().encode("green".getBytes(StandardCharsets.UTF_8))) - .build(); - util.createAndWait(RIGHT_NAMESPACE, null, rightSecret); - - // comes from handler::onAdd (and as such from "onEvent") - Commons.assertReloadLogStatements("Secret service-wiremock was added in namespace left", "", - "spring-cloud-kubernetes-configuration-watcher"); - - // comes from handler::onAdd (and as such from "onEvent") - Commons.assertReloadLogStatements("Secret service-wiremock was added in namespace right", "", - "spring-cloud-kubernetes-configuration-watcher"); - - await().atMost(Duration.ofSeconds(30)) - .until(() -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))) - .isEmpty()); - WireMock.verify(WireMock.exactly(4), WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); + createSecret(util, LEFT_NAMESPACE); + createSecret(util, RIGHT_NAMESPACE); + + Commons.waitForLogStatement("ConfigMap service-wiremock was added in namespace left", K3S, + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME); + Commons.waitForLogStatement("ConfigMap service-wiremock was added in namespace right", K3S, + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME); + + Commons.waitForLogStatement("Secret service-wiremock was added in namespace left", K3S, + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME); + Commons.waitForLogStatement("Secret service-wiremock was added in namespace right", K3S, + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME); + verifyActuatorCalled(4); + deleteConfigMap(util, LEFT_NAMESPACE); + deleteConfigMap(util, RIGHT_NAMESPACE); + deleteSecret(util, LEFT_NAMESPACE); + deleteSecret(util, RIGHT_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"); List envVars = List.of( new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_RELOAD_NAMESPACES_0").value(LEFT_NAMESPACE), + new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_REFRESHDELAY").value("0"), new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_RELOAD_NAMESPACES_1").value(RIGHT_NAMESPACE), - new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK").value("TRACE")); + new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD") + .value("DEBUG")); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); @@ -213,11 +128,9 @@ private static void configWatcher(Phase phase) { .yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); if (phase.equals(Phase.CREATE)) { - util.createAndWait(DEFAULT_NAMESPACE, configMap, null); util.createAndWait(DEFAULT_NAMESPACE, null, deployment, service, null, true); } else { - util.deleteAndWait(DEFAULT_NAMESPACE, configMap, null); util.deleteAndWait(DEFAULT_NAMESPACE, deployment, service, null); } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/TestUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/TestUtil.java index b9d77740a8..1eb5ad8291 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/TestUtil.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/TestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 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,53 +16,110 @@ package org.springframework.cloud.kubernetes.configuration.watcher; +import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; +import java.util.List; import java.util.Map; -import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithReplace; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; + +import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.builder; +import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.retrySpec; /** * @author wind57 */ final class TestUtil { + private static final String WIREMOCK_HOST = "localhost"; + + private static final int WIREMOCK_PORT = 80; + + static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher"; + private TestUtil() { } - private static final Map POD_LABELS = Map.of("app", - "spring-cloud-kubernetes-configuration-watcher"); - - private static final String BODY_ONE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-configuration-watcher", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CONFIGURATION_WATCHER", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_RELOAD_ENABLED", - "value": "FALSE" - } - ] - }] - } - } - } - } - """; - - static void patchForDisabledReload(String deploymentName, String namespace, String imageName) { - patchWithReplace(imageName, deploymentName, namespace, BODY_ONE, POD_LABELS); + static void configureWireMock() { + WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT); + // the above statement configures the client, but we need to make sure the cluster + // is ready to take a request via 'Wiremock::stubFor' (because sometimes it fails) + // As such, get the existing mappings and retrySpec() makes sure we retry until + // we get a response back. + WebClient client = builder().baseUrl("http://localhost:80/__admin/mappings").build(); + client.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()).block(); + + StubMapping stubMapping = WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh")) + .willReturn(WireMock.aResponse().withBody("{}").withStatus(200))); + + await().atMost(Duration.ofSeconds(60)) + .pollInterval(Duration.ofSeconds(1)) + .ignoreException(SocketTimeoutException.class) + .until(() -> stubMapping.getResponse().wasConfigured()); + } + + static void verifyActuatorCalled(int timesCalled) { + await().atMost(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(1)).until(() -> { + List requests = WireMock + .findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); + return !requests.isEmpty(); + }); + WireMock.verify(WireMock.exactly(timesCalled), + WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); + } + + static void createConfigMap(Util util, String namespace) { + V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata() + .withName("service-wiremock") + .withNamespace(namespace) + .addToLabels("spring.cloud.kubernetes.config", "true") + .endMetadata() + .addToData("foo", "bar") + .build(); + util.createAndWait(namespace, configMap, null); + } + + static void deleteConfigMap(Util util, String namespace) { + V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata() + .withName("service-wiremock") + .withNamespace(namespace) + .endMetadata() + .build(); + util.deleteAndWait(namespace, configMap, null); + } + + static void createSecret(Util util, String namespace) { + V1Secret secret = new V1SecretBuilder().editOrNewMetadata() + .withLabels(Map.of("spring.cloud.kubernetes.secret", "true")) + .withName("service-wiremock") + .withNamespace(namespace) + .endMetadata() + .addToData("color", Base64.getEncoder().encode("purple".getBytes(StandardCharsets.UTF_8))) + .build(); + util.createAndWait(namespace, null, secret); + } + + static void deleteSecret(Util util, String namespace) { + V1Secret secret = new V1SecretBuilder().editOrNewMetadata() + .withName("service-wiremock") + .withNamespace(namespace) + .endMetadata() + .build(); + util.deleteAndWait(namespace, null, secret); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml deleted file mode 100644 index 9c2ea62e40..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-configuration-watcher/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -data: - application.properties: |- - # Set the refresh interval to 0 so the refresh event gets sent immediately - spring.cloud.kubernetes.configuration.watcher.refreshDelay=0 - logging.level.org.springframework.cloud.kubernetes=TRACE -kind: ConfigMap -metadata: - name: spring-cloud-kubernetes-configuration-watcher