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
- * - 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