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 cfdbfb9f..7d30a2a6 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.time.Duration; 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.core.env.PropertySource; 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 @@ class EventReloadConfigMapTest { 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 @@ class EventReloadConfigMapTest { .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 @@ class EventReloadConfigMapTest { */ @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 @@ class EventReloadConfigMapTest { 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 @@ class EventReloadConfigMapTest { @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 @@ class EventReloadConfigMapTest { @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 @@ class EventReloadConfigMapTest { @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 86e3b09e..5cc87004 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.Base64; 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.core.env.PropertySource; 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 @@ class EventReloadSecretTest { 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 @@ class EventReloadSecretTest { .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 @@ class EventReloadSecretTest { */ @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 @@ class EventReloadSecretTest { 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 @@ class EventReloadSecretTest { @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 @@ class EventReloadSecretTest { @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 @@ class EventReloadSecretTest { @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 d0e2bdc0..348ac5cc 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.time.Duration; 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 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options @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 @@ class PollingReloadConfigMapTest { 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 @@ class PollingReloadConfigMapTest { 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 @@ class PollingReloadConfigMapTest { @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 @@ class PollingReloadConfigMapTest { @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 b15c12b7..f15f5fc1 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.Base64; 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 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options @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 @@ class PollingReloadSecretTest { 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 @@ class PollingReloadSecretTest { 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 @@ class PollingReloadSecretTest { @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 @@ class PollingReloadSecretTest { @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 3986f6c8..c8df2821 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 final class ConfigReloadUtil { 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-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 0f4c7c3d..9702dd65 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.time.Duration; 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 @@ public class EventReloadConfigMapTest { 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 @@ public class EventReloadConfigMapTest { 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 @@ public class EventReloadConfigMapTest { @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 57a699bc..7515bba3 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.Base64; 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 @@ class EventReloadSecretTest { 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 @@ class EventReloadSecretTest { 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 @@ class EventReloadSecretTest { @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 a6a1aa08..0e6f0253 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.time.Duration; 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 @@ class PollingReloadConfigMapTest { @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 @@ class PollingReloadConfigMapTest { @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 @@ class PollingReloadConfigMapTest { @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 365cc668..4429b8ab 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.Base64; 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 @@ public class PollingReloadSecretTest { @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 @@ public class PollingReloadSecretTest { @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 @@ public class PollingReloadSecretTest { @Primary ConfigurationUpdateStrategy configurationUpdateStrategy() { return new ConfigurationUpdateStrategy("to-console", () -> { - strategyCalled[0] = true; + STRATEGY_CALLED.set(true); }); }