Skip to content

Commit

Permalink
Merge branch 'main' into move-to-a-common-configuration-for-health
Browse files Browse the repository at this point in the history
  • Loading branch information
wind57 committed Jan 8, 2025
2 parents 0890fd8 + a0756c6 commit 5ab73dc
Show file tree
Hide file tree
Showing 53 changed files with 1,190 additions and 1,316 deletions.
2 changes: 1 addition & 1 deletion docs/antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ nav:
ext:
collector:
run:
command: ./mvnw --no-transfer-progress -B process-resources -Pdocs -pl docs -Dantora-maven-plugin.phase=none -Dgenerate-docs.phase=none -Dgenerate-readme.phase=none -Dgenerate-cloud-resources.phase=none -Dmaven-dependency-plugin-for-docs.phase=none -Dmaven-dependency-plugin-for-docs-classes.phase=none -DskipTests
command: ./mvnw --no-transfer-progress -B process-resources -Pdocs -pl docs -Dantora-maven-plugin.phase=none -Dgenerate-docs.phase=none -Dgenerate-readme.phase=none -Dgenerate-cloud-resources.phase=none -Dmaven-dependency-plugin-for-docs.phase=none -Dmaven-dependency-plugin-for-docs-classes.phase=none -DskipTests -DdisableConfigurationProperties
local: true
scan:
dir: ./target/classes/antora-resources/
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"dependencies": {
"antora": "3.2.0-alpha.6",
"antora": "3.2.0-alpha.8",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.1",
"@asciidoctor/tabs": "1.0.0-beta.6",
Expand Down
36 changes: 22 additions & 14 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,32 @@
<!-- Don't upload docs jar to central / repo.spring.io -->
<maven-deploy-plugin-default.phase>none</maven-deploy-plugin-default.phase>
</properties>
<!-- <dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8-all</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8-config</artifactId>
</dependency>
</dependencies> -->
<build>
<sourceDirectory>src/main/asciidoc</sourceDirectory>
</build>
<profiles>
<profile>
<id>enable-configuration-properties</id>
<activation>
<property>
<name>!disableConfigurationProperties</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8-all</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8-config</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>docs</id>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.stubbing.Scenario;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.JSON;
Expand Down Expand Up @@ -59,8 +61,10 @@
import org.springframework.mock.env.MockEnvironment;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;

/**
Expand All @@ -81,7 +85,11 @@ class EventReloadConfigMapTest {

private static final String NAMESPACE = "spring-k8s";

private static final boolean[] strategyCalled = new boolean[] { false };
private static final String SCENARIO_NAME = "reload-test";

private static final String PATH = "/api/v1/namespaces/spring-k8s/configmaps";

private static final AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false);

private static CoreV1Api coreV1Api;

Expand All @@ -98,6 +106,12 @@ static void setup() {
wireMockServer.start();
WireMock.configureFor("localhost", wireMockServer.port());

// something that the informer can work with. Since we do not care about this one
// in the test, we mock it to return a 500 as it does not matter anyway.
stubFor(get(urlPathMatching(PATH)).withQueryParam("resourceVersion", equalTo("0"))
.withQueryParam("watch", equalTo("false"))
.willReturn(aResponse().withStatus(500).withBody("Error From Informer")));

ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
client.setDebugging(true);
MOCK_STATIC.when(KubernetesClientUtils::createApiClientForInformerClient).thenReturn(client);
Expand All @@ -107,30 +121,6 @@ static void setup() {
.thenReturn(NAMESPACE);
Configuration.setDefaultApiClient(client);
coreV1Api = new CoreV1Api();

String path = "/api/v1/namespaces/spring-k8s/configmaps";
V1ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of());
V1ConfigMapList listOne = new V1ConfigMapList().addItemsItem(configMapOne);

// needed so that our environment is populated with 'something'
// this call is done in the method that returns the AbstractEnvironment
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listOne)))
.inScenario("mine-test")
.willSetStateTo("go-to-fail"));

// first call will fail
stubFor(get(path).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))
.inScenario("mine-test")
.whenScenarioStateIs("go-to-fail")
.willSetStateTo("go-to-ok"));

// second call passes (change data so that reload is triggered)
configMapOne = configMap(CONFIG_MAP_NAME, Map.of("a", "b"));
listOne = new V1ConfigMapList().addItemsItem(configMapOne);
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listOne)))
.inScenario("mine-test")
.whenScenarioStateIs("go-to-ok")
.willSetStateTo("done"));
}

@AfterAll
Expand All @@ -149,6 +139,13 @@ static void after() {
*/
@Test
void test(CapturedOutput output) {

// first call will fail
stubFor(get(PATH).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))
.inScenario(SCENARIO_NAME)
.whenScenarioStateIs("go-to-fail")
.willSetStateTo("go-to-ok"));

V1ConfigMap configMapNotMine = configMap("not" + CONFIG_MAP_NAME, Map.of());
kubernetesClientEventBasedConfigMapChangeDetector.onEvent(configMapNotMine);

Expand All @@ -158,17 +155,25 @@ void test(CapturedOutput output) {
boolean two = output.getOut().contains("Failed to load source");
boolean three = output.getOut()
.contains("Reloadable condition was not satisfied, reload will not be triggered");
boolean updateStrategyNotCalled = !strategyCalled[0];
boolean updateStrategyNotCalled = !STRATEGY_CALLED.get();
return one && two && three && updateStrategyNotCalled;
});

// second call passes (change data so that reload is triggered)
V1ConfigMap configMap = configMap(CONFIG_MAP_NAME, Map.of("a", "b"));
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(configMap);
stubFor(get(PATH).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))
.inScenario(SCENARIO_NAME)
.whenScenarioStateIs("go-to-ok")
.willSetStateTo("done"));

// trigger the call again
V1ConfigMap configMapMine = configMap(CONFIG_MAP_NAME, Map.of());
kubernetesClientEventBasedConfigMapChangeDetector.onEvent(configMapMine);
Awaitility.await()
.atMost(Duration.ofSeconds(10))
.pollInterval(Duration.ofSeconds(1))
.until(() -> strategyCalled[0]);
.until(STRATEGY_CALLED::get);
}

private static V1ConfigMap configMap(String name, Map<String, String> data) {
Expand All @@ -193,6 +198,17 @@ VisibleKubernetesClientEventBasedConfigMapChangeDetector kubernetesClientEventBa
@Bean
@Primary
AbstractEnvironment environment() {

V1ConfigMap configMap = configMap(CONFIG_MAP_NAME, Map.of());
V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(configMap);

// needed so that our environment is populated with 'something'
// this call is done in the method that returns the AbstractEnvironment
stubFor(get(PATH).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))
.inScenario(SCENARIO_NAME)
.whenScenarioStateIs(Scenario.STARTED)
.willSetStateTo("go-to-fail"));

MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);

Expand All @@ -216,7 +232,7 @@ AbstractEnvironment environment() {
@Primary
ConfigReloadProperties configReloadProperties() {
return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("non-default"),
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("spring-k8s"),
false, Duration.ofSeconds(2));
}

Expand All @@ -237,7 +253,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
@Primary
ConfigurationUpdateStrategy configurationUpdateStrategy() {
return new ConfigurationUpdateStrategy("to-console", () -> {
strategyCalled[0] = true;
STRATEGY_CALLED.set(true);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.stubbing.Scenario;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.JSON;
Expand Down Expand Up @@ -61,8 +63,10 @@
import org.springframework.mock.env.MockEnvironment;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;

/**
Expand All @@ -83,7 +87,11 @@ class EventReloadSecretTest {

private static final String NAMESPACE = "spring-k8s";

private static final boolean[] strategyCalled = new boolean[] { false };
private static final String PATH = "/api/v1/namespaces/spring-k8s/secrets";

private static final String SCENARIO_NAME = "reload-test";

private static final AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false);

private static CoreV1Api coreV1Api;

Expand All @@ -100,6 +108,12 @@ static void setup() {
wireMockServer.start();
WireMock.configureFor("localhost", wireMockServer.port());

// something that the informer can work with. Since we do not care about this one
// in the test, we mock it to return a 500 as it does not matter anyway.
stubFor(get(urlPathMatching(PATH)).withQueryParam("resourceVersion", equalTo("0"))
.withQueryParam("watch", equalTo("false"))
.willReturn(aResponse().withStatus(500).withBody("Error From Informer")));

ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
client.setDebugging(true);
MOCK_STATIC.when(KubernetesClientUtils::createApiClientForInformerClient).thenReturn(client);
Expand All @@ -109,30 +123,6 @@ static void setup() {
.thenReturn(NAMESPACE);
Configuration.setDefaultApiClient(client);
coreV1Api = new CoreV1Api();

String path = "/api/v1/namespaces/spring-k8s/secrets";
V1Secret secretOne = secret(SECRET_NAME, Map.of());
V1SecretList listOne = new V1SecretList().addItemsItem(secretOne);

// needed so that our environment is populated with 'something'
// this call is done in the method that returns the AbstractEnvironment
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listOne)))
.inScenario("mine-test")
.willSetStateTo("go-to-fail"));

// first call will fail
stubFor(get(path).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))
.inScenario("mine-test")
.whenScenarioStateIs("go-to-fail")
.willSetStateTo("go-to-ok"));

// second call passes (change data so that reload is triggered)
secretOne = secret(SECRET_NAME, Map.of("a", "b"));
listOne = new V1SecretList().addItemsItem(secretOne);
stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listOne)))
.inScenario("mine-test")
.whenScenarioStateIs("go-to-ok")
.willSetStateTo("done"));
}

@AfterAll
Expand All @@ -151,6 +141,12 @@ static void after() {
*/
@Test
void test(CapturedOutput output) {
// first call will fail
stubFor(get(PATH).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))
.inScenario(SCENARIO_NAME)
.whenScenarioStateIs("go-to-fail")
.willSetStateTo("go-to-ok"));

V1Secret secretNotMine = secret("not" + SECRET_NAME, Map.of());
kubernetesClientEventBasedSecretsChangeDetector.onEvent(secretNotMine);

Expand All @@ -160,17 +156,25 @@ void test(CapturedOutput output) {
boolean two = output.getOut().contains("Failed to load source");
boolean three = output.getOut()
.contains("Reloadable condition was not satisfied, reload will not be triggered");
boolean updateStrategyNotCalled = !strategyCalled[0];
boolean updateStrategyNotCalled = !STRATEGY_CALLED.get();
return one && two && three && updateStrategyNotCalled;
});

// second call passes (change data so that reload is triggered)
V1Secret secret = secret(SECRET_NAME, Map.of("a", "b"));
V1SecretList secretList = new V1SecretList().addItemsItem(secret);
stubFor(get(PATH).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))
.inScenario(SCENARIO_NAME)
.whenScenarioStateIs("go-to-ok")
.willSetStateTo("done"));

// trigger the call again
V1Secret secretMine = secret(SECRET_NAME, Map.of());
kubernetesClientEventBasedSecretsChangeDetector.onEvent(secretMine);
Awaitility.await()
.atMost(Duration.ofSeconds(10))
.pollInterval(Duration.ofSeconds(1))
.until(() -> strategyCalled[0]);
.until(STRATEGY_CALLED::get);
}

private static V1Secret secret(String name, Map<String, String> data) {
Expand Down Expand Up @@ -199,6 +203,17 @@ VisibleKubernetesClientEventBasedSecretsChangeDetector kubernetesClientEventBase
@Bean
@Primary
AbstractEnvironment environment() {

// needed so that our environment is populated with 'something'
// this call is done in the method that returns the AbstractEnvironment
V1Secret secret = secret(SECRET_NAME, Map.of());
V1SecretList secretList = new V1SecretList().addItemsItem(secret);

stubFor(get(PATH).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))
.inScenario(SCENARIO_NAME)
.whenScenarioStateIs(Scenario.STARTED)
.willSetStateTo("go-to-fail"));

MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);

Expand All @@ -221,7 +236,7 @@ AbstractEnvironment environment() {
@Primary
ConfigReloadProperties configReloadProperties() {
return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("non-default"),
ConfigReloadProperties.ReloadDetectionMode.POLLING, Duration.ofMillis(2000), Set.of("spring-k8s"),
false, Duration.ofSeconds(2));
}

Expand All @@ -242,7 +257,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
@Primary
ConfigurationUpdateStrategy configurationUpdateStrategy() {
return new ConfigurationUpdateStrategy("to-console", () -> {
strategyCalled[0] = true;
STRATEGY_CALLED.set(true);
});
}

Expand Down
Loading

0 comments on commit 5ab73dc

Please sign in to comment.