From 7c441dfb2b3b2ab6114cc1db14a3ac2814db02dd Mon Sep 17 00:00:00 2001 From: wind57 Date: Fri, 21 Mar 2025 14:54:17 +0200 Subject: [PATCH] dirty Signed-off-by: wind57 --- ...ric8ConfigMapMountPollingBootstrapIT.java} | 4 +- .../k8s/client/reload/Controller.java | 37 +- .../k8s/client/reload/SecretsController.java | 39 -- .../src/main/resources/application-mount.yaml | 2 +- .../src/main/resources/application-one.yaml | 16 - .../resources/application-with-bootstrap.yaml | 2 +- .../src/main/resources/bootstrap-one.yaml | 13 - .../DataChangesInConfigMapReloadDelegate.java | 137 ------ .../configmap/K8sClientConfigMapReloadIT.java | 136 ------ .../K8sClientConfigMapReloadITUtil.java | 445 ------------------ .../PollingReloadConfigMapMountDelegate.java | 100 ---- .../K8sClientConfigMapEventTriggeredIT.java | 2 +- ...sClientConfigMapLabelEventTriggeredIT.java | 20 +- .../K8sClientConfigMapMountPollingIT.java} | 89 ++-- .../{configmap => }/K8sClientReloadBase.java | 75 +-- ...8sClientSecretMountBootstrapPollingIT.java | 125 +++++ .../DataChangesInSecretsReloadDelegate.java | 126 ----- .../secret/K8sClientSecretsReloadIT.java | 159 ------- .../secret/K8sClientSecretsReloadITUtil.java | 102 ---- .../src/test/resources/ingress.yaml | 16 - .../src/test/resources/left-configmap.yaml | 7 - .../configmap.yaml} | 2 +- .../deployment-with-secret.yaml} | 24 +- .../deployment.yaml} | 26 +- .../test/resources/{ => mount}/secret.yaml | 2 +- .../resources/mount/service-with-secret.yaml | 15 + .../src/test/resources/mount/service.yaml | 15 + .../src/test/resources/service.yaml | 14 - 28 files changed, 323 insertions(+), 1427 deletions(-) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/{Fabric8ConfigMapMountMountPollingBootstrapIT.java => Fabric8ConfigMapMountPollingBootstrapIT.java} (97%) delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/SecretsController.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-one.yaml delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/bootstrap-one.yaml delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/DataChangesInConfigMapReloadDelegate.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadIT.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadITUtil.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/PollingReloadConfigMapMountDelegate.java rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/{configmap => }/K8sClientConfigMapEventTriggeredIT.java (99%) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/{configmap => }/K8sClientConfigMapLabelEventTriggeredIT.java (92%) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/{configmap/BootstrapEnabledPollingReloadConfigMapMountDelegate.java => it/K8sClientConfigMapMountPollingIT.java} (54%) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/{configmap => }/K8sClientReloadBase.java (60%) create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientSecretMountBootstrapPollingIT.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/DataChangesInSecretsReloadDelegate.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadIT.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadITUtil.java delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/ingress.yaml delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/left-configmap.yaml rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/{configmap-mount.yaml => mount/configmap.yaml} (82%) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/{deployment.yaml => mount/deployment-with-secret.yaml} (62%) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/{deployment-with-secret.yaml => mount/deployment.yaml} (54%) rename spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/{ => mount}/secret.yaml (88%) create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service-with-secret.yaml create mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service.yaml delete mode 100644 spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8ConfigMapMountMountPollingBootstrapIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8ConfigMapMountPollingBootstrapIT.java similarity index 97% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8ConfigMapMountMountPollingBootstrapIT.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8ConfigMapMountPollingBootstrapIT.java index 71389a57..d99b0b11 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8ConfigMapMountMountPollingBootstrapIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8ConfigMapMountPollingBootstrapIT.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. @@ -44,7 +44,7 @@ import static org.springframework.cloud.kubernetes.integration.tests.commons.Com /** * @author wind57 */ -class Fabric8ConfigMapMountMountPollingBootstrapIT { +class Fabric8ConfigMapMountPollingBootstrapIT { private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-reload"; diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/Controller.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/Controller.java index e2eb6435..660ed1dc 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/Controller.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/Controller.java @@ -25,40 +25,23 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class Controller { - private final LeftProperties leftProperties; - - private final RightProperties rightProperties; - - private final RightWithLabelsProperties rightWithLabelsProperties; - private final ConfigMapProperties configMapProperties; - public Controller(LeftProperties leftProperties, RightProperties rightProperties, - RightWithLabelsProperties rightWithLabelsProperties, ConfigMapProperties configMapProperties) { - this.leftProperties = leftProperties; - this.rightProperties = rightProperties; - this.rightWithLabelsProperties = rightWithLabelsProperties; + private final SecretsProperties secretsProperties; + + public Controller(ConfigMapProperties configMapProperties, SecretsProperties secretsProperties) { this.configMapProperties = configMapProperties; + this.secretsProperties = secretsProperties; } - @GetMapping("/left") - public String left() { - return leftProperties.getValue(); - } - - @GetMapping("/right") - public String right() { - return rightProperties.getValue(); - } - - @GetMapping("/with-label") - public String witLabel() { - return rightWithLabelsProperties.getValue(); - } - - @GetMapping("/mount") + @GetMapping("/configmap") public String key() { return configMapProperties.getKey(); } + @GetMapping("/secret") + public String secret() { + return configMapProperties.getKey(); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/SecretsController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/SecretsController.java deleted file mode 100644 index b0e0597a..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/java/org/springframework/cloud/kubernetes/k8s/client/reload/SecretsController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013-2021 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.k8s.client.reload; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author wind57 - */ -@RestController -public class SecretsController { - - private final SecretsProperties properties; - - public SecretsController(SecretsProperties properties) { - this.properties = properties; - } - - @GetMapping("/key") - public String key() { - return properties.getKey(); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-mount.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-mount.yaml index cd1765f5..62548724 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-mount.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-mount.yaml @@ -1,6 +1,6 @@ spring: application: - name: poll-reload-mount + name: poll-reload cloud: kubernetes: reload: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-one.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-one.yaml deleted file mode 100644 index 988bfb21..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-one.yaml +++ /dev/null @@ -1,16 +0,0 @@ -logging: - level: - root: DEBUG - -spring: - application: - name: event-reload - cloud: - kubernetes: - reload: - enabled: true - strategy: shutdown - mode: event - namespaces: - - left - monitoring-config-maps: true diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-bootstrap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-bootstrap.yaml index c997322d..220edfde 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-bootstrap.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/application-with-bootstrap.yaml @@ -5,8 +5,8 @@ spring: kubernetes: reload: enabled: true - monitoring-config-maps: true strategy: shutdown mode: polling period: 5000 + monitoring-secrets: true diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/bootstrap-one.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/bootstrap-one.yaml deleted file mode 100644 index 9265fce1..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/main/resources/bootstrap-one.yaml +++ /dev/null @@ -1,13 +0,0 @@ -logging: - level: - root: DEBUG - -spring: - cloud: - kubernetes: - config: - sources: - - namespace: left - name: left-configmap - - namespace: right - name: right-configmap diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/DataChangesInConfigMapReloadDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/DataChangesInConfigMapReloadDelegate.java deleted file mode 100644 index c527145d..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/DataChangesInConfigMapReloadDelegate.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.k8s.client.reload.configmap; - -import java.time.Duration; -import java.util.Map; - -import io.kubernetes.client.openapi.ApiException; -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.V1ObjectMetaBuilder; -import org.junit.jupiter.api.Assertions; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.http.HttpMethod; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.awaitility.Awaitility.await; - -/** - * @author wind57 - */ -final class DataChangesInConfigMapReloadDelegate { - - private static final String NAMESPACE = "default"; - - private static final String LEFT_NAMESPACE = "left"; - - /** - *
-	 *     - configMap with no labels and data: left.value = left-initial exists in namespace left
-	 *     - we assert that we can read it correctly first, by invoking localhost/left
-	 *
-	 *     - then we change the configmap by adding a label, this in turn does not
-	 *       change the result of localhost/left, because the data has not changed.
-	 *
-	 *     - then we change data inside the config map, and we must see the updated value
-	 * 
- */ - static void testSimple(String dockerImage, String deploymentName, K3sContainer k3sContainer) { - - K8sClientConfigMapReloadITUtil.patchFour(deploymentName, NAMESPACE, dockerImage); - Commons.assertReloadLogStatements("added configmap informer for namespace", - "added secret informer for namespace", deploymentName); - - WebClient webClient = K8sClientConfigMapReloadITUtil.builder() - .baseUrl("http://localhost/" + LEFT_NAMESPACE) - .build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) - .block(); - - // we first read the initial value from the left-configmap - Assertions.assertEquals("left-initial", result); - - // then deploy a new version of left-configmap, but without changing its data, - // only add a label - V1ConfigMap configMap = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) - .withNamespace("left") - .withName("left-configmap") - .build()) - .withData(Map.of("left.value", "left-initial")) - .build(); - - replaceConfigMap(configMap); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = K8sClientConfigMapReloadITUtil.builder() - .baseUrl("http://localhost/" + LEFT_NAMESPACE) - .build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) - .block(); - return "left-initial".equals(innerResult); - }); - - String logs = K8sClientConfigMapReloadITUtil.logs(deploymentName, k3sContainer); - Assertions.assertTrue(logs.contains("ConfigMap left-configmap was updated in namespace left")); - Assertions.assertTrue(logs.contains("data in configmap has not changed, will not reload")); - - // change data - configMap = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) - .withNamespace("left") - .withName("left-configmap") - .build()) - .withData(Map.of("left.value", "left-after-change")) - .build(); - - replaceConfigMap(configMap); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = K8sClientConfigMapReloadITUtil.builder() - .baseUrl("http://localhost/" + LEFT_NAMESPACE) - .build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) - .block(); - return "left-after-change".equals(innerResult); - }); - - } - - private static void replaceConfigMap(V1ConfigMap configMap) { - try { - new CoreV1Api().replaceNamespacedConfigMap("left-configmap", LEFT_NAMESPACE, configMap, null, null, null, - null); - } - catch (ApiException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadIT.java deleted file mode 100644 index be593d37..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadIT.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2013-2022 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.k8s.client.reload.configmap; - -import java.time.Duration; -import java.util.Map; -import java.util.Set; - -import io.kubernetes.client.openapi.ApiException; -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.V1Ingress; -import io.kubernetes.client.openapi.models.V1ObjectMeta; -import io.kubernetes.client.openapi.models.V1Service; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -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.k8s.client.reload.configmap.BootstrapEnabledPollingReloadConfigMapMountDelegate.testBootstrapEnabledPollingReloadConfigMapMount; -import static org.springframework.cloud.kubernetes.k8s.client.reload.configmap.DataChangesInConfigMapReloadDelegate.testSimple; -import static org.springframework.cloud.kubernetes.k8s.client.reload.configmap.K8sClientConfigMapReloadITUtil.builder; -import static org.springframework.cloud.kubernetes.k8s.client.reload.configmap.K8sClientConfigMapReloadITUtil.patchThree; -import static org.springframework.cloud.kubernetes.k8s.client.reload.configmap.K8sClientConfigMapReloadITUtil.retrySpec; -import static org.springframework.cloud.kubernetes.k8s.client.reload.configmap.PollingReloadConfigMapMountDelegate.testPollingReloadConfigMapMount; - -/** - * @author wind57 - */ -class K8sClientConfigMapReloadIT { - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-k8s-client-reload"; - - private static final String DEPLOYMENT_NAME = "spring-k8s-client-reload"; - - private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + Commons.pomVersion(); - - private static final String NAMESPACE = "default"; - - private static final K3sContainer K3S = Commons.container(); - - private static Util util; - - private static CoreV1Api api; - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - util = new Util(K3S); - util.createNamespace("left"); - util.createNamespace("right"); - util.setUpClusterWide(NAMESPACE, Set.of("left", "right")); - util.setUp(NAMESPACE); - api = new CoreV1Api(); - } - - @AfterAll - static void afterAll() { - util.deleteClusterWide(NAMESPACE, Set.of("left", "right")); - manifests(Phase.DELETE); - util.deleteNamespace("left"); - util.deleteNamespace("right"); - } - - // since we patch each deployment with "replace" strategy, any of the above can be - // commented out and debugged individually. - private void testAllOther() throws Exception { - testSimple(DOCKER_IMAGE, DEPLOYMENT_NAME, K3S); - testPollingReloadConfigMapMount(DEPLOYMENT_NAME, K3S, util, DOCKER_IMAGE); - testBootstrapEnabledPollingReloadConfigMapMount(DEPLOYMENT_NAME, K3S, util, DOCKER_IMAGE); - - } - - private static void manifests(Phase phase) { - - try { - - V1ConfigMap leftConfigMap = (V1ConfigMap) util.yaml("left-configmap.yaml"); - V1ConfigMap rightConfigMap = (V1ConfigMap) util.yaml("right-configmap.yaml"); - V1ConfigMap mountConfigMap = (V1ConfigMap) util.yaml("configmap-mount.yaml"); - - V1Deployment deployment = (V1Deployment) util.yaml("deployment.yaml"); - V1Service service = (V1Service) util.yaml("service.yaml"); - V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml"); - - if (phase.equals(Phase.CREATE)) { - util.createAndWait(NAMESPACE, mountConfigMap, null); - util.createAndWait("left", leftConfigMap, null); - util.createAndWait("right", rightConfigMap, null); - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - } - - if (phase.equals(Phase.DELETE)) { - util.deleteAndWait(NAMESPACE, mountConfigMap, null); - util.deleteAndWait("left", leftConfigMap, null); - util.deleteAndWait("right", rightConfigMap, null); - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - } - - } - catch (Exception e) { - throw new RuntimeException(e); - } - - } - - private static void replaceConfigMap(V1ConfigMap configMap, String name) throws ApiException { - api.replaceNamespacedConfigMap(name, "right", configMap, null, null, null, null); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadITUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadITUtil.java deleted file mode 100644 index f7e7197d..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/K8sClientConfigMapReloadITUtil.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * 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.k8s.client.reload.configmap; - -import java.time.Duration; -import java.util.Map; -import java.util.Objects; - -import org.testcontainers.containers.Container; -import org.testcontainers.k3s.K3sContainer; -import reactor.netty.http.client.HttpClient; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithReplace; - -/** - * @author wind57 - */ -final class K8sClientConfigMapReloadITUtil { - - private static final Map POD_LABELS = Map.of("app", "spring-k8s-client-reload"); - - private K8sClientConfigMapReloadITUtil() { - } - - private static final String BODY_ONE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "two" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_TWO = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "three" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_THREE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "two" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED", - "value": "FALSE" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_FOUR = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "one" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED", - "value": "FALSE" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_FIVE = """ - { - "spec": { - "template": { - "spec": { - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "name": "poll-reload-as-mount" - }, - "name": "config-map-volume" - } - ], - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "config-map-volume" - } - ], - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "mount" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "FALSE" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG", - "value": "DEBUG" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS", - "value": "DEBUG" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_SIX = """ - { - "spec": { - "template": { - "spec": { - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "name": "poll-reload-as-mount" - }, - "name": "config-map-volume" - } - ], - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "config-map-volume" - } - ], - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "with-bootstrap" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG", - "value": "DEBUG" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS", - "value": "DEBUG" - } - ] - }] - } - } - } - } - """; - - static void patchThree(String deploymentName, String namespace, String imageName) { - patchWithReplace(imageName, deploymentName, namespace, BODY_THREE, POD_LABELS); - } - - static void patchFour(String deploymentName, String namespace, String imageName) { - patchWithReplace(imageName, deploymentName, namespace, BODY_FOUR, POD_LABELS); - } - - static void patchFive(String deploymentName, String namespace, String imageName) { - patchWithReplace(imageName, deploymentName, namespace, BODY_FIVE, POD_LABELS); - } - - static void patchSix(String deploymentName, String namespace, String imageName) { - patchWithReplace(imageName, deploymentName, namespace, BODY_SIX, POD_LABELS); - } - - static WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - static RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(120, Duration.ofSeconds(1)).filter(Objects::nonNull); - } - - static String logs(String appLabelValue, K3sContainer k3sContainer) { - try { - String appPodName = k3sContainer - .execInContainer("sh", "-c", - "kubectl get pods -l app=" + appLabelValue + " -o=name --no-headers | tr -d '\n'") - .getStdout(); - - Container.ExecResult execResult = k3sContainer.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-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/PollingReloadConfigMapMountDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/PollingReloadConfigMapMountDelegate.java deleted file mode 100644 index 3092e925..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/PollingReloadConfigMapMountDelegate.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.k8s.client.reload.configmap; - -import java.time.Duration; -import java.util.Map; - -import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.models.V1ConfigMap; -import org.junit.jupiter.api.Assertions; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.commons.config.Constants; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -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; - -/** - * @author wind57 - */ -final class PollingReloadConfigMapMountDelegate { - - private PollingReloadConfigMapMountDelegate() { - - } - - /** - *
-	 *     - we have "spring.config.import: kubernetes", which means we will 'locate' property sources
-	 *       from config maps.
-	 *     - the property above means that at the moment we will be searching for config maps that only
-	 *       match the application name, in this specific test there is no such config map.
-	 *     - what we will also read, is 'spring.cloud.kubernetes.config.paths', which we have set to
-	 *     	 '/tmp/application.properties'
-	 *       in this test. That is populated by the volumeMounts (see BODY_FIVE)
-	 *     - we first assert that we are actually reading the path based source via (1), (2) and (3).
-	 *
-	 *     - we then change the config map content, wait for k8s to pick it up and replace them
-	 *     - our polling will then detect that change, and trigger a reload.
-	 * 
- */ - static void testPollingReloadConfigMapMount(String deploymentName, K3sContainer k3sContainer, Util util, - String imageName) throws Exception { - - K8sClientConfigMapReloadITUtil.patchFive(deploymentName, "default", imageName); - - // (1) - Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", k3sContainer, - deploymentName); - - // (2) - Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", k3sContainer, - deploymentName); - - // (3) - WebClient webClient = K8sClientConfigMapReloadITUtil.builder().baseUrl("http://localhost/mount").build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) - .block(); - - // we first read the initial value from the configmap - Assertions.assertEquals("as-mount-initial", result); - - // replace data in configmap and wait for k8s to pick it up - // our polling will detect that and restart the app - V1ConfigMap configMap = (V1ConfigMap) util.yaml("configmap-mount.yaml"); - configMap.setData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=as-mount-changed")); - new CoreV1Api().replaceNamespacedConfigMap("poll-reload-as-mount", "default", configMap, null, null, null, - null); - - await().timeout(Duration.ofSeconds(180)) - .until(() -> webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) - .block() - .equals("as-mount-changed")); - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientConfigMapEventTriggeredIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapEventTriggeredIT.java similarity index 99% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientConfigMapEventTriggeredIT.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapEventTriggeredIT.java index 55a420ff..1831ca4a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientConfigMapEventTriggeredIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapEventTriggeredIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.k8s.client.reload.it.configmap; +package org.springframework.cloud.kubernetes.k8s.client.reload.it; import java.time.Duration; import java.util.Map; diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientConfigMapLabelEventTriggeredIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapLabelEventTriggeredIT.java similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientConfigMapLabelEventTriggeredIT.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapLabelEventTriggeredIT.java index f16eda4f..303ecbd3 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientConfigMapLabelEventTriggeredIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapLabelEventTriggeredIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.k8s.client.reload.it.configmap; +package org.springframework.cloud.kubernetes.k8s.client.reload.it; import java.time.Duration; import java.util.Map; @@ -139,23 +139,28 @@ class K8sClientConfigMapLabelEventTriggeredIT extends K8sClientReloadBase { // then deploy a new version of right-configmap-with-label // but only add a label, this does not trigger a refresh V1ConfigMap rightWithLabelConfigMap = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMeta().namespace(NAMESPACE_RIGHT).name("right-configmap-with-label") - .labels(Map.of("spring.cloud.kubernetes.config.informer.enabled", "true", - "custom.label", "spring-k8s"))) + .withMetadata(new V1ObjectMeta().namespace(NAMESPACE_RIGHT) + .name("right-configmap-with-label") + .labels(Map.of("spring.cloud.kubernetes.config.informer.enabled", "true", "custom.label", + "spring-k8s"))) + .withData(Map.of("right.with.label.value", "right-with-label-initial")) .build(); + replaceConfigMap(coreV1Api, rightWithLabelConfigMap); + await().atMost(Duration.ofSeconds(60)) .pollDelay(Duration.ofSeconds(1)) .until(() -> output.getOut().contains("data in configmap has not changed, will not reload")); await().atMost(Duration.ofSeconds(60)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> rightWithLabelsProperties.getValue().equals("right-with-label-after-change")); + .until(() -> rightWithLabelsProperties.getValue().equals("right-with-label-initial")); // then deploy a new version of right-configmap-with-label // that changes data also V1ConfigMap rightWithLabelConfigMapAfterChange = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMeta().namespace(NAMESPACE_RIGHT).name("right-configmap-with-label") + .withMetadata(new V1ObjectMeta().namespace(NAMESPACE_RIGHT) + .name("right-configmap-with-label") .labels(Map.of("spring.cloud.kubernetes.config.informer.enabled", "true"))) .withData(Map.of("right.with.label.value", "right-with-label-after-change")) .build(); @@ -164,7 +169,8 @@ class K8sClientConfigMapLabelEventTriggeredIT extends K8sClientReloadBase { await().atMost(Duration.ofSeconds(60)) .pollDelay(Duration.ofSeconds(1)) - .until(() -> output.getOut().contains("ConfigMap right-configmap-with-label was updated in namespace right")); + .until(() -> output.getOut() + .contains("ConfigMap right-configmap-with-label was updated in namespace right")); await().atMost(Duration.ofSeconds(60)) .pollInterval(Duration.ofSeconds(1)) diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/BootstrapEnabledPollingReloadConfigMapMountDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapMountPollingIT.java similarity index 54% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/BootstrapEnabledPollingReloadConfigMapMountDelegate.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapMountPollingIT.java index 358ae43f..8a9d9bc0 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/configmap/BootstrapEnabledPollingReloadConfigMapMountDelegate.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientConfigMapMountPollingIT.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. @@ -14,92 +14,111 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.k8s.client.reload.configmap; +package org.springframework.cloud.kubernetes.k8s.client.reload.it; import java.time.Duration; import java.util.Map; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; -import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.testcontainers.k3s.K3sContainer; import org.springframework.cloud.kubernetes.commons.config.Constants; import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; 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.assertj.core.api.Assertions.assertThat; 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 BootstrapEnabledPollingReloadConfigMapMountDelegate { +class K8sClientConfigMapMountPollingIT extends K8sClientReloadBase { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-k8s-client-reload"; private static final String NAMESPACE = "default"; + private static final K3sContainer K3S = Commons.container(); + + private static Util util; + + private static CoreV1Api coreV1Api; + + @BeforeAll + static void beforeAllLocal() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + util = new Util(K3S); + coreV1Api = new CoreV1Api(); + util.setUp(NAMESPACE); + manifests(Phase.CREATE, util, NAMESPACE, IMAGE_NAME); + } + + @AfterAll + static void afterAll() { + manifests(Phase.DELETE, util, NAMESPACE, IMAGE_NAME); + } + /** *
-	 *     - we have bootstrap enabled, which means we will 'locate' property sources
-	 *       from config maps.
+	 *     - we have bootstrap disabled
+	 *     - we will 'locate' property sources from config maps.
 	 *     - there are no explicit config maps to search for, but what we will also read,
 	 *     	 is 'spring.cloud.kubernetes.config.paths', which we have set to
 	 *     	 '/tmp/application.properties'
-	 *       in this test. That is populated by the volumeMounts (see deployment-mount.yaml)
+	 *       in this test. That is populated by the volumeMounts (see mount/deployment.yaml)
 	 *     - we first assert that we are actually reading the path based source via (1), (2) and (3).
 	 *
 	 *     - we then change the config map content, wait for k8s to pick it up and replace them
 	 *     - our polling will then detect that change, and trigger a reload.
 	 * 
*/ - static void testBootstrapEnabledPollingReloadConfigMapMount(String deploymentName, K3sContainer k3sContainer, - Util util, String imageName) throws Exception { - - recreateMountConfigMap(util); - K8sClientConfigMapReloadITUtil.patchSix(deploymentName, "default", imageName); - + @Test + void test() throws Exception { // (1) - Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", k3sContainer, - deploymentName); - + Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", K3S, IMAGE_NAME); // (2) - Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", k3sContainer, - deploymentName); - + Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", K3S, + IMAGE_NAME); // (3) - WebClient webClient = K8sClientConfigMapReloadITUtil.builder().baseUrl("http://localhost/mount").build(); + WebClient webClient = builder().baseUrl("http://localhost:32321/configmap").build(); String result = webClient.method(HttpMethod.GET) .retrieve() .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) + .retryWhen(retrySpec()) .block(); // we first read the initial value from the configmap - Assertions.assertEquals("as-mount-initial", result); + assertThat(result).isEqualTo("as-mount-initial"); // replace data in configmap and wait for k8s to pick it up // our polling will detect that and restart the app - V1ConfigMap configMap = (V1ConfigMap) util.yaml("configmap-mount.yaml"); + V1ConfigMap configMap = (V1ConfigMap) util.yaml("mount/configmap.yaml"); configMap.setData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=as-mount-changed")); - new CoreV1Api().replaceNamespacedConfigMap("poll-reload-as-mount", NAMESPACE, configMap, null, null, null, - null); + coreV1Api.replaceNamespacedConfigMap("poll-reload", NAMESPACE, configMap, null, null, null, null); - await().timeout(Duration.ofSeconds(180)) + Commons.waitForLogStatement("Detected change in config maps/secrets, reload will be triggered", K3S, + IMAGE_NAME); + + await().atMost(Duration.ofSeconds(120)) + .pollInterval(Duration.ofSeconds(1)) .until(() -> webClient.method(HttpMethod.GET) .retrieve() .bodyToMono(String.class) - .retryWhen(K8sClientConfigMapReloadITUtil.retrySpec()) + .retryWhen(retrySpec()) .block() .equals("as-mount-changed")); - - } - - private static void recreateMountConfigMap(Util util) { - V1ConfigMap mountConfigMap = (V1ConfigMap) util.yaml("configmap-mount.yaml"); - - util.deleteAndWait("default", mountConfigMap, null); - util.createAndWait("default", mountConfigMap, null); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientReloadBase.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientReloadBase.java similarity index 60% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientReloadBase.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientReloadBase.java index cc7b078c..5655ab7f 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/configmap/K8sClientReloadBase.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientReloadBase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.k8s.client.reload.it.configmap; +package org.springframework.cloud.kubernetes.k8s.client.reload.it; import java.io.IOException; import java.io.StringReader; @@ -24,6 +24,9 @@ import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1Service; import io.kubernetes.client.util.Config; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.ExtendWith; @@ -32,6 +35,7 @@ import org.testcontainers.k3s.K3sContainer; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util; import static org.testcontainers.shaded.org.awaitility.Awaitility.await; @@ -72,33 +76,14 @@ abstract class K8sClientReloadBase { */ static void assertReloadLogStatements(String left, String right, CapturedOutput output) { - await().atMost(Duration.ofSeconds(30)) - .pollInterval(Duration.ofSeconds(1)) - .until(() -> { - boolean leftIsPresent = output.getOut().contains(left); - if (leftIsPresent) { - boolean rightIsPresent = output.getOut().contains(right); - return !rightIsPresent; - } - return false; - }); - } - - /** - * assert that 'left' is present, and IFF it is, assert that 'right' is not - */ - static void assertLogStatements(CapturedOutput output, String text) { - - await().atMost(Duration.ofSeconds(30)) - .pollInterval(Duration.ofSeconds(1)) - .until(() -> { - boolean leftIsPresent = output.getOut().contains(left); - if (leftIsPresent) { - boolean rightIsPresent = output.getOut().contains(right); - return !rightIsPresent; - } - return false; - }); + await().atMost(Duration.ofSeconds(30)).pollInterval(Duration.ofSeconds(1)).until(() -> { + boolean leftIsPresent = output.getOut().contains(left); + if (leftIsPresent) { + boolean rightIsPresent = output.getOut().contains(right); + return !rightIsPresent; + } + return false; + }); } protected static void replaceConfigMap(CoreV1Api api, V1ConfigMap configMap) { @@ -112,4 +97,38 @@ abstract class K8sClientReloadBase { } } + protected static void manifests(Phase phase, Util util, String namespace, String imageName) { + + V1Deployment deployment = (V1Deployment) util.yaml("mount/deployment.yaml"); + V1Service service = (V1Service) util.yaml("mount/service.yaml"); + V1ConfigMap configMap = (V1ConfigMap) util.yaml("mount/configmap.yaml"); + + if (phase.equals(Phase.CREATE)) { + util.createAndWait(namespace, configMap, null); + util.createAndWait(namespace, imageName, deployment, service, null, true); + } + else { + util.deleteAndWait(namespace, configMap, null); + util.deleteAndWait(namespace, deployment, service, null); + } + + } + + protected static void manifestsSecret(Phase phase, Util util, String namespace, String imageName) { + + V1Deployment deployment = (V1Deployment) util.yaml("mount/deployment-with-secret.yaml"); + V1Service service = (V1Service) util.yaml("mount/service-with-secret.yaml"); + V1Secret secret = (V1Secret) util.yaml("mount/secret.yaml"); + + if (phase.equals(Phase.CREATE)) { + util.createAndWait(namespace, null, secret); + util.createAndWait(namespace, imageName, deployment, service, null, true); + } + else { + util.deleteAndWait(namespace, null, secret); + util.deleteAndWait(namespace, deployment, service, null); + } + + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientSecretMountBootstrapPollingIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientSecretMountBootstrapPollingIT.java new file mode 100644 index 00000000..87568eb0 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/it/K8sClientSecretMountBootstrapPollingIT.java @@ -0,0 +1,125 @@ +/* + * 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. + * 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.k8s.client.reload.it; + +import java.time.Duration; +import java.util.Map; + +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; + +import org.springframework.cloud.kubernetes.commons.config.Constants; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +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.assertj.core.api.Assertions.assertThat; +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 + */ +class K8sClientSecretMountBootstrapPollingIT extends K8sClientReloadBase { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-k8s-client-reload"; + + private static final String NAMESPACE = "default"; + + private static final K3sContainer K3S = Commons.container(); + + private static Util util; + + private static CoreV1Api coreV1Api; + + @BeforeAll + static void beforeAllLocal() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + util = new Util(K3S); + coreV1Api = new CoreV1Api(); + util.setUp(NAMESPACE); + manifestsSecret(Phase.CREATE, util, NAMESPACE, IMAGE_NAME); + } + + @AfterAll + static void afterAll() { + manifestsSecret(Phase.DELETE, util, NAMESPACE, IMAGE_NAME); + } + + /** + *
+	 *     - we have bootstrap enabled
+	 *     - we will 'locate' property sources from secrets.
+	 *     - there are no explicit secrets to search for, but what we will also read,
+	 *     	 is 'spring.cloud.kubernetes.secret.paths', which we have set to
+	 *     	 '/tmp/application.properties'
+	 *       in this test. That is populated by the volumeMounts (see mount/deployment-with-secret.yaml)
+	 *     - we first assert that we are actually reading the path based source via (1), (2) and (3).
+	 *
+	 *     - we then change the secret content, wait for k8s to pick it up and replace them
+	 *     - our polling will then detect that change, and trigger a reload.
+	 * 
+ */ + @Test + void test() throws Exception { + // (1) + Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", K3S, IMAGE_NAME); + // (2) + Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", K3S, + IMAGE_NAME); + // (3) + WebClient webClient = builder().baseUrl("http://localhost:32321/secret").build(); + String result = webClient.method(HttpMethod.GET) + .retrieve() + .bodyToMono(String.class) + .retryWhen(retrySpec()) + .block(); + + // we first read the initial value from the configmap + assertThat(result).isEqualTo("as-mount-initial"); + + // replace data in configmap and wait for k8s to pick it up + // our polling will detect that and restart the app + V1ConfigMap configMap = (V1ConfigMap) util.yaml("mount/secret.yaml"); + configMap.setData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=as-mount-changed")); + coreV1Api.replaceNamespacedConfigMap("poll-reload", NAMESPACE, configMap, null, null, null, null); + + System.out.println("Waiting for reload change to be observed"); + Commons.waitForLogStatement("Detected change in config maps/secrets, reload will be triggered", K3S, + IMAGE_NAME); + + await().atMost(Duration.ofSeconds(120)) + .pollInterval(Duration.ofSeconds(1)) + .until(() -> webClient.method(HttpMethod.GET) + .retrieve() + .bodyToMono(String.class) + .retryWhen(retrySpec()) + .block() + .equals("as-mount-changed")); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/DataChangesInSecretsReloadDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/DataChangesInSecretsReloadDelegate.java deleted file mode 100644 index ba0ab2fa..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/DataChangesInSecretsReloadDelegate.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.k8s.client.reload.secret; - -import java.time.Duration; -import java.util.Map; - -import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; -import io.kubernetes.client.openapi.models.V1Secret; -import io.kubernetes.client.openapi.models.V1SecretBuilder; -import org.junit.jupiter.api.Assertions; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.commons.config.Constants; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -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.k8s.client.reload.secret.K8sClientSecretsReloadITUtil.builder; -import static org.springframework.cloud.kubernetes.k8s.client.reload.secret.K8sClientSecretsReloadITUtil.retrySpec; - -final class DataChangesInSecretsReloadDelegate { - - private static final String NAMESPACE = "default"; - - /** - *
-	 *     - secret with no labels and data: from.properties.key = initial exists in namespace default
-	 *     - we assert that we can read it correctly first, by invoking localhost/key.
-	 *
-	 *     - then we change the secret by adding a label, this in turn does not
-	 *       change the result of localhost/key, because the data has not changed.
-	 *
-	 *     - then we change data inside the secret, and we must see the updated value.
-	 * 
- */ - static void testDataChangesInSecretsReload(K3sContainer k3sContainer, String deploymentName) { - Commons.assertReloadLogStatements("added secret informer for namespace", - "added configmap informer for namespace", deploymentName); - - 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 left-configmap, but without changing its data, - // only add a label - V1Secret secret = new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) - .withNamespace(NAMESPACE) - .withName("event-reload") - .build()) - .withData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=initial".getBytes())) - .build(); - - replaceSecret(secret, "event-reload"); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = builder().baseUrl("http://localhost/key").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - return "initial".equals(innerResult); - }); - - Commons.waitForLogStatement("Secret event-reload was updated in namespace default", k3sContainer, - deploymentName); - Commons.waitForLogStatement("data in secret has not changed, will not reload", k3sContainer, deploymentName); - - // change data - secret = new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) - .withNamespace(NAMESPACE) - .withName("event-reload") - .build()) - .withData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=change-initial".getBytes())) - .build(); - - replaceSecret(secret, "event-reload"); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = builder().baseUrl("http://localhost/key").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - return "change-initial".equals(innerResult); - }); - - } - - private static void replaceSecret(V1Secret secret, String name) { - try { - new CoreV1Api().replaceNamespacedSecret(name, NAMESPACE, secret, null, null, null, null); - } - catch (ApiException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadIT.java deleted file mode 100644 index 91f83032..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadIT.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.k8s.client.reload.secret; - -import java.time.Duration; -import java.util.Map; - -import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.models.V1Deployment; -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.BeforeAll; -import org.junit.jupiter.api.Test; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.commons.config.Constants; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -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.k8s.client.reload.secret.DataChangesInSecretsReloadDelegate.testDataChangesInSecretsReload; -import static org.springframework.cloud.kubernetes.k8s.client.reload.secret.K8sClientSecretsReloadITUtil.builder; -import static org.springframework.cloud.kubernetes.k8s.client.reload.secret.K8sClientSecretsReloadITUtil.patchOne; -import static org.springframework.cloud.kubernetes.k8s.client.reload.secret.K8sClientSecretsReloadITUtil.retrySpec; - -/** - * @author wind57 - */ -class K8sClientSecretsReloadIT { - - private static final String PROPERTY_URL = "http://localhost:80/key"; - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-k8s-client-reload"; - - private static final String NAMESPACE = "default"; - - private static final String DEPLOYMENT_NAME = "spring-k8s-client-reload"; - - private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + Commons.pomVersion(); - - private static final K3sContainer K3S = Commons.container(); - - private static Util util; - - private static CoreV1Api coreV1Api; - - @BeforeAll - static void setup() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - util = new Util(K3S); - coreV1Api = new CoreV1Api(); - util.setUp(NAMESPACE); - configK8sClientIt(Phase.CREATE); - } - - @AfterAll - static void afterAll() { - configK8sClientIt(Phase.DELETE); - } - - @Test - void testSecretReload() throws Exception { - Commons.assertReloadLogStatements("added secret informer for namespace", - "added configmap informer for namespace", DEPLOYMENT_NAME); - testSecretEventReload(); - - testAllOther(); - } - - private void testAllOther() throws Exception { - recreateSecret(); - patchOne(DEPLOYMENT_NAME, NAMESPACE, DOCKER_IMAGE); - testSecretReloadConfigDisabled(); - - recreateSecret(); - patchOne(DEPLOYMENT_NAME, NAMESPACE, DOCKER_IMAGE); - testDataChangesInSecretsReload(K3S, DEPLOYMENT_NAME); - } - - void testSecretReloadConfigDisabled() throws Exception { - Commons.assertReloadLogStatements("added secret informer for namespace", - "added configmap informer for namespace", DEPLOYMENT_NAME); - testSecretEventReload(); - } - - void testSecretEventReload() throws Exception { - - WebClient.Builder builder = builder(); - WebClient secretClient = builder.baseUrl(PROPERTY_URL).build(); - - await().timeout(Duration.ofSeconds(120)) - .pollInterval(Duration.ofSeconds(2)) - .until(() -> secretClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block() - .equals("initial")); - - V1Secret v1Secret = (V1Secret) util.yaml("secret.yaml"); - Map secretData = v1Secret.getData(); - secretData.replace(Constants.APPLICATION_PROPERTIES, "from.properties.key: after-change".getBytes()); - v1Secret.setData(secretData); - coreV1Api.replaceNamespacedSecret("event-reload", NAMESPACE, v1Secret, null, null, null, null); - - await().timeout(Duration.ofSeconds(120)) - .pollInterval(Duration.ofSeconds(2)) - .until(() -> secretClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block() - .equals("after-change")); - } - - private void recreateSecret() { - V1Secret secret = (V1Secret) util.yaml("secret.yaml"); - util.deleteAndWait(NAMESPACE, null, secret); - util.createAndWait(NAMESPACE, null, secret); - } - - private static void configK8sClientIt(Phase phase) { - V1Deployment deployment = (V1Deployment) util.yaml("deployment-with-secret.yaml"); - V1Service service = (V1Service) util.yaml("service.yaml"); - V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml"); - V1Secret secret = (V1Secret) util.yaml("secret.yaml"); - - if (phase.equals(Phase.CREATE)) { - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - util.createAndWait(NAMESPACE, null, secret); - } - else if (phase.equals(Phase.DELETE)) { - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - util.deleteAndWait(NAMESPACE, null, secret); - } - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadITUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadITUtil.java deleted file mode 100644 index b5b3039a..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/java/org/springframework/cloud/kubernetes/k8s/client/reload/secret/K8sClientSecretsReloadITUtil.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.k8s.client.reload.secret; - -import java.time.Duration; -import java.util.Map; -import java.util.Objects; - -import reactor.netty.http.client.HttpClient; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithReplace; - -/** - * @author wind57 - */ -final class K8sClientSecretsReloadITUtil { - - private static final Map POD_LABELS = Map.of("app", "spring-k8s-client-reload"); - - private static final String BODY_ONE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-k8s-client-reload", - "image": "image_name_here", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/liveness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/actuator/health/readiness", - "port": 8080, - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 1 - }, - "env": [ - { - "name": "SPRING_CLOUD_KUBERNETES_CONFIG_ENABLED", - "value": "FALSE" - }, - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "with-secret" - } - ] - }] - } - } - } - } - """; - - private K8sClientSecretsReloadITUtil() { - - } - - static WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - static RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(60, Duration.ofSeconds(2)).filter(Objects::nonNull); - } - - static void patchOne(String deploymentName, String namespace, String imageName) { - patchWithReplace(imageName, deploymentName, namespace, BODY_ONE, POD_LABELS); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/ingress.yaml deleted file mode 100644 index 4beef36c..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/ingress.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: spring-k8s-client-ingress-reload - namespace: default -spec: - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: spring-k8s-client-reload - port: - number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/left-configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/left-configmap.yaml deleted file mode 100644 index a72d8af5..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/left-configmap.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: left-configmap - namespace: left -data: - left.value: "left-initial" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/configmap-mount.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/configmap.yaml similarity index 82% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/configmap-mount.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/configmap.yaml index f2ea29a5..dd14c314 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/configmap-mount.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/configmap.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: poll-reload-as-mount + name: poll-reload namespace: default data: application.properties: | diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/deployment-with-secret.yaml similarity index 62% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/deployment-with-secret.yaml index dde4e3ff..1ac93cf9 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/deployment-with-secret.yaml @@ -1,19 +1,19 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: spring-k8s-client-reload + name: spring-cloud-kubernetes-k8s-client-reload spec: selector: matchLabels: - app: spring-k8s-client-reload + app: spring-cloud-kubernetes-k8s-client-reload template: metadata: labels: - app: spring-k8s-client-reload + app: spring-cloud-kubernetes-k8s-client-reload spec: serviceAccountName: spring-cloud-kubernetes-serviceaccount containers: - - name: spring-k8s-client-reload + - name: spring-cloud-kubernetes-k8s-client-reload image: docker.io/springcloud/spring-cloud-kubernetes-k8s-client-reload imagePullPolicy: IfNotPresent readinessProbe: @@ -27,9 +27,19 @@ spec: ports: - containerPort: 8080 env: - - name: SPRING_PROFILES_ACTIVE - value: one - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD value: DEBUG + - name: SPRING_PROFILES_ACTIVE + value: "with-bootstrap" - name: SPRING_CLOUD_BOOTSTRAP_ENABLED - value: "TRUE" + value: TRUE + + volumeMounts: + - mountPath: /tmp + name: "secret-volume" + + volumes: + - name: "secret-volume" + secret: + defaultMode: 420 + secretName: secret-reload diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/deployment-with-secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/deployment.yaml similarity index 54% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/deployment-with-secret.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/deployment.yaml index e1e6ac3f..22184fa4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/deployment-with-secret.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/deployment.yaml @@ -1,19 +1,19 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: spring-k8s-client-reload + name: spring-cloud-kubernetes-k8s-client-reload spec: selector: matchLabels: - app: spring-k8s-client-reload + app: spring-cloud-kubernetes-k8s-client-reload template: metadata: labels: - app: spring-k8s-client-reload + app: spring-cloud-kubernetes-k8s-client-reload spec: serviceAccountName: spring-cloud-kubernetes-serviceaccount containers: - - name: spring-k8s-client-reload + - name: spring-cloud-kubernetes-k8s-client-reload image: docker.io/springcloud/spring-cloud-kubernetes-k8s-client-reload imagePullPolicy: IfNotPresent readinessProbe: @@ -27,7 +27,21 @@ spec: ports: - containerPort: 8080 env: + - name: SPRING_PROFILES_ACTIVE + value: mount - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD value: DEBUG - - name: SPRING_PROFILES_ACTIVE - value: "with-secret" + - name: SPRING_CLOUD_BOOTSTRAP_ENABLED + value: FALSE + - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS + value: DEBUG + + volumeMounts: + - mountPath: /tmp + name: "config-map-volume" + + volumes: + - name: "config-map-volume" + configMap: + defaultMode: 420 + name: "poll-reload" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/secret.yaml similarity index 88% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/secret.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/secret.yaml index 9ad19aad..26c9c3e2 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/secret.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/secret.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Secret metadata: - name: event-reload + name: secret-reload namespace: default data: # from.properties.key=initial diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service-with-secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service-with-secret.yaml new file mode 100644 index 00000000..9a8302f3 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service-with-secret.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-k8s-client-reload + name: spring-cloud-kubernetes-k8s-client-reload +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + nodePort: 32321 + selector: + app: spring-cloud-kubernetes-k8s-client-reload + type: NodePort diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service.yaml new file mode 100644 index 00000000..9a8302f3 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/mount/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-k8s-client-reload + name: spring-cloud-kubernetes-k8s-client-reload +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + nodePort: 32321 + selector: + app: spring-cloud-kubernetes-k8s-client-reload + type: NodePort diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/service.yaml deleted file mode 100644 index fae9311f..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-reload/src/test/resources/service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-k8s-client-reload - name: spring-k8s-client-reload -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: spring-k8s-client-reload - type: ClusterIP