diff --git a/docs/antora.yml b/docs/antora.yml index 8ac9d3a032..e6ce695151 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -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/ diff --git a/docs/package.json b/docs/package.json index 9e19d2ea18..ca5b2ce837 100644 --- a/docs/package.json +++ b/docs/package.json @@ -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", diff --git a/docs/pom.xml b/docs/pom.xml index c54ea81bad..60d80bd7da 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -21,24 +21,32 @@ none - src/main/asciidoc + + enable-configuration-properties + + + !disableConfigurationProperties + + + + + ${project.groupId} + spring-cloud-starter-kubernetes-fabric8 + + + ${project.groupId} + spring-cloud-starter-kubernetes-fabric8-all + + + ${project.groupId} + spring-cloud-starter-kubernetes-fabric8-config + + + docs diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadConfigMapTest.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadConfigMapTest.java index cfdbfb9f2d..7d30a2a6c9 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadConfigMapTest.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadConfigMapTest.java @@ -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; @@ -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; /** @@ -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; @@ -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); @@ -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 @@ -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); @@ -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 data) { @@ -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); @@ -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)); } @@ -237,7 +253,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadSecretTest.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadSecretTest.java index 86e3b09ed0..5cc87004b0 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadSecretTest.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/EventReloadSecretTest.java @@ -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; @@ -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; /** @@ -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; @@ -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); @@ -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 @@ -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); @@ -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 data) { @@ -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); @@ -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)); } @@ -242,7 +257,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadConfigMapTest.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadConfigMapTest.java index d0e2bdc0d4..348ac5cc2d 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadConfigMapTest.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadConfigMapTest.java @@ -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; @@ -72,18 +74,22 @@ @ExtendWith(OutputCaptureExtension.class) class PollingReloadConfigMapTest { - private static WireMockServer wireMockServer; - private static final boolean FAIL_FAST = false; private static final String CONFIG_MAP_NAME = "mine"; + private static final String PATH = "/api/v1/namespaces/spring-k8s/configmaps"; + 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 AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false); private static CoreV1Api coreV1Api; + private static WireMockServer wireMockServer; + @BeforeAll static void setup() { wireMockServer = new WireMockServer(options().dynamicPort()); @@ -95,30 +101,6 @@ static void setup() { client.setDebugging(true); 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("my-test") - .willSetStateTo("go-to-fail")); - - // first reload call fails - stubFor(get(path).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")) - .inScenario("my-test") - .whenScenarioStateIs("go-to-fail") - .willSetStateTo("go-to-ok")); - - // second reload call passes - V1ConfigMap configMapTwo = configMap(CONFIG_MAP_NAME, Map.of("a", "b")); - V1ConfigMapList listTwo = new V1ConfigMapList().addItemsItem(configMapTwo); - stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listTwo))) - .inScenario("my-test") - .whenScenarioStateIs("go-to-ok")); - } @AfterAll @@ -137,21 +119,40 @@ static void after() { disabledReason = "failing on jenkins") @Test void test(CapturedOutput output) { + + // first reload call fails + stubFor(get(PATH).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")) + .inScenario(SCENARIO_NAME) + .whenScenarioStateIs("go-to-fail") + .willSetStateTo("go-to-ok")); + // we fail while reading 'configMapOne' Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> { boolean one = output.getOut().contains("Failure in reading named sources"); 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(); + System.out.println("one: " + one + " two: " + two + " three: " + three + " updateStrategyNotCalled: " + + updateStrategyNotCalled); return one && two && three && updateStrategyNotCalled; }); + // second reload call passes + V1ConfigMap configMapTwo = configMap(CONFIG_MAP_NAME, Map.of("a", "b")); + V1ConfigMapList listTwo = new V1ConfigMapList().addItemsItem(configMapTwo); + stubFor(get(PATH).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listTwo))) + .inScenario(SCENARIO_NAME) + .whenScenarioStateIs("go-to-ok") + .willSetStateTo("done")); + + System.out.println("first assertion passed"); + // it passes while reading 'configMapTwo' Awaitility.await() - .atMost(Duration.ofSeconds(10)) + .atMost(Duration.ofSeconds(20)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> strategyCalled[0]); + .until(STRATEGY_CALLED::get); } private static V1ConfigMap configMap(String name, Map data) { @@ -176,6 +177,17 @@ PollingConfigMapChangeDetector pollingConfigMapChangeDetector(AbstractEnvironmen @Bean @Primary AbstractEnvironment environment() { + + 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(SCENARIO_NAME) + .whenScenarioStateIs(Scenario.STARTED) + .willSetStateTo("go-to-fail")); + MockEnvironment mockEnvironment = new MockEnvironment(); mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE); @@ -219,7 +231,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadSecretTest.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadSecretTest.java index b15c12b783..f15f5fc119 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadSecretTest.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload_it/PollingReloadSecretTest.java @@ -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; @@ -74,18 +76,22 @@ @ExtendWith(OutputCaptureExtension.class) class PollingReloadSecretTest { - private static WireMockServer wireMockServer; - private static final boolean FAIL_FAST = false; private static final String SECRET_NAME = "mine"; + private static final String PATH = "/api/v1/namespaces/spring-k8s/secrets"; + 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 AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false); private static CoreV1Api coreV1Api; + private static WireMockServer wireMockServer; + @BeforeAll static void setup() { wireMockServer = new WireMockServer(options().dynamicPort()); @@ -97,29 +103,6 @@ static void setup() { client.setDebugging(true); 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("my-test") - .willSetStateTo("go-to-fail")); - - // first reload call fails - stubFor(get(path).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")) - .inScenario("my-test") - .whenScenarioStateIs("go-to-fail") - .willSetStateTo("go-to-ok")); - - V1Secret secretTwo = secret(SECRET_NAME, Map.of("a", "b")); - V1SecretList listTwo = new V1SecretList().addItemsItem(secretTwo); - stubFor(get(path).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listTwo))) - .inScenario("my-test") - .whenScenarioStateIs("go-to-ok")); - } @AfterAll @@ -138,21 +121,35 @@ static void after() { disabledReason = "failing on jenkins") @Test void test(CapturedOutput output) { + + // first reload call fails + stubFor(get(PATH).willReturn(aResponse().withStatus(500).withBody("Internal Server Error")) + .inScenario(SCENARIO_NAME) + .whenScenarioStateIs("go-to-fail") + .willSetStateTo("go-to-ok")); + // we fail while reading 'secretOne' Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> { boolean one = output.getOut().contains("Failure in reading named sources"); 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; }); + V1Secret secretTwo = secret(SECRET_NAME, Map.of("a", "b")); + V1SecretList listTwo = new V1SecretList().addItemsItem(secretTwo); + stubFor(get(PATH).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(listTwo))) + .inScenario(SCENARIO_NAME) + .willSetStateTo("done") + .whenScenarioStateIs("go-to-ok")); + // it passes while reading 'secretTwo' Awaitility.await() - .atMost(Duration.ofSeconds(10)) + .atMost(Duration.ofSeconds(20)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> strategyCalled[0]); + .until(STRATEGY_CALLED::get); } private static V1Secret secret(String name, Map data) { @@ -182,6 +179,17 @@ PollingSecretsChangeDetector pollingSecretsChangeDetector(AbstractEnvironment en @Bean @Primary AbstractEnvironment environment() { + + 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(SCENARIO_NAME) + .whenScenarioStateIs(Scenario.STARTED) + .willSetStateTo("go-to-fail")); + MockEnvironment mockEnvironment = new MockEnvironment(); mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE); @@ -225,7 +233,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadUtil.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadUtil.java index 3986f6c825..c8df282107 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadUtil.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/reload/ConfigReloadUtil.java @@ -71,7 +71,7 @@ public static boolean reload(PropertySourceLocator locator, ConfigurableEnvironm boolean changed = changed(sourceFromK8s, existingSources); if (changed) { - LOG.info("Detected change in config maps/secrets, reload will ne triggered"); + LOG.info("Detected change in config maps/secrets, reload will be triggered"); return true; } else { diff --git a/spring-cloud-kubernetes-dependencies/pom.xml b/spring-cloud-kubernetes-dependencies/pom.xml index 82e45d8a89..724eb4a157 100644 --- a/spring-cloud-kubernetes-dependencies/pom.xml +++ b/spring-cloud-kubernetes-dependencies/pom.xml @@ -33,7 +33,7 @@ Spring Cloud Kubernetes Dependencies 6.13.4 - 19.0.1 + 19.0.2 3.4.2 diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java index 0f4c7c3db9..9702dd6572 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadConfigMapTest.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @@ -72,7 +73,7 @@ public class EventReloadConfigMapTest { private static KubernetesClient kubernetesClient; - private static final boolean[] strategyCalled = new boolean[] { false }; + private static final AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false); @BeforeAll static void beforeAll() { @@ -118,7 +119,7 @@ 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; }); @@ -132,7 +133,7 @@ void test(CapturedOutput output) { Awaitility.await() .atMost(Duration.ofSeconds(10)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> strategyCalled[0]); + .until(STRATEGY_CALLED::get); } private static ConfigMap configMap(String name, Map data) { @@ -197,7 +198,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java index 57a699bc5c..7515bba3f8 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/EventReloadSecretTest.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.Secret; @@ -75,7 +76,7 @@ class EventReloadSecretTest { private static KubernetesClient kubernetesClient; - private static final boolean[] strategyCalled = new boolean[] { false }; + private static final AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false); @BeforeAll static void beforeAll() { @@ -120,7 +121,7 @@ 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; }); @@ -134,7 +135,7 @@ void test(CapturedOutput output) { Awaitility.await() .atMost(Duration.ofSeconds(10)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> strategyCalled[0]); + .until(STRATEGY_CALLED::get); } private static Secret secret(String name, Map data) { @@ -204,7 +205,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java index a6a1aa08ac..0e6f0253c6 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadConfigMapTest.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @@ -70,33 +71,17 @@ class PollingReloadConfigMapTest { private static final String NAMESPACE = "spring-k8s"; + private static final String PATH = "/api/v1/namespaces/spring-k8s/configmaps"; + private static KubernetesMockServer kubernetesMockServer; private static KubernetesClient kubernetesClient; - private static final boolean[] strategyCalled = new boolean[] { false }; + private static final AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false); @BeforeAll static void beforeAll() { - kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0); - - // needed so that our environment is populated with 'something' - // this call is done in the method that returns the AbstractEnvironment - ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of()); - ConfigMap configMapTwo = configMap(CONFIG_MAP_NAME, Map.of("a", "b")); - String path = "/api/v1/namespaces/spring-k8s/configmaps"; - kubernetesMockServer.expect() - .withPath(path) - .andReturn(200, new ConfigMapListBuilder().withItems(configMapOne).build()) - .once(); - - kubernetesMockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); - - kubernetesMockServer.expect() - .withPath(path) - .andReturn(200, new ConfigMapListBuilder().withItems(configMapTwo).build()) - .once(); } /** @@ -111,20 +96,27 @@ static void beforeAll() { @Test void test(CapturedOutput output) { // we fail while reading 'configMapOne' - Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> { + kubernetesMockServer.expect().withPath(PATH).andReturn(500, "Internal Server Error").once(); + Awaitility.await().atMost(Duration.ofSeconds(20)).pollInterval(Duration.ofSeconds(1)).until(() -> { boolean one = output.getOut().contains("Failure in reading named sources"); 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; }); + ConfigMap configMapTwo = configMap(CONFIG_MAP_NAME, Map.of("a", "b")); + kubernetesMockServer.expect() + .withPath(PATH) + .andReturn(200, new ConfigMapListBuilder().withItems(configMapTwo).build()) + .once(); + // it passes while reading 'configMapTwo' Awaitility.await() - .atMost(Duration.ofSeconds(10)) + .atMost(Duration.ofSeconds(20)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> strategyCalled[0]); + .until(STRATEGY_CALLED::get); } private static ConfigMap configMap(String name, Map data) { @@ -148,6 +140,15 @@ PollingConfigMapChangeDetector pollingConfigMapChangeDetector(AbstractEnvironmen @Bean @Primary AbstractEnvironment environment() { + + // needed so that our environment is populated with 'something' + // this call is done in the method that returns the AbstractEnvironment + ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of()); + kubernetesMockServer.expect() + .withPath(PATH) + .andReturn(200, new ConfigMapListBuilder().withItems(configMapOne).build()) + .once(); + MockEnvironment mockEnvironment = new MockEnvironment(); mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE); @@ -190,7 +191,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java index 365cc668dc..4429b8ab38 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload_it/PollingReloadSecretTest.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.Secret; @@ -73,33 +74,17 @@ public class PollingReloadSecretTest { private static final String NAMESPACE = "spring-k8s"; + private static final String PATH = "/api/v1/namespaces/spring-k8s/secrets"; + private static KubernetesMockServer kubernetesMockServer; private static KubernetesClient kubernetesClient; - private static final boolean[] strategyCalled = new boolean[] { false }; + private static final AtomicBoolean STRATEGY_CALLED = new AtomicBoolean(false); @BeforeAll static void beforeAll() { - kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0); - - // needed so that our environment is populated with 'something' - // this call is done in the method that returns the AbstractEnvironment - Secret secretOne = secret(SECRET_NAME, Map.of()); - Secret secretTwo = secret(SECRET_NAME, Map.of("a", "b")); - String path = "/api/v1/namespaces/spring-k8s/secrets"; - kubernetesMockServer.expect() - .withPath(path) - .andReturn(200, new SecretListBuilder().withItems(secretOne).build()) - .once(); - - kubernetesMockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); - - kubernetesMockServer.expect() - .withPath(path) - .andReturn(200, new SecretListBuilder().withItems(secretTwo).build()) - .once(); } /** @@ -114,20 +99,27 @@ static void beforeAll() { @Test void test(CapturedOutput output) { // we fail while reading 'secretOne' - Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> { + kubernetesMockServer.expect().withPath(PATH).andReturn(500, "Internal Server Error").once(); + Awaitility.await().atMost(Duration.ofSeconds(20)).pollInterval(Duration.ofSeconds(1)).until(() -> { boolean one = output.getOut().contains("Failure in reading named sources"); 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; }); + Secret secretTwo = secret(SECRET_NAME, Map.of("a", "b")); + kubernetesMockServer.expect() + .withPath(PATH) + .andReturn(200, new SecretListBuilder().withItems(secretTwo).build()) + .once(); + // it passes while reading 'secretTwo' Awaitility.await() - .atMost(Duration.ofSeconds(10)) + .atMost(Duration.ofSeconds(20)) .pollInterval(Duration.ofSeconds(1)) - .until(() -> strategyCalled[0]); + .until(STRATEGY_CALLED::get); } private static Secret secret(String name, Map data) { @@ -155,6 +147,15 @@ PollingSecretsChangeDetector pollingSecretsChangeDetector(AbstractEnvironment en @Bean @Primary AbstractEnvironment environment() { + + // needed so that our environment is populated with 'something' + // this call is done in the method that returns the AbstractEnvironment + Secret secretOne = secret(SECRET_NAME, Map.of()); + kubernetesMockServer.expect() + .withPath(PATH) + .andReturn(200, new SecretListBuilder().withItems(secretOne).build()) + .once(); + MockEnvironment mockEnvironment = new MockEnvironment(); mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE); @@ -197,7 +198,7 @@ KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java index ef5d34fa59..b241a68f71 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java @@ -39,6 +39,7 @@ */ class Fabric8DiscoveryAllServicesIT extends Fabric8DiscoveryBase { + private static Service externalServiceName; @BeforeAll diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java index 0b385736e3..93ccf8ddd4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java @@ -57,19 +57,15 @@ protected static void beforeAll() { util = new Util(K3S); } - protected static KubernetesClient client() { - String kubeConfigYaml = K3S.getKubeConfigYaml(); - Config config = Config.fromKubeconfig(kubeConfigYaml); - return new KubernetesClientBuilder().withConfig(config).build(); - } - @TestConfiguration static class TestConfig { @Bean @Primary KubernetesClient kubernetesClient() { - return client(); + String kubeConfigYaml = K3S.getKubeConfigYaml(); + Config config = Config.fromKubeconfig(kubeConfigYaml); + return new KubernetesClientBuilder().withConfig(config).build(); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java index f7716bd6dd..1a8393f71c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java @@ -35,7 +35,6 @@ "spring.cloud.kubernetes.discovery.namespaces[1]=b-uat", "spring.cloud.kubernetes.discovery.filter=#root.metadata.namespace matches 'a-uat$'", "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG" - }) class Fabric8DiscoveryFilterMatchOneNamespaceIT extends Fabric8DiscoveryBase { diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java index 4872c2091e..83e84f74ea 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java @@ -35,7 +35,6 @@ "spring.cloud.kubernetes.discovery.namespaces[1]=b-uat", "spring.cloud.kubernetes.discovery.filter=#root.metadata.namespace matches '^.*uat$'", "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG" - }) class Fabric8DiscoveryFilterMatchTwoNamespacesIT extends Fabric8DiscoveryBase { diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioApp.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioApp.java index d5626db45d..58efc873a5 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioApp.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioApp.java @@ -23,7 +23,7 @@ * @author wind57 */ @SpringBootApplication -public class IstioApp { +class IstioApp { public static void main(String[] args) { SpringApplication.run(IstioApp.class, args); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioController.java index 09e27f99fb..8ed560b0b5 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioController.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-istio/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/istio/IstioController.java @@ -29,20 +29,20 @@ * @author wind57 */ @RestController -public class IstioController { +class IstioController { private final Environment environment; // not used, but just to prove that it is injected private final IstioClient istioClient; - public IstioController(Environment environment, IstioClient istioClient) { + IstioController(Environment environment, IstioClient istioClient) { this.environment = environment; this.istioClient = istioClient; } @GetMapping("/profiles") - public List profiles() { + List profiles() { return Arrays.asList(environment.getActiveProfiles()); } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapProperties.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapProperties.java index bb3fb709e1..5376e65c88 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapProperties.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapProperties.java @@ -22,15 +22,15 @@ * @author wind57 */ @ConfigurationProperties("from.properties") -public class ConfigMapProperties { +class ConfigMapProperties { private String key; - public String getKey() { + String getKey() { return key; } - public void setKey(String key1) { + void setKey(String key1) { this.key = key1; } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Controller.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Controller.java index 12469a7fff..5a190760cd 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Controller.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Controller.java @@ -23,51 +23,17 @@ * @author wind57 */ @RestController -public class Controller { - - private final LeftProperties leftProperties; - - private final RightProperties rightProperties; - - private final RightWithLabelsProperties rightWithLabelsProperties; +class Controller { private final ConfigMapProperties configMapProperties; - private final SecretProperties secretProperties; - - public Controller(LeftProperties leftProperties, RightProperties rightProperties, - RightWithLabelsProperties rightWithLabelsProperties, ConfigMapProperties configMapProperties, - SecretProperties secretProperties) { - this.leftProperties = leftProperties; - this.rightProperties = rightProperties; - this.rightWithLabelsProperties = rightWithLabelsProperties; + Controller(ConfigMapProperties configMapProperties) { this.configMapProperties = configMapProperties; - this.secretProperties = secretProperties; - } - - @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("/key") - public String key() { + String key() { return configMapProperties.getKey(); } - @GetMapping("/key-from-secret") - public String keyFromSecret() { - return secretProperties.getKey(); - } - } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-mount.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-mount.yaml index cd1765f5c8..a035e13da0 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-mount.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-mount.yaml @@ -14,4 +14,3 @@ spring: - /tmp/application.properties config: import: "kubernetes:" - diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-no-mount.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-no-mount.yaml index baf0f12d35..12bc7a05fd 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-no-mount.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-no-mount.yaml @@ -1,7 +1,3 @@ -logging: - level: - root: DEBUG - spring: application: name: poll-reload diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-one.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-one.yaml index 988bfb2127..041768896c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-one.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-one.yaml @@ -1,16 +1,14 @@ -logging: - level: - root: DEBUG - spring: application: name: event-reload cloud: kubernetes: + secrets: + enabled: false reload: enabled: true - strategy: shutdown + strategy: refresh mode: event namespaces: - - left + - right monitoring-config-maps: true diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-two.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-two.yaml index 43ae273796..1945840e95 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-two.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-two.yaml @@ -1,7 +1,3 @@ -logging: - level: - root: DEBUG - spring: application: name: event-reload @@ -9,8 +5,11 @@ spring: kubernetes: reload: enabled: true - strategy: shutdown + strategy: refresh mode: event namespaces: - right monitoring-config-maps: true + + main: + cloud-platform: kubernetes diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-with-secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-with-secret.yaml index bd33ead110..ffd1487b59 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-with-secret.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/application-with-secret.yaml @@ -1,7 +1,3 @@ -logging: - level: - root: DEBUG - spring: application: name: event-reload @@ -12,11 +8,18 @@ spring: reload: enabled: true monitoring-secrets: true - strategy: shutdown + strategy: refresh mode: event monitoring-configMaps: false + config: + enabled: false + enable-api: false secrets: enabled: true enable-api: true + # otherwise we can't refresh in the test + main: + cloud-platform: kubernetes + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-one.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-one.yaml index 9265fce12e..035e8e8410 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-one.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-one.yaml @@ -1,13 +1,13 @@ -logging: - level: - root: DEBUG - spring: cloud: kubernetes: config: sources: - - namespace: left - name: left-configmap - namespace: right name: right-configmap + + # otherwise on context refresh we lose this property + # and test fails, since beans are not wired. + main: + cloud-platform: kubernetes + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-three.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-three.yaml index 5c5f360585..2463e20a97 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-three.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-three.yaml @@ -1,15 +1,15 @@ -logging: - level: - root: DEBUG - spring: cloud: kubernetes: config: sources: - - namespace: left - name: left-configmap - namespace: right name: right-configmap - namespace: right name: right-configmap-with-label + + + # otherwise on context refresh we lose this property + # and test fails, since beans are not wired. + main: + cloud-platform: kubernetes diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-two.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-two.yaml index 9265fce12e..c4c058b62e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-two.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/main/resources/bootstrap-two.yaml @@ -1,13 +1,16 @@ -logging: - level: - root: DEBUG - spring: cloud: kubernetes: + secrets: + enabled: false config: sources: - namespace: left name: left-configmap - namespace: right name: right-configmap + + # otherwise on context refresh we lose this property + # and test fails, since beans are not wired. + main: + cloud-platform: kubernetes diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/BootstrapEnabledPollingReloadConfigMapMountDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/BootstrapEnabledPollingReloadConfigMapMountIT.java similarity index 64% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/BootstrapEnabledPollingReloadConfigMapMountDelegate.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/BootstrapEnabledPollingReloadConfigMapMountIT.java index 0b6c397967..144462bb8f 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/BootstrapEnabledPollingReloadConfigMapMountDelegate.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/BootstrapEnabledPollingReloadConfigMapMountIT.java @@ -23,18 +23,55 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.utils.Serialization; -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.fabric8_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.fabric8.client.reload.TestAssertions.builder; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.manifests; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.retrySpec; -final class BootstrapEnabledPollingReloadConfigMapMountDelegate { +/** + * @author wind57 + */ +class BootstrapEnabledPollingReloadConfigMapMountIT { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-reload"; + + private static final String NAMESPACE = "default"; + + private static final K3sContainer K3S = Commons.container(); + + private static Util util; + + private static KubernetesClient client; + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + util = new Util(K3S); + client = util.client(); + util.setUp(NAMESPACE); + manifests(Phase.CREATE, util, NAMESPACE); + } + + @AfterAll + static void afterAll() { + manifests(Phase.DELETE, util, NAMESPACE); + } /** *
@@ -50,27 +87,27 @@ final class BootstrapEnabledPollingReloadConfigMapMountDelegate {
 	 *     - our polling will then detect that change, and trigger a reload.
 	 * 
*/ - static void testPollingReloadConfigMapWithBootstrap(KubernetesClient client, Util util, K3sContainer container, - String appLabelValue) { + @Test + void test() { // (1) - Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", container, appLabelValue); + Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", K3S, IMAGE_NAME); // (2) - Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", container, - appLabelValue); + Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", K3S, + IMAGE_NAME); // (3) - WebClient webClient = TestUtil.builder().baseUrl("http://localhost/key").build(); + WebClient webClient = builder().baseUrl("http://localhost/key").build(); String result = webClient.method(HttpMethod.GET) .retrieve() .bodyToMono(String.class) - .retryWhen(TestUtil.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 - InputStream configMapStream = util.inputStream("configmap.yaml"); + InputStream configMapStream = util.inputStream("manifests/configmap.yaml"); ConfigMap configMap = Serialization.unmarshal(configMapStream, ConfigMap.class); configMap.setData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=as-mount-changed")); client.configMaps().inNamespace("default").resource(configMap).createOrReplace(); @@ -79,10 +116,9 @@ static void testPollingReloadConfigMapWithBootstrap(KubernetesClient client, Uti .until(() -> webClient.method(HttpMethod.GET) .retrieve() .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) + .retryWhen(retrySpec()) .block() .equals("as-mount-changed")); - } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapMountPollingReloadDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapMountPollingReloadDelegateIT.java similarity index 65% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapMountPollingReloadDelegate.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapMountPollingReloadDelegateIT.java index c0f5fc5d3f..c6a21e6500 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapMountPollingReloadDelegate.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/ConfigMapMountPollingReloadDelegateIT.java @@ -23,21 +23,55 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.utils.Serialization; -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.fabric8_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.fabric8.client.reload.TestAssertions.builder; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.manifests; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.retrySpec; /** * @author wind57 */ -final class ConfigMapMountPollingReloadDelegate { +class ConfigMapMountPollingReloadDelegateIT { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-reload"; + + private static final String NAMESPACE = "default"; + + private static final K3sContainer K3S = Commons.container(); + + private static Util util; + + private static KubernetesClient client; + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + util = new Util(K3S); + client = util.client(); + util.setUp(NAMESPACE); + manifests(Phase.CREATE, util, NAMESPACE); + } + + @AfterAll + static void afterAll() { + manifests(Phase.DELETE, util, NAMESPACE); + } /** *
@@ -54,27 +88,27 @@ final class ConfigMapMountPollingReloadDelegate {
 	 *     - our polling will then detect that change, and trigger a reload.
 	 * 
*/ - static void testConfigMapMountPollingReload(KubernetesClient client, Util util, K3sContainer container, - String appLabelValue) { + @Test + void test() { // (1) - Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", container, appLabelValue); + Commons.waitForLogStatement("paths property sources : [/tmp/application.properties]", K3S, IMAGE_NAME); // (2) - Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", container, - appLabelValue); + Commons.waitForLogStatement("will add file-based property source : /tmp/application.properties", K3S, + IMAGE_NAME); // (3) - WebClient webClient = TestUtil.builder().baseUrl("http://localhost/key").build(); + WebClient webClient = builder().baseUrl("http://localhost/key").build(); String result = webClient.method(HttpMethod.GET) .retrieve() .bodyToMono(String.class) - .retryWhen(TestUtil.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 - InputStream configMapStream = util.inputStream("configmap.yaml"); + InputStream configMapStream = util.inputStream("manifests/configmap.yaml"); ConfigMap configMap = Serialization.unmarshal(configMapStream, ConfigMap.class); configMap.setData(Map.of(Constants.APPLICATION_PROPERTIES, "from.properties.key=as-mount-changed")); client.configMaps().inNamespace("default").resource(configMap).createOrReplace(); @@ -83,10 +117,9 @@ static void testConfigMapMountPollingReload(KubernetesClient client, Util util, .until(() -> webClient.method(HttpMethod.GET) .retrieve() .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) + .retryWhen(retrySpec()) .block() .equals("as-mount-changed")); - } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/DataChangesInConfigMapReloadDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/DataChangesInConfigMapReloadDelegate.java deleted file mode 100644 index 26698fe617..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/DataChangesInConfigMapReloadDelegate.java +++ /dev/null @@ -1,119 +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.fabric8.client.reload; - -import java.time.Duration; -import java.util.Map; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.ConfigMapBuilder; -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.fabric8.kubernetes.client.KubernetesClient; -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; -import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestUtil.logs; -import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestUtil.replaceConfigMap; -import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestUtil.retrySpec; - -final class DataChangesInConfigMapReloadDelegate { - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-reload"; - - 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 testDataChangesInConfigMap(KubernetesClient client, K3sContainer container, String appLabelValue) { - Commons.assertReloadLogStatements("added configmap informer for namespace", - "added secret informer for namespace", IMAGE_NAME); - - WebClient webClient = builder().baseUrl("http://localhost/" + LEFT_NAMESPACE).build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(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 - ConfigMap configMap = new ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) - .withNamespace("left") - .withName("left-configmap") - .build()) - .withData(Map.of("left.value", "left-initial")) - .build(); - - replaceConfigMap(client, configMap, "left"); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = builder().baseUrl("http://localhost/" + LEFT_NAMESPACE).build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - return "left-initial".equals(innerResult); - }); - - String logs = logs(container, appLabelValue); - 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 ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) - .withNamespace("left") - .withName("left-configmap") - .build()) - .withData(Map.of("left.value", "left-after-change")) - .build(); - - replaceConfigMap(client, configMap, "left"); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = builder().baseUrl("http://localhost/" + LEFT_NAMESPACE).build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - return "left-after-change".equals(innerResult); - }); - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadBase.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadBase.java new file mode 100644 index 0000000000..4b71862666 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadBase.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2024 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.fabric8.client.reload; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.k3s.K3sContainer; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +/** + * @author wind57 + */ +@SpringBootTest(classes = { App.class }) +@ExtendWith(OutputCaptureExtension.class) +abstract class Fabric8EventReloadBase { + + protected static final K3sContainer K3S = Commons.container(); + + protected static Util util; + + @BeforeAll + protected static void beforeAll() { + K3S.start(); + util = new Util(K3S); + } + + @TestConfiguration + static class TestConfig { + + @Bean + @Primary + KubernetesClient kubernetesClient() { + String kubeConfigYaml = K3S.getKubeConfigYaml(); + Config config = Config.fromKubeconfig(kubeConfigYaml); + return new KubernetesClientBuilder().withConfig(config).build(); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadDataChangesInConfigMapIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadDataChangesInConfigMapIT.java new file mode 100644 index 0000000000..b39d08746a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadDataChangesInConfigMapIT.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2024 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.fabric8.client.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.assertReloadLogStatements; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.configMap; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.replaceConfigMap; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.main.cloud-platform=kubernetes", + "logging.level.org.springframework.cloud.kubernetes.fabric8.config.reload=debug", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles("one") +class Fabric8EventReloadDataChangesInConfigMapIT extends Fabric8EventReloadBase { + + private static final String NAMESPACE = "right"; + + private static ConfigMap configMap; + + @Autowired + private RightProperties properties; + + @Autowired + private KubernetesClient kubernetesClient; + + @BeforeAll + static void beforeAllLocal() { + InputStream rightConfigMapStream = util.inputStream("manifests/right-configmap.yaml"); + configMap = Serialization.unmarshal(rightConfigMapStream, ConfigMap.class); + + util.createNamespace(NAMESPACE); + configMap(Phase.CREATE, util, configMap, NAMESPACE); + } + + @AfterAll + static void afterAllLocal() { + configMap(Phase.DELETE, util, configMap, NAMESPACE); + util.deleteNamespace(NAMESPACE); + } + + /** + *
+	 *     - configMap with no labels and data: right.value = right-initial exists in namespace right
+	 *
+	 *     - then we change the configmap by adding a label, this in turn does not
+	 *       change the result, because the data has not changed.
+	 *
+	 *     - then we change data inside the config map, and we must see the updated value
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + + assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", + output); + + // we first read the initial value from configmap + assertThat(properties.getValue()).isEqualTo("right-initial"); + + // then deploy a new version of right-configmap, but without changing its data, + // only add a label + ConfigMap configMap = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) + .withNamespace(NAMESPACE) + .withName("right-configmap") + .build()) + .withData(Map.of("right.value", "right-initial")) + .build(); + + replaceConfigMap(kubernetesClient, configMap, NAMESPACE); + + await().atMost(Duration.ofSeconds(60)) + .pollDelay(Duration.ofSeconds(1)) + .until(() -> output.getOut().contains("ConfigMap right-configmap was updated in namespace right")); + + await().atMost(Duration.ofSeconds(60)) + .pollDelay(Duration.ofSeconds(1)) + .until(() -> output.getOut().contains("data in configmap has not changed, will not reload")); + + assertThat(properties.getValue()).isEqualTo("right-initial"); + + // change data + configMap = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withLabels(Map.of("new-label", "abc")) + .withNamespace(NAMESPACE) + .withName("right-configmap") + .build()) + .withData(Map.of("right.value", "right-after-change")) + .build(); + + replaceConfigMap(kubernetesClient, configMap, NAMESPACE); + + await().atMost(Duration.ofSeconds(60)).pollDelay(Duration.ofSeconds(1)).until(() -> { + String afterUpdateRightValue = properties.getValue(); + return afterUpdateRightValue.equals("right-after-change"); + }); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadIT.java deleted file mode 100644 index 13bda6d496..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadIT.java +++ /dev/null @@ -1,399 +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.fabric8.client.reload; - -import java.io.InputStream; -import java.time.Duration; -import java.util.Map; -import java.util.Set; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.ConfigMapBuilder; -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.fabric8.kubernetes.api.model.Secret; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.utils.Serialization; -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.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.fabric8_client.Util; -import org.springframework.http.HttpMethod; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion; - -/** - * @author wind57 - */ -class Fabric8EventReloadIT { - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-reload"; - - private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + pomVersion(); - - private static final String NAMESPACE = "default"; - - private static final K3sContainer K3S = Commons.container(); - - private static Util util; - - private static KubernetesClient client; - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - - util = new Util(K3S); - client = util.client(); - - util.createNamespace("left"); - util.createNamespace("right"); - util.setUpClusterWide(NAMESPACE, Set.of("left", "right")); - util.setUp(NAMESPACE); - - manifests(Phase.CREATE); - } - - @AfterAll - static void afterAll() { - util.deleteNamespace("left"); - util.deleteNamespace("right"); - - manifests(Phase.DELETE); - } - - /** - *
-	 *     - there are two namespaces : left and right
-	 *     - each of the namespaces has one configmap
-	 *     - we watch the "left" namespace, but make a change in the configmap in the right namespace
-	 *     - as such, no event is triggered and "left-configmap" stays as-is
-	 * 
- */ - @Test - void testInformFromOneNamespaceEventNotTriggered() { - Commons.assertReloadLogStatements("added configmap informer for namespace", - "added secret informer for namespace", IMAGE_NAME); - - WebClient webClient = TestUtil.builder().baseUrl("http://localhost/left").build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - - // we first read the initial value from the left-configmap - Assertions.assertEquals("left-initial", result); - - // then read the value from the right-configmap - webClient = TestUtil.builder().baseUrl("http://localhost/right").build(); - result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - Assertions.assertEquals("right-initial", result); - - // then deploy a new version of right-configmap - ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build()) - .withData(Map.of("right.value", "right-after-change")) - .build(); - - TestUtil.replaceConfigMap(client, rightConfigMapAfterChange, "right"); - - webClient = TestUtil.builder().baseUrl("http://localhost/left").build(); - - WebClient finalWebClient = webClient; - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(30)).until(() -> { - String innerResult = finalWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - // left configmap has not changed, no restart of app has happened - return "left-initial".equals(innerResult); - }); - - testInformFromOneNamespaceEventTriggered(); - testInform(); - testInformFromOneNamespaceEventTriggeredSecretsDisabled(); - testDataChangesInConfigMap(); - testConfigMapMountPollingReload(); - testPollingReloadConfigMapWithBootstrap(); - testSecretReload(); - } - - /** - *
-	 * - there are two namespaces : left and right
-	 * - each of the namespaces has one configmap
-	 * - we watch the "right" namespace and make a change in the configmap in the same
-	 * namespace
-	 * - as such, event is triggered and we see the updated value
-	 * 
- */ - void testInformFromOneNamespaceEventTriggered() { - - TestUtil.reCreateSources(util, client); - TestUtil.patchOne(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - - Commons.assertReloadLogStatements("added configmap informer for namespace", - "added secret informer for namespace", IMAGE_NAME); - - // read the value from the right-configmap - WebClient webClient = TestUtil.builder().baseUrl("http://localhost/right").build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - Assertions.assertEquals("right-initial", result); - - // then deploy a new version of right-configmap - ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build()) - .withData(Map.of("right.value", "right-after-change")) - .build(); - - TestUtil.replaceConfigMap(client, rightConfigMapAfterChange, "right"); - - String[] resultAfterChange = new String[1]; - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = TestUtil.builder().baseUrl("http://localhost/right").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - resultAfterChange[0] = innerResult; - return innerResult != null; - }); - Assertions.assertEquals("right-after-change", resultAfterChange[0]); - } - - /** - *
-	 * - there are two namespaces : left and right (though we do not care about the left
-	 * one)
-	 * - left has one configmap : left-configmap
-	 * - right has two configmaps: right-configmap, right-configmap-with-label
-	 * - we watch the "right" namespace, but enable tagging; which means that only
-	 * right-configmap-with-label triggers changes.
-	 * 
- */ - void testInform() { - - TestUtil.reCreateSources(util, client); - TestUtil.patchTwo(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - - Commons.assertReloadLogStatements("added configmap informer for namespace", - "added secret informer for namespace", IMAGE_NAME); - - // read the initial value from the right-configmap - WebClient rightWebClient = TestUtil.builder().baseUrl("http://localhost/right").build(); - String rightResult = rightWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - Assertions.assertEquals("right-initial", rightResult); - - // then read the initial value from the right-with-label-configmap - WebClient rightWithLabelWebClient = TestUtil.builder().baseUrl("http://localhost/with-label").build(); - String rightWithLabelResult = rightWithLabelWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - Assertions.assertEquals("right-with-label-initial", rightWithLabelResult); - - // then deploy a new version of right-configmap - ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build()) - .withData(Map.of("right.value", "right-after-change")) - .build(); - - TestUtil.replaceConfigMap(client, rightConfigMapAfterChange, "right"); - - // nothing changes in our app, because we are watching only labeled configmaps - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(30)).until(() -> { - String innerRightResult = rightWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - return "right-initial".equals(innerRightResult); - }); - - // then deploy a new version of right-with-label-configmap - ConfigMap rightWithLabelConfigMapAfterChange = new ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap-with-label").build()) - .withData(Map.of("right.with.label.value", "right-with-label-after-change")) - .build(); - - TestUtil.replaceConfigMap(client, rightWithLabelConfigMapAfterChange, "right"); - - // since we have changed a labeled configmap, app will restart and pick up the new - // value - String[] resultAfterChange = new String[1]; - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = TestUtil.builder().baseUrl("http://localhost/with-label").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - resultAfterChange[0] = innerResult; - return innerResult != null; - }); - Assertions.assertEquals("right-with-label-after-change", resultAfterChange[0]); - - // right-configmap now will see the new value also, but only because the other - // configmap has triggered the restart - rightResult = rightWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - Assertions.assertEquals("right-after-change", rightResult); - } - - /** - *
-	 * - there are two namespaces : left and right
-	 * - each of the namespaces has one configmap
-	 * - secrets are disabled
-	 * - we watch the "right" namespace and make a change in the configmap in the same
-	 * namespace
-	 * - as such, event is triggered and we see the updated value
-	 * 
- */ - void testInformFromOneNamespaceEventTriggeredSecretsDisabled() { - - TestUtil.reCreateSources(util, client); - TestUtil.patchThree(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - - Commons.assertReloadLogStatements("added configmap informer for namespace", - "added secret informer for namespace", IMAGE_NAME); - - // read the value from the right-configmap - WebClient webClient = TestUtil.builder().baseUrl("http://localhost/right").build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - Assertions.assertEquals("right-initial", result); - - // then deploy a new version of right-configmap - ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() - .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build()) - .withData(Map.of("right.value", "right-after-change")) - .build(); - - TestUtil.replaceConfigMap(client, rightConfigMapAfterChange, "right"); - - String[] resultAfterChange = new String[1]; - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = TestUtil.builder().baseUrl("http://localhost/right").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(TestUtil.retrySpec()) - .block(); - resultAfterChange[0] = innerResult; - return innerResult != null; - }); - Assertions.assertEquals("right-after-change", resultAfterChange[0]); - - } - - void testDataChangesInConfigMap() { - TestUtil.reCreateSources(util, client); - TestUtil.patchFour(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - DataChangesInConfigMapReloadDelegate.testDataChangesInConfigMap(client, K3S, IMAGE_NAME); - } - - void testConfigMapMountPollingReload() { - TestUtil.reCreateSources(util, client); - TestUtil.patchFive(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - ConfigMapMountPollingReloadDelegate.testConfigMapMountPollingReload(client, util, K3S, IMAGE_NAME); - } - - void testPollingReloadConfigMapWithBootstrap() { - TestUtil.reCreateSources(util, client); - TestUtil.patchSix(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - BootstrapEnabledPollingReloadConfigMapMountDelegate.testPollingReloadConfigMapWithBootstrap(client, util, K3S, - IMAGE_NAME); - } - - void testSecretReload() { - TestUtil.patchSeven(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - SecretsEventsReloadDelegate.testSecretReload(client, K3S, IMAGE_NAME); - } - - private static void manifests(Phase phase) { - - InputStream deploymentStream = util.inputStream("deployment.yaml"); - InputStream serviceStream = util.inputStream("service.yaml"); - InputStream ingressStream = util.inputStream("ingress.yaml"); - InputStream leftConfigMapStream = util.inputStream("left-configmap.yaml"); - InputStream rightConfigMapStream = util.inputStream("right-configmap.yaml"); - InputStream rightWithLabelConfigMapStream = util.inputStream("right-configmap-with-label.yaml"); - InputStream configMapAsStream = util.inputStream("configmap.yaml"); - InputStream secretAsStream = util.inputStream("secret.yaml"); - - Deployment deployment = Serialization.unmarshal(deploymentStream, Deployment.class); - - Service service = Serialization.unmarshal(serviceStream, Service.class); - Ingress ingress = Serialization.unmarshal(ingressStream, Ingress.class); - ConfigMap leftConfigMap = Serialization.unmarshal(leftConfigMapStream, ConfigMap.class); - ConfigMap rightConfigMap = Serialization.unmarshal(rightConfigMapStream, ConfigMap.class); - ConfigMap rightWithLabelConfigMap = Serialization.unmarshal(rightWithLabelConfigMapStream, ConfigMap.class); - ConfigMap configMap = Serialization.unmarshal(configMapAsStream, ConfigMap.class); - Secret secret = Serialization.unmarshal(secretAsStream, Secret.class); - - if (phase.equals(Phase.CREATE)) { - util.createAndWait("left", leftConfigMap, null); - util.createAndWait("right", rightConfigMap, null); - util.createAndWait("right", rightWithLabelConfigMap, null); - util.createAndWait(NAMESPACE, configMap, secret); - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - } - else { - util.deleteAndWait("left", leftConfigMap, null); - util.deleteAndWait("right", rightConfigMap, null); - util.deleteAndWait("right", rightWithLabelConfigMap, null); - util.deleteAndWait(NAMESPACE, configMap, secret); - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - } - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadInformIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadInformIT.java new file mode 100644 index 0000000000..ceeff4968e --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadInformIT.java @@ -0,0 +1,129 @@ +/* + * Copyright 2012-2024 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.fabric8.client.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.assertReloadLogStatements; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.configMap; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.replaceConfigMap; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.main.cloud-platform=kubernetes", + "logging.level.org.springframework.cloud.kubernetes.fabric8.config.reload=debug", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles("two") +class Fabric8EventReloadInformIT extends Fabric8EventReloadBase { + + private static final String LEFT_NAMESPACE = "left"; + + private static final String RIGHT_NAMESPACE = "right"; + + private static ConfigMap leftConfigMap; + + private static ConfigMap rightConfigMap; + + @Autowired + private LeftProperties leftProperties; + + @Autowired + private RightProperties rightProperties; + + @Autowired + private KubernetesClient kubernetesClient; + + @BeforeAll + static void beforeAllLocal() { + InputStream leftConfigMapStream = util.inputStream("manifests/left-configmap.yaml"); + InputStream rightConfigMapStream = util.inputStream("manifests/right-configmap.yaml"); + + leftConfigMap = Serialization.unmarshal(leftConfigMapStream, ConfigMap.class); + rightConfigMap = Serialization.unmarshal(rightConfigMapStream, ConfigMap.class); + + util.createNamespace(LEFT_NAMESPACE); + util.createNamespace(RIGHT_NAMESPACE); + + configMap(Phase.CREATE, util, leftConfigMap, LEFT_NAMESPACE); + configMap(Phase.CREATE, util, rightConfigMap, RIGHT_NAMESPACE); + } + + @AfterAll + static void afterAllLocal() { + configMap(Phase.DELETE, util, leftConfigMap, LEFT_NAMESPACE); + configMap(Phase.DELETE, util, rightConfigMap, RIGHT_NAMESPACE); + + util.deleteNamespace(LEFT_NAMESPACE); + util.deleteNamespace(RIGHT_NAMESPACE); + } + + /** + *
+	 * - there are two namespaces : left and right
+	 * - each of the namespaces has one configmap
+	 * - we watch the "right" namespace and make a change in the configmap in the same
+	 * namespace
+	 * - as such, event is triggered (refresh happens) and we see the updated value
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", + output); + + // first we read these with default values + assertThat(leftProperties.getValue()).isEqualTo("left-initial"); + assertThat(rightProperties.getValue()).isEqualTo("right-initial"); + + // then deploy a new version of right-configmap + ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace(RIGHT_NAMESPACE).withName("right-configmap").build()) + .withData(Map.of("right.value", "right-after-change")) + .build(); + + replaceConfigMap(kubernetesClient, rightConfigMapAfterChange, RIGHT_NAMESPACE); + + await().atMost(Duration.ofSeconds(60)).pollDelay(Duration.ofSeconds(1)).until(() -> { + String afterUpdateRightValue = rightProperties.getValue(); + return afterUpdateRightValue.equals("right-after-change"); + }); + + // left does not change + assertThat(leftProperties.getValue()).isEqualTo("left-initial"); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadInformWithLabelIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadInformWithLabelIT.java new file mode 100644 index 0000000000..044742bbe5 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadInformWithLabelIT.java @@ -0,0 +1,143 @@ +/* + * Copyright 2012-2024 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.fabric8.client.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.assertReloadLogStatements; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.configMap; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.replaceConfigMap; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.main.cloud-platform=kubernetes", + "logging.level.org.springframework.cloud.kubernetes.fabric8.config.reload=debug", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles("three") +class Fabric8EventReloadInformWithLabelIT extends Fabric8EventReloadBase { + + private static final String RIGHT_NAMESPACE = "right"; + + private static ConfigMap rightConfigMap; + + private static ConfigMap rightConfigMapWithLabel; + + @Autowired + private KubernetesClient kubernetesClient; + + @Autowired + private RightProperties rightProperties; + + @Autowired + private RightWithLabelsProperties rightWithLabelsProperties; + + @BeforeAll + static void beforeAllLocal() { + InputStream rightConfigMapStream = util.inputStream("manifests/right-configmap.yaml"); + InputStream rightConfigMapWithLabelStream = util.inputStream("manifests/right-configmap-with-label.yaml"); + + rightConfigMap = Serialization.unmarshal(rightConfigMapStream, ConfigMap.class); + rightConfigMapWithLabel = Serialization.unmarshal(rightConfigMapWithLabelStream, ConfigMap.class); + + util.createNamespace(RIGHT_NAMESPACE); + + configMap(Phase.CREATE, util, rightConfigMap, RIGHT_NAMESPACE); + configMap(Phase.CREATE, util, rightConfigMapWithLabel, RIGHT_NAMESPACE); + } + + @AfterAll + static void afterAllLocal() { + configMap(Phase.DELETE, util, rightConfigMap, RIGHT_NAMESPACE); + configMap(Phase.DELETE, util, rightConfigMapWithLabel, RIGHT_NAMESPACE); + util.deleteNamespace(RIGHT_NAMESPACE); + } + + /** + *
+	 * - there is one namespace : right
+	 * - right has two configmaps: right-configmap, right-configmap-with-label
+	 * - we watch the "right" namespace, but enable tagging; which means that only
+	 * right-configmap-with-label triggers changes.
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + assertReloadLogStatements("added configmap informer for namespace", "added secret informer for namespace", + output); + + // read the initial value from the right-configmap + assertThat(rightProperties.getValue()).isEqualTo("right-initial"); + + // then read the initial value from the right-with-label-configmap + assertThat(rightWithLabelsProperties.getValue()).isEqualTo("right-with-label-initial"); + + // then deploy a new version of right-configmap + ConfigMap rightConfigMapAfterChange = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build()) + .withData(Map.of("right.value", "right-after-change")) + .build(); + + replaceConfigMap(kubernetesClient, rightConfigMapAfterChange, RIGHT_NAMESPACE); + + // nothing changes in our app, because we are watching only labeled configmaps + assertThat(rightProperties.getValue()).isEqualTo("right-initial"); + assertThat(rightWithLabelsProperties.getValue()).isEqualTo("right-with-label-initial"); + + // then deploy a new version of right-with-label-configmap + ConfigMap rightWithLabelConfigMapAfterChange = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap-with-label").build()) + .withData(Map.of("right.with.label.value", "right-with-label-after-change")) + .build(); + + replaceConfigMap(kubernetesClient, rightWithLabelConfigMapAfterChange, RIGHT_NAMESPACE); + + // since we have changed a labeled configmap, app will restart and pick up the new + // value + await().atMost(Duration.ofSeconds(60)).pollDelay(Duration.ofSeconds(1)).until(() -> { + String afterUpdateRightValue = rightWithLabelsProperties.getValue(); + return afterUpdateRightValue.equals("right-with-label-after-change"); + }); + + // right-configmap now will see the new value also, but only because the other + // configmap has triggered the restart + await().atMost(Duration.ofSeconds(60)).pollDelay(Duration.ofSeconds(1)).until(() -> { + String afterUpdateRightValue = rightProperties.getValue(); + return afterUpdateRightValue.equals("right-after-change"); + }); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadSecretIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadSecretIT.java new file mode 100644 index 0000000000..f9d869ac58 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/Fabric8EventReloadSecretIT.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-2024 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.fabric8.client.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Base64; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.cloud.kubernetes.commons.config.Constants; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.assertReloadLogStatements; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.replaceSecret; +import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestAssertions.secret; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.main.cloud-platform=kubernetes", + "logging.level.org.springframework.cloud.kubernetes.fabric8.config.reload=debug", + "spring.cloud.kubernetes.client.namespace=default" }) +@ActiveProfiles("with-secret") +class Fabric8EventReloadSecretIT extends Fabric8EventReloadBase { + + private static final String NAMESPACE = "default"; + + private static Secret secret; + + @Autowired + private KubernetesClient kubernetesClient; + + @Autowired + private SecretProperties secretProperties; + + @BeforeAll + static void beforeAllLocal() { + + // set system properties very early, so that when + // 'Fabric8ConfigDataLocationResolver' + // loads KubernetesClient from Config, these would be already present + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); + String caCertData = config.getCaCertData(); + String clientCertData = config.getClientCertData(); + String clientKeyData = config.getClientKeyData(); + String clientKeyAlgo = config.getClientKeyAlgo(); + String clientKeyPass = config.getClientKeyPassphrase(); + String masterUrl = new KubernetesClientBuilder().withConfig(config).build().getConfiguration().getMasterUrl(); + + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, masterUrl); + System.setProperty(Config.KUBERNETES_CA_CERTIFICATE_DATA_SYSTEM_PROPERTY, caCertData); + System.setProperty(Config.KUBERNETES_CLIENT_CERTIFICATE_DATA_SYSTEM_PROPERTY, clientCertData); + System.setProperty(Config.KUBERNETES_CLIENT_KEY_DATA_SYSTEM_PROPERTY, clientKeyData); + System.setProperty(Config.KUBERNETES_CLIENT_KEY_ALGO_SYSTEM_PROPERTY, clientKeyAlgo); + System.setProperty(Config.KUBERNETES_CLIENT_KEY_PASSPHRASE_SYSTEM_PROPERTY, clientKeyPass); + + InputStream secretStream = util.inputStream("manifests/secret.yaml"); + secret = Serialization.unmarshal(secretStream, Secret.class); + secret(Phase.CREATE, util, secret, NAMESPACE); + } + + @AfterAll + static void afterAllLocal() { + secret(Phase.DELETE, util, secret, NAMESPACE); + } + + /** + *
+	 *     - secret with no labels and data: from.secret.properties.key = secret-initial exists in namespace default
+	 *
+	 *     - then we change the secret by adding a label, this in turn does not
+	 *       change the result
+	 *
+	 *     - then we change data inside the secret, and we must see the updated value.
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + assertReloadLogStatements("added secret informer for namespace", "added configmap informer for namespace", + output); + assertThat(secretProperties.getKey()).isEqualTo("secret-initial"); + + Secret secret = new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withLabels(Map.of("letter", "a")) + .withNamespace("default") + .withName("event-reload") + .build()) + .withData(Map.of(Constants.APPLICATION_PROPERTIES, + Base64.getEncoder().encodeToString("from.secret.properties.key=secret-initial".getBytes()))) + .build(); + replaceSecret(kubernetesClient, secret, NAMESPACE); + + await().atMost(Duration.ofSeconds(60)) + .pollDelay(Duration.ofSeconds(1)) + .until(() -> output.getOut().contains("Secret event-reload was updated in namespace default")); + + await().atMost(Duration.ofSeconds(60)) + .pollDelay(Duration.ofSeconds(1)) + .until(() -> output.getOut().contains("data in secret has not changed, will not reload")); + assertThat(secretProperties.getKey()).isEqualTo("secret-initial"); + + // change data + secret = new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("default").withName("event-reload").build()) + .withData(Map.of(Constants.APPLICATION_PROPERTIES, + Base64.getEncoder().encodeToString("from.secret.properties.key=secret-initial-changed".getBytes()))) + .build(); + replaceSecret(kubernetesClient, secret, NAMESPACE); + + await().atMost(Duration.ofSeconds(60)) + .pollDelay(Duration.ofSeconds(1)) + .until(() -> secretProperties.getKey().equals("secret-initial-changed")); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/SecretsEventsReloadDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/SecretsEventsReloadDelegate.java deleted file mode 100644 index 281608211a..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/SecretsEventsReloadDelegate.java +++ /dev/null @@ -1,111 +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.fabric8.client.reload; - -import java.time.Duration; -import java.util.Base64; -import java.util.Map; - -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.fabric8.kubernetes.api.model.Secret; -import io.fabric8.kubernetes.api.model.SecretBuilder; -import io.fabric8.kubernetes.client.KubernetesClient; -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.fabric8.client.reload.TestUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.reload.TestUtil.retrySpec; - -/** - * @author wind57 - */ -final class SecretsEventsReloadDelegate { - - /** - *
-	 *     - secret with no labels and data: from.secret.properties.key = secret-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 testSecretReload(KubernetesClient client, K3sContainer container, String appLabelValue) { - Commons.assertReloadLogStatements("added secret informer for namespace", - "added configmap informer for namespace", appLabelValue); - - WebClient webClient = builder().baseUrl("http://localhost/key-from-secret").build(); - String result = webClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - Assertions.assertEquals("secret-initial", result); - - Secret secret = new SecretBuilder() - .withMetadata(new ObjectMetaBuilder().withLabels(Map.of("letter", "a")) - .withNamespace("default") - .withName("event-reload") - .build()) - .withData(Map.of(Constants.APPLICATION_PROPERTIES, - Base64.getEncoder().encodeToString("from.secret.properties.key=secret-initial".getBytes()))) - .build(); - client.secrets().inNamespace("default").resource(secret).createOrReplace(); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = builder().baseUrl("http://localhost/key-from-secret").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - return "secret-initial".equals(innerResult); - }); - - Commons.waitForLogStatement("Secret event-reload was updated in namespace default", container, appLabelValue); - Commons.waitForLogStatement("data in secret has not changed, will not reload", container, appLabelValue); - - // change data - secret = new SecretBuilder() - .withMetadata(new ObjectMetaBuilder().withNamespace("default").withName("event-reload").build()) - .withData(Map.of(Constants.APPLICATION_PROPERTIES, - Base64.getEncoder().encodeToString("from.secret.properties.key=secret-initial-changed".getBytes()))) - .build(); - - client.secrets().inNamespace("default").resource(secret).createOrReplace(); - - await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { - WebClient innerWebClient = builder().baseUrl("http://localhost/key-from-secret").build(); - String innerResult = innerWebClient.method(HttpMethod.GET) - .retrieve() - .bodyToMono(String.class) - .retryWhen(retrySpec()) - .block(); - return "secret-initial-changed".equals(innerResult); - }); - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/TestAssertions.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/TestAssertions.java new file mode 100644 index 0000000000..fb4103eb17 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/TestAssertions.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2024 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.fabric8.client.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Objects; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Serialization; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +final class TestAssertions { + + private TestAssertions() { + + } + + /** + * assert that 'left' is present, and IFF it is, assert that 'right' is not + */ + static void assertReloadLogStatements(String left, String right, CapturedOutput output) { + + await().pollDelay(Duration.ofSeconds(5)) + .atMost(Duration.ofSeconds(15)) + .pollInterval(Duration.ofSeconds(1)) + .until(() -> { + boolean leftIsPresent = output.getOut().contains(left); + if (leftIsPresent) { + boolean rightIsPresent = output.getOut().contains(right); + return !rightIsPresent; + } + return false; + }); + } + + static void replaceConfigMap(KubernetesClient client, ConfigMap configMap, String namespace) { + client.configMaps().inNamespace(namespace).resource(configMap).update(); + } + + static void replaceSecret(KubernetesClient client, Secret secret, String namespace) { + client.secrets().inNamespace(namespace).resource(secret).update(); + } + + static void configMap(Phase phase, Util util, ConfigMap configMap, String namespace) { + if (phase.equals(Phase.CREATE)) { + util.createAndWait(namespace, configMap, null); + } + else { + util.deleteAndWait(namespace, configMap, null); + } + } + + static void secret(Phase phase, Util util, Secret secret, String namespace) { + if (phase.equals(Phase.CREATE)) { + util.createAndWait(namespace, null, secret); + } + else { + util.deleteAndWait(namespace, null, secret); + } + } + + static void manifests(Phase phase, Util util, String namespace) { + + InputStream deploymentStream = util.inputStream("manifests/deployment.yaml"); + InputStream serviceStream = util.inputStream("manifests/service.yaml"); + InputStream ingressStream = util.inputStream("manifests/ingress.yaml"); + InputStream configMapAsStream = util.inputStream("manifests/configmap.yaml"); + + Deployment deployment = Serialization.unmarshal(deploymentStream, Deployment.class); + + Service service = Serialization.unmarshal(serviceStream, Service.class); + Ingress ingress = Serialization.unmarshal(ingressStream, Ingress.class); + ConfigMap configMap = Serialization.unmarshal(configMapAsStream, ConfigMap.class); + + if (phase.equals(Phase.CREATE)) { + util.createAndWait(namespace, configMap, null); + util.createAndWait(namespace, null, deployment, service, ingress, true); + } + else { + util.deleteAndWait(namespace, configMap, null); + util.deleteAndWait(namespace, deployment, service, ingress); + } + + } + + static WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + static RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(120, Duration.ofSeconds(2)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/TestUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/TestUtil.java deleted file mode 100644 index 7ede854c6f..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/reload/TestUtil.java +++ /dev/null @@ -1,372 +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.fabric8.client.reload; - -import java.io.InputStream; -import java.time.Duration; -import java.util.Map; -import java.util.Objects; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.utils.Serialization; -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.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author wind57 - */ -final class TestUtil { - - private static final Map POD_LABELS = Map.of("app", - "spring-cloud-kubernetes-fabric8-client-reload"); - - private static final String BODY_ONE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", - "value": "DEBUG" - }, - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "two" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_TWO = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", - "value": "DEBUG" - }, - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "three" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_THREE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", - "value": "DEBUG" - }, - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "two" - }, - { - "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-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "one" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED", - "value": "FALSE" - }, - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_FIVE = """ - { - "spec": { - "template": { - "spec": { - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "name": "poll-reload" - }, - "name": "config-map-volume" - } - ], - "containers": [{ - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "config-map-volume" - } - ], - "name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "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" - }, - "name": "config-map-volume" - } - ], - "containers": [{ - "volumeMounts": [ - { - "mountPath": "/tmp", - "name": "config-map-volume" - } - ], - "name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "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" - } - ] - }] - } - } - } - } - """; - - private static final String BODY_SEVEN = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload", - "image": "image_name_here", - "env": [ - { - "name": "SPRING_PROFILES_ACTIVE", - "value": "with-secret" - } - ] - }] - } - } - } - } - """; - - private TestUtil() { - - } - - static void reCreateSources(Util util, KubernetesClient client) { - InputStream leftConfigMapStream = util.inputStream("left-configmap.yaml"); - InputStream rightConfigMapStream = util.inputStream("right-configmap.yaml"); - InputStream configMapStream = util.inputStream("configmap.yaml"); - - ConfigMap leftConfigMap = Serialization.unmarshal(leftConfigMapStream, ConfigMap.class); - ConfigMap rightConfigMap = Serialization.unmarshal(rightConfigMapStream, ConfigMap.class); - ConfigMap configMap = Serialization.unmarshal(configMapStream, ConfigMap.class); - - replaceConfigMap(client, leftConfigMap, "left"); - replaceConfigMap(client, rightConfigMap, "right"); - replaceConfigMap(client, configMap, "default"); - } - - static void replaceConfigMap(KubernetesClient client, ConfigMap configMap, String namespace) { - client.configMaps().inNamespace(namespace).resource(configMap).createOrReplace(); - } - - static void patchOne(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_ONE, POD_LABELS); - } - - static void patchTwo(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_TWO, POD_LABELS); - } - - static void patchThree(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_THREE, POD_LABELS); - } - - static void patchFour(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_FOUR, POD_LABELS); - } - - static void patchFive(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_FIVE, POD_LABELS); - } - - static void patchSix(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_SIX, POD_LABELS); - } - - static void patchSeven(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_SEVEN, POD_LABELS); - } - - static WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - static RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(120, Duration.ofSeconds(2)).filter(Objects::nonNull); - } - - static String logs(K3sContainer container, String appLabelValue) { - try { - String appPodName = container - .execInContainer("sh", "-c", - "kubectl get pods -l app=" + appLabelValue + " -o=name --no-headers | tr -d '\n'") - .getStdout(); - - Container.ExecResult execResult = container.execInContainer("sh", "-c", - "kubectl logs " + appPodName.trim()); - return execResult.getStdout(); - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/META-INF/spring.factories b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/META-INF/spring.factories new file mode 100644 index 0000000000..cc4154d5ba --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.cloud.bootstrap.BootstrapConfiguration=\ +org.springframework.cloud.kubernetes.fabric8.client.reload.Fabric8EventReloadBase.TestConfig diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/logback-test.xml index ee24334373..0f9de3d918 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/logback-test.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/logback-test.xml @@ -12,4 +12,6 @@ + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/configmap.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/configmap.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/configmap.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/deployment.yaml similarity index 74% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/deployment.yaml index aba13d5127..a430c69bf2 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/deployment.yaml @@ -30,6 +30,18 @@ spec: - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_CONFIG_RELOAD value: "DEBUG" - name: SPRING_PROFILES_ACTIVE - value: "one" + value: "with-bootstrap" - name: SPRING_CLOUD_BOOTSTRAP_ENABLED value: "TRUE" + - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG + 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-fabric8-client-reload/src/test/resources/ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/ingress.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/ingress.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/left-configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/left-configmap.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/left-configmap.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/left-configmap.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/right-configmap-with-label.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/right-configmap-with-label.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/right-configmap-with-label.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/right-configmap-with-label.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/right-configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/right-configmap.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/right-configmap.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/right-configmap.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/secret.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/secret.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/secret.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-reload/src/test/resources/manifests/service.yaml diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java index 47021c563f..4a39540a1b 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java @@ -79,11 +79,15 @@ public static String wiremockVersion() { } public static void loadBusybox(K3sContainer container) { - Commons.load(container, BUSYBOX_TAR, BUSYBOX, busyboxVersion()); + if (!imageAlreadyInK3s(container, BUSYBOX_TAR)) { + Commons.load(container, BUSYBOX_TAR, BUSYBOX, busyboxVersion()); + } } public static void loadWiremock(K3sContainer container) { - Commons.load(container, WIREMOCK_TAR, WIREMOCK, wiremockVersion()); + if (!imageAlreadyInK3s(container, WIREMOCK_TAR)) { + Commons.load(container, WIREMOCK_TAR, WIREMOCK, wiremockVersion()); + } } public static void loadIstioCtl(K3sContainer container) { @@ -106,6 +110,25 @@ public static void loadRabbitmq(K3sContainer container) { Commons.load(container, RABBITMQ_TAR, RABBITMQ, rabbitMqVersion()); } + private static boolean imageAlreadyInK3s(K3sContainer container, String tarName) { + try { + boolean present = container.execInContainer("sh", "-c", "ctr images list | grep " + tarName) + .getStdout() + .contains(tarName); + if (present) { + System.out.println("image : " + tarName + " already in k3s, skipping"); + return true; + } + else { + System.out.println("image : " + tarName + " not in k3s"); + return false; + } + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + // find the image version from current-images.txt private static String imageVersion(String imageNameForDownload) { BufferedReader reader = new BufferedReader( diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/current-images.txt b/spring-cloud-kubernetes-test-support/src/main/resources/current-images.txt index 34d4371080..2853673403 100644 --- a/spring-cloud-kubernetes-test-support/src/main/resources/current-images.txt +++ b/spring-cloud-kubernetes-test-support/src/main/resources/current-images.txt @@ -1,7 +1,7 @@ busybox:1.36.1 -istio/istioctl:1.20.5 -istio/proxyv2:1.20.5 -istio/pilot:1.20.5 +istio/istioctl:1.21.6 +istio/proxyv2:1.21.6 +istio/pilot:1.21.6 confluentinc/cp-kafka:7.2.1 rabbitmq:3-management wiremock/wiremock:3.4.2