Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion spring-cloud-kubernetes-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<properties>
<kubernetes-fabric8-client.version>6.9.2</kubernetes-fabric8-client.version>
<kubernetes-native-client.version>19.0.2</kubernetes-native-client.version>
<wiremock.version>3.4.2</wiremock.version>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor upgrade, safe since it is used only in tests

<wiremock.version>3.9.1</wiremock.version>
</properties>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.utils.Serialization;
import org.assertj.core.api.Assertions;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using assertj assertions

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Container;
Expand Down Expand Up @@ -106,7 +106,7 @@ void test() {
.block();

// istio profile is present
Assertions.assertTrue(result.contains("istio"));
Assertions.assertThat(result).contains("istio");
}

private static void appManifests(Phase phase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-cloud-kubernetes-k8s-client-configuration-watcher</artifactId>
<packaging>jar</packaging>

<properties>
<wiremock.version>3.4.2</wiremock.version>
<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
<spring-boot.build-image.skip>true</spring-boot.build-image.skip>
</properties>

<dependencies>
Expand All @@ -40,7 +40,6 @@
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>

Expand All @@ -67,29 +66,5 @@
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>build-image</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
<execution>
<id>repackage</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,48 +16,38 @@

package org.springframework.cloud.kubernetes.configuration.watcher;

import java.net.SocketTimeoutException;
import java.time.Duration;
import java.util.List;

import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1EnvVar;
import io.kubernetes.client.openapi.models.V1Service;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Container;
import org.testcontainers.k3s.K3sContainer;

import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Images;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;

import static org.awaitility.Awaitility.await;
import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME;
import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.configureWireMock;
import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.createConfigMap;
import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.deleteConfigMap;
import static org.springframework.cloud.kubernetes.configuration.watcher.TestUtil.verifyActuatorCalled;

/**
* @author Ryan Baxter
*/
class ActuatorRefreshIT {

private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher";

private static final String WIREMOCK_HOST = "localhost";

private static final String WIREMOCK_PATH = "/";

private static final int WIREMOCK_PORT = 80;

private static final String NAMESPACE = "default";

private static final String DOCKER_IMAGE = "docker.io/springcloud/" + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME + ":"
+ Commons.pomVersion();

private static final K3sContainer K3S = Commons.container();

private static Util util;
Expand All @@ -67,7 +57,6 @@ static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);

Images.loadWiremock(K3S);

util = new Util(K3S);
Expand All @@ -92,129 +81,45 @@ void after() {
}

/*
* this test loads uses two services: wiremock on port 8080 and configuration-watcher
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to simplify tests a bit here, but did not change the logical behind them at all.

I did drop one overlapping tests here though :)

* on port 8888. we deploy configuration-watcher first and configure it via a
* configmap with the same name. then, we mock the call to actuator/refresh endpoint
* and deploy a new configmap: "service-wiremock", this in turn will trigger that
* This test loads two services: wiremock on port 8080 and configuration-watcher on
* port 8888. We deploy configuration-watcher first and configure its env variables
* that we need for this test. Then, we mock the call to actuator/refresh endpoint and
* deploy a new configmap: "service-wiremock". Because This in turn will trigger a
* refresh that we capture and assert for.
*/
// curl <WIREMOCK_POD_IP>:8080/__admin/mappings
@Test
void testActuatorRefresh() {

WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT);
await().timeout(Duration.ofSeconds(60))
.ignoreException(SocketTimeoutException.class)
.until(() -> WireMock
.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh"))
.willReturn(WireMock.aResponse().withBody("{}").withStatus(200)))
.getResponse()
.wasConfigured());

createConfigMap();

// Wait a bit before we verify
await().atMost(Duration.ofSeconds(30))
.until(() -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh")))
.isEmpty());
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh")));

deleteConfigMap();

// the other test
testActuatorRefreshReloadDisabled();

}

/*
* same test as above, but reload is disabled.
*/
void testActuatorRefreshReloadDisabled() {

TestUtil.patchForDisabledReload(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, DOCKER_IMAGE);

WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT);
await().timeout(Duration.ofSeconds(60))
.until(() -> WireMock
.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh"))
.willReturn(WireMock.aResponse().withBody("{}").withStatus(200)))
.getResponse()
.wasConfigured());

createConfigMap();

// Wait a bit before we verify
await().atMost(Duration.ofSeconds(30))
.until(() -> !WireMock.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh")))
.isEmpty());
configureWireMock();
createConfigMap(util, NAMESPACE);
verifyActuatorCalled(1);

Commons.waitForLogStatement("creating NOOP strategy because reload is disabled", K3S,
SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME);

// nothing related to 'ConfigReloadUtil' is present in logs
// this proves that once we disable reload everything still works
Assertions.assertFalse(logs().contains("ConfigReloadUtil"));
WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh")));

deleteConfigMap();

deleteConfigMap(util, NAMESPACE);
}

private static void configWatcher(Phase phase) {
V1ConfigMap configMap = (V1ConfigMap) util
.yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
V1Deployment deployment = (V1Deployment) util
.yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-deployment.yaml");
V1Service service = (V1Service) util
.yaml("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml");

List<V1EnvVar> envVars = List.of(
new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_REFRESHDELAY").value("0"),
new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_RELOAD_ENABLED").value("FALSE"),
new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CONFIGURATION_WATCHER")
.value("DEBUG"));

deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);

if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, configMap, null);
util.createAndWait(NAMESPACE, null, deployment, service, null, true);
}
else {
util.deleteAndWait(NAMESPACE, configMap, null);
util.deleteAndWait(NAMESPACE, deployment, service, null);
}

}

// Create new configmap to trigger controller to signal app to refresh
private void createConfigMap() {
V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata()
.withName("service-wiremock")
.addToLabels("spring.cloud.kubernetes.config", "true")
.endMetadata()
.addToData("foo", "bar")
.build();
util.createAndWait(NAMESPACE, configMap, null);
}

private void deleteConfigMap() {
V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata()
.withName("service-wiremock")
.addToLabels("spring.cloud.kubernetes.config", "true")
.endMetadata()
.addToData("foo", "bar")
.build();
util.deleteAndWait(NAMESPACE, configMap, null);
}

private String logs() {
try {
String appPodName = K3S
.execInContainer("sh", "-c",
"kubectl get pods -l app=" + SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME
+ " -o=name --no-headers | tr -d '\n'")
.getStdout();

Container.ExecResult execResult = K3S.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim());
return execResult.getStdout();
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

}
Loading
Loading