diff --git a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientAutoConfiguration.java b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientAutoConfiguration.java index 26c5c3a4d5..e58107464d 100644 --- a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientAutoConfiguration.java +++ b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientAutoConfiguration.java @@ -73,7 +73,7 @@ public KubernetesNamespaceProvider kubernetesNamespaceProvider(Environment envir @ConditionalOnMissingBean public KubernetesClientPodUtils kubernetesPodUtils(CoreV1Api client, KubernetesNamespaceProvider kubernetesNamespaceProvider) { - return new KubernetesClientPodUtils(client, kubernetesNamespaceProvider.getNamespace()); + return new KubernetesClientPodUtils(client, kubernetesNamespaceProvider.getNamespace(), true); } } diff --git a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientHealthIndicator.java b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientHealthIndicator.java index 7a2490b18a..26506fdf8b 100644 --- a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientHealthIndicator.java +++ b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientHealthIndicator.java @@ -41,7 +41,7 @@ public KubernetesClientHealthIndicator(PodUtils utils) { @Override protected Map getDetails() { - V1Pod current = this.utils.currentPod().get(); + V1Pod current = utils.currentPod().get(); if (current != null) { Map details = CollectionUtils.newHashMap(8); details.put(INSIDE, true); diff --git a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientInfoContributor.java b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientInfoContributor.java index 4d9d5e687f..6cd0c8053e 100644 --- a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientInfoContributor.java +++ b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientInfoContributor.java @@ -41,7 +41,7 @@ public KubernetesClientInfoContributor(PodUtils utils) { @Override public Map getDetails() { - V1Pod current = this.utils.currentPod().get(); + V1Pod current = utils.currentPod().get(); if (current != null) { Map details = CollectionUtils.newHashMap(7); details.put(INSIDE, true); diff --git a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtils.java b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtils.java index f50394db19..a87fd61409 100644 --- a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtils.java +++ b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtils.java @@ -58,6 +58,9 @@ public class KubernetesClientPodUtils implements PodUtils { private final String serviceHost; + private final boolean failFast; + + @Deprecated(forRemoval = true) public KubernetesClientPodUtils(CoreV1Api client, String namespace) { if (client == null) { throw new IllegalArgumentException("Must provide an instance of KubernetesClient"); @@ -68,6 +71,22 @@ public KubernetesClientPodUtils(CoreV1Api client, String namespace) { this.serviceHost = EnvReader.getEnv(KUBERNETES_SERVICE_HOST); this.current = LazilyInstantiate.using(this::internalGetPod); this.namespace = namespace; + this.failFast = false; + } + + // mainly needed for the health and info contributors, so that they report DOWN + // correctly + public KubernetesClientPodUtils(CoreV1Api client, String namespace, boolean failFast) { + if (client == null) { + throw new IllegalArgumentException("Must provide an instance of KubernetesClient"); + } + + this.client = client; + this.hostName = EnvReader.getEnv(HOSTNAME); + this.serviceHost = EnvReader.getEnv(KUBERNETES_SERVICE_HOST); + this.current = LazilyInstantiate.using(this::internalGetPod); + this.namespace = namespace; + this.failFast = failFast; } @Override @@ -84,10 +103,14 @@ private V1Pod internalGetPod() { try { if (isServiceHostEnvVarPresent() && isHostNameEnvVarPresent() && isServiceAccountFound()) { LOG.debug("reading pod in namespace : " + namespace); + // The hostname of your pod is typically also its name. return client.readNamespacedPod(hostName, namespace, null); } } catch (Throwable t) { + if (failFast) { + throw new RuntimeException(t); + } if (t instanceof ApiException apiException) { LOG.warn("error reading pod, with error : " + apiException.getResponseBody()); } diff --git a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/profile/KubernetesClientProfileEnvironmentPostProcessor.java b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/profile/KubernetesClientProfileEnvironmentPostProcessor.java index 767d082355..78d87e3565 100644 --- a/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/profile/KubernetesClientProfileEnvironmentPostProcessor.java +++ b/spring-cloud-kubernetes-client-autoconfig/src/main/java/org/springframework/cloud/kubernetes/client/profile/KubernetesClientProfileEnvironmentPostProcessor.java @@ -33,7 +33,8 @@ public class KubernetesClientProfileEnvironmentPostProcessor extends AbstractKub @Override protected boolean isInsideKubernetes(Environment environment) { CoreV1Api api = new CoreV1Api(); - KubernetesClientPodUtils utils = new KubernetesClientPodUtils(api, environment.getProperty(NAMESPACE_PROPERTY)); + KubernetesClientPodUtils utils = new KubernetesClientPodUtils(api, environment.getProperty(NAMESPACE_PROPERTY), + false); return environment.containsProperty(ENV_SERVICE_HOST) || utils.isInsideKubernetes(); } diff --git a/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/ActuatorEnabledFailFastExceptionTest.java b/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/ActuatorEnabledFailFastExceptionTest.java new file mode 100644 index 0000000000..b851245b05 --- /dev/null +++ b/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/ActuatorEnabledFailFastExceptionTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013-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.client; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.util.Config; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.kubernetes.client.example.App; +import org.springframework.cloud.kubernetes.commons.EnvReader; +import org.springframework.context.annotation.Bean; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { App.class, ActuatorEnabledFailFastExceptionTest.ActuatorConfig.class }, + properties = { "management.endpoint.health.show-details=always", + "management.endpoint.health.show-components=always", "management.endpoints.web.exposure.include=health", + "spring.main.cloud-platform=KUBERNETES" }) +class ActuatorEnabledFailFastExceptionTest { + + private static final boolean FAIL_FAST = true; + + private static MockedStatic envReaderMockedStatic; + + private static MockedStatic pathsMockedStatic; + + private static final CoreV1Api coreV1Api = Mockito.mock(CoreV1Api.class); + + @Autowired + private KubernetesClientHealthIndicator healthIndicator; + + @AfterEach + void afterEach() { + envReaderMockedStatic.close(); + pathsMockedStatic.close(); + } + + @Test + void test() throws ApiException { + Health health = healthIndicator.getHealth(true); + Assertions.assertEquals(health.getStatus(), Status.DOWN); + Mockito.verify(coreV1Api).readNamespacedPod("host", "my-namespace", null); + } + + private static void mocks() { + envReaderMockedStatic = Mockito.mockStatic(EnvReader.class); + pathsMockedStatic = Mockito.mockStatic(Paths.class); + + envReaderMockedStatic.when(() -> EnvReader.getEnv(KubernetesClientPodUtils.KUBERNETES_SERVICE_HOST)) + .thenReturn("k8s-host"); + envReaderMockedStatic.when(() -> EnvReader.getEnv(KubernetesClientPodUtils.HOSTNAME)).thenReturn("host"); + + Path serviceAccountTokenPath = Mockito.mock(Path.class); + File serviceAccountTokenFile = Mockito.mock(File.class); + Mockito.when(serviceAccountTokenPath.toFile()).thenReturn(serviceAccountTokenFile); + Mockito.when(serviceAccountTokenFile.exists()).thenReturn(true); + pathsMockedStatic.when(() -> Paths.get(Config.SERVICEACCOUNT_TOKEN_PATH)).thenReturn(serviceAccountTokenPath); + + Path serviceAccountCAPath = Mockito.mock(Path.class); + File serviceAccountCAFile = Mockito.mock(File.class); + Mockito.when(serviceAccountCAPath.toFile()).thenReturn(serviceAccountCAFile); + Mockito.when(serviceAccountCAFile.exists()).thenReturn(true); + pathsMockedStatic.when(() -> Paths.get(Config.SERVICEACCOUNT_CA_PATH)).thenReturn(serviceAccountCAPath); + } + + @TestConfiguration + static class ActuatorConfig { + + // will be created "instead" of + // KubernetesClientAutoConfiguration::kubernetesPodUtils + @Bean + KubernetesClientPodUtils kubernetesPodUtils() throws ApiException { + + mocks(); + + Mockito.when(coreV1Api.readNamespacedPod("host", "my-namespace", null)) + .thenThrow(new RuntimeException("just because")); + + return new KubernetesClientPodUtils(coreV1Api, "my-namespace", FAIL_FAST); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/ActuatorEnabledNoFailFastExceptionTest.java b/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/ActuatorEnabledNoFailFastExceptionTest.java new file mode 100644 index 0000000000..f6bba80d3e --- /dev/null +++ b/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/ActuatorEnabledNoFailFastExceptionTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013-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.client; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.util.Config; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.kubernetes.client.example.App; +import org.springframework.cloud.kubernetes.commons.EnvReader; +import org.springframework.context.annotation.Bean; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { App.class, ActuatorEnabledNoFailFastExceptionTest.ActuatorConfig.class }, + properties = { "management.endpoint.health.show-details=always", + "management.endpoint.health.show-components=always", "management.endpoints.web.exposure.include=health", + "spring.main.cloud-platform=KUBERNETES" }) + +class ActuatorEnabledNoFailFastExceptionTest { + + private static final boolean FAIL_FAST = false; + + private static MockedStatic envReaderMockedStatic; + + private static MockedStatic pathsMockedStatic; + + private static final CoreV1Api coreV1Api = Mockito.mock(CoreV1Api.class); + + @Autowired + private KubernetesClientHealthIndicator healthIndicator; + + @AfterEach + void afterEach() { + envReaderMockedStatic.close(); + pathsMockedStatic.close(); + } + + // without a fail-fast, we would not fail and actuator would return "UP" + // This is not a real case we have, it just makes sure + @Test + void test() throws ApiException { + Health health = healthIndicator.getHealth(true); + Assertions.assertEquals(health.getStatus(), Status.UP); + Mockito.verify(coreV1Api).readNamespacedPod("host", "my-namespace", null); + } + + private static void mocks() { + envReaderMockedStatic = Mockito.mockStatic(EnvReader.class); + pathsMockedStatic = Mockito.mockStatic(Paths.class); + + envReaderMockedStatic.when(() -> EnvReader.getEnv(KubernetesClientPodUtils.KUBERNETES_SERVICE_HOST)) + .thenReturn("k8s-host"); + envReaderMockedStatic.when(() -> EnvReader.getEnv(KubernetesClientPodUtils.HOSTNAME)).thenReturn("host"); + + Path serviceAccountTokenPath = Mockito.mock(Path.class); + File serviceAccountTokenFile = Mockito.mock(File.class); + Mockito.when(serviceAccountTokenPath.toFile()).thenReturn(serviceAccountTokenFile); + Mockito.when(serviceAccountTokenFile.exists()).thenReturn(true); + pathsMockedStatic.when(() -> Paths.get(Config.SERVICEACCOUNT_TOKEN_PATH)).thenReturn(serviceAccountTokenPath); + + Path serviceAccountCAPath = Mockito.mock(Path.class); + File serviceAccountCAFile = Mockito.mock(File.class); + Mockito.when(serviceAccountCAPath.toFile()).thenReturn(serviceAccountCAFile); + Mockito.when(serviceAccountCAFile.exists()).thenReturn(true); + pathsMockedStatic.when(() -> Paths.get(Config.SERVICEACCOUNT_CA_PATH)).thenReturn(serviceAccountCAPath); + } + + @TestConfiguration + static class ActuatorConfig { + + // will be created "instead" of + // KubernetesClientAutoConfiguration::kubernetesPodUtils + @Bean + KubernetesClientPodUtils kubernetesPodUtils() throws ApiException { + + mocks(); + + Mockito.when(coreV1Api.readNamespacedPod("host", "my-namespace", null)) + .thenThrow(new RuntimeException("just because")); + + return new KubernetesClientPodUtils(coreV1Api, "my-namespace", FAIL_FAST); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtilsTests.java b/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtilsTests.java index a81bbefb5a..e97b901d23 100644 --- a/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtilsTests.java +++ b/spring-cloud-kubernetes-client-autoconfig/src/test/java/org/springframework/cloud/kubernetes/client/KubernetesClientPodUtilsTests.java @@ -39,7 +39,7 @@ /** * @author wind57 */ -public class KubernetesClientPodUtilsTests { +class KubernetesClientPodUtilsTests { private static final String KUBERNETES_SERVICE_HOST = KubernetesClientPodUtils.KUBERNETES_SERVICE_HOST; @@ -70,73 +70,73 @@ public class KubernetesClientPodUtilsTests { private MockedStatic paths; @BeforeEach - public void before() { + void before() { envReader = Mockito.mockStatic(EnvReader.class); paths = Mockito.mockStatic(Paths.class); } @AfterEach - public void after() { + void after() { envReader.close(); paths.close(); } @Test - public void constructorThrowsIllegalArgumentExceptionWhenKubeClientIsNull() { - assertThatThrownBy(() -> new KubernetesClientPodUtils(null, "namespace")) + void constructorThrowsIllegalArgumentExceptionWhenKubeClientIsNull() { + assertThatThrownBy(() -> new KubernetesClientPodUtils(null, "namespace", false)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Must provide an instance of KubernetesClient"); } @Test - public void serviceHostNotPresent() { + void serviceHostNotPresent() { mockHost(null); - KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace"); + KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace", false); Supplier sup = util.currentPod(); assertSupplierAndClient(sup, util); } @Test - public void hostNameNotPresent() { + void hostNameNotPresent() { mockHost(HOST); mockHostname(null); - KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace"); + KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace", false); Supplier sup = util.currentPod(); assertSupplierAndClient(sup, util); } @Test - public void serviceAccountPathNotPresent() { + void serviceAccountPathNotPresent() { mockTokenPath(false); mockHostname(HOST); - KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace"); + KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace", false); Supplier sup = util.currentPod(); assertSupplierAndClient(sup, util); } @Test - public void serviceAccountCertPathNotPresent() { + void serviceAccountCertPathNotPresent() { mockTokenPath(true); mockCertPath(false); mockHostname(HOST); - KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace"); + KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace", false); Supplier sup = util.currentPod(); assertSupplierAndClient(sup, util); } @Test - public void allPresent() throws ApiException { + void allPresent() throws ApiException { mockTokenPath(true); mockCertPath(true); mockHost(HOST); mockHostname(POD_HOSTNAME); mockPodResult(); - KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace"); + KubernetesClientPodUtils util = new KubernetesClientPodUtils(client, "namespace", false); Supplier sup = util.currentPod(); assertThat(sup.get()).isNotNull(); assertThat(util.isInsideKubernetes()).isTrue(); diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryClientHealthIndicatorInitializer.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryClientHealthIndicatorInitializer.java index 8d8cd991d2..c2d320e8c8 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryClientHealthIndicatorInitializer.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryClientHealthIndicatorInitializer.java @@ -48,7 +48,7 @@ private void postConstruct() { InstanceRegisteredEvent instanceRegisteredEvent = new InstanceRegisteredEvent<>( new RegisteredEventSource("kubernetes", podUtils.isInsideKubernetes(), podUtils.currentPod().get()), null); - this.applicationEventPublisher.publishEvent(instanceRegisteredEvent); + applicationEventPublisher.publishEvent(instanceRegisteredEvent); } /** diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/main/resources/application.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/main/resources/application.yaml index 58e7ca9849..8f649931a2 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/main/resources/application.yaml @@ -1,2 +1,20 @@ server: port: 8761 + +# needed to disable the publication of InstanceRegisteredEvent +# which in case of discovery server would not be needed. +spring: + cloud: + discovery: + client: + health-indicator: + enabled: false + +management: + endpoint: + health: + group: + liveness: + include: livenessState, kubernetes + readiness: + include: readinessState, kubernetes diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerApplicationContextTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerApplicationContextTests.java index 10a9b2ace7..99a2474387 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerApplicationContextTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerApplicationContextTests.java @@ -35,7 +35,12 @@ class DiscoveryServerApplicationContextTests { @Nested @SpringBootTest(classes = TestConfig.class, - properties = "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true") + properties = { "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true", + /* disable kubernetes from liveness and readiness */ + "management.health.livenessstate.enabled=true", + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class BothControllersPresent { @Autowired @@ -59,7 +64,12 @@ void test() { @Nested @SpringBootTest(classes = TestConfig.class, properties = { "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false", - "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true" }) + "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true", + /* disable kubernetes from liveness and readiness */ + "management.health.livenessstate.enabled=true", + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class CatalogControllerNotPresentOne { @Autowired @@ -83,7 +93,12 @@ void test() { @Nested @SpringBootTest(classes = TestConfig.class, properties = { "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=true", - "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=false" }) + "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=false", + /* disable kubernetes from liveness and readiness */ + "management.health.livenessstate.enabled=true", + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class CatalogControllerNotPresentTwo { @Autowired @@ -107,7 +122,12 @@ void test() { @Nested @SpringBootTest(classes = TestConfig.class, properties = { "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false", - "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=false" }) + "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=false", + /* disable kubernetes from liveness and readiness */ + "management.health.livenessstate.enabled=true", + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class CatalogControllerNotPresentThree { @Autowired diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsEndpointTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsEndpointTest.java index 95245d6f61..0acd107f8c 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsEndpointTest.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsEndpointTest.java @@ -52,7 +52,12 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = DiscoveryServerIntegrationAppsEndpointTest.TestConfig.class) + classes = DiscoveryServerIntegrationAppsEndpointTest.TestConfig.class, + properties = { "management.health.livenessstate.enabled=true", + /* disable kubernetes from liveness and readiness */ + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class DiscoveryServerIntegrationAppsEndpointTest { private static final String NAMESPACE = "namespace"; diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsNameEndpointTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsNameEndpointTest.java index e0387dc264..405a7882c4 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsNameEndpointTest.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationAppsNameEndpointTest.java @@ -52,7 +52,12 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = DiscoveryServerIntegrationAppsNameEndpointTest.TestConfig.class) + classes = DiscoveryServerIntegrationAppsNameEndpointTest.TestConfig.class, + properties = { "management.health.livenessstate.enabled=true", + /* disable kubernetes from liveness and readiness */ + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class DiscoveryServerIntegrationAppsNameEndpointTest { private static final String NAMESPACE = "namespace"; diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationInstanceEndpointTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationInstanceEndpointTest.java index 3037c627fb..957dee8462 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationInstanceEndpointTest.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/DiscoveryServerIntegrationInstanceEndpointTest.java @@ -52,7 +52,12 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = DiscoveryServerIntegrationInstanceEndpointTest.TestConfig.class) + classes = DiscoveryServerIntegrationInstanceEndpointTest.TestConfig.class, + properties = { "management.health.livenessstate.enabled=true", + /* disable kubernetes from liveness and readiness */ + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) class DiscoveryServerIntegrationInstanceEndpointTest { private static final String NAMESPACE = "namespace"; diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/HeartbeatTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/HeartbeatTest.java index 9de172608a..1c5bb99b71 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/HeartbeatTest.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-discoveryserver/src/test/java/org/springframework/cloud/kubernetes/discoveryserver/HeartbeatTest.java @@ -39,7 +39,12 @@ * @author wind57 */ @SpringBootTest(classes = HeartbeatTest.TestConfig.class, - properties = "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true") + properties = { "spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true", + /* disable kubernetes from liveness and readiness */ + "management.health.livenessstate.enabled=true", + "management.endpoint.health.group.liveness.include=livenessState", + "management.health.readinessstate.enabled=true", + "management.endpoint.health.group.readiness.include=readinessState" }) @AutoConfigureWebTestClient class HeartbeatTest { diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java index acd215fa04..fdbd45fa6b 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-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. @@ -240,21 +240,29 @@ private void testLoadBalancer() { } void testHealth() { - WebClient.Builder builder = builder(); - WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build(); + WebClient.Builder clientBuilder = builder(); + WebClient.Builder serverBuilder = builder(); + + WebClient client = clientBuilder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build(); + WebClient server = serverBuilder.baseUrl("http://localhost:80/actuator/health").build(); - String health = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + String clientHealth = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) .block(); + String serverHealth = server.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); - Assertions.assertThat(BASIC_JSON_TESTER.from(health)) + Assertions.assertThat(BASIC_JSON_TESTER.from(clientHealth)) .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(serverHealth)) + .extractingJsonPathStringValue("$.components.kubernetes.status").isEqualTo("UP"); } private static void discoveryClient(Phase phase) { V1Deployment deployment = (V1Deployment) util .yaml("client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml"); V1Service service = (V1Service) util.yaml("client/spring-cloud-kubernetes-discoveryclient-it-service.yaml"); - V1Ingress ingress = (V1Ingress) util.yaml("client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml"); + V1Ingress ingress = (V1Ingress) util.yaml("ingress.yaml"); if (phase.equals(Phase.CREATE)) { util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); @@ -268,13 +276,12 @@ private static void discoveryServer(Phase phase) { V1Deployment deployment = (V1Deployment) util .yaml("server/spring-cloud-kubernetes-discoveryserver-deployment.yaml"); V1Service service = (V1Service) util.yaml("server/spring-cloud-kubernetes-discoveryserver-service.yaml"); - V1Ingress ingress = (V1Ingress) util.yaml("server/spring-cloud-kubernetes-discoveryserver-ingress.yaml"); if (phase.equals(Phase.CREATE)) { - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); + util.createAndWait(NAMESPACE, null, deployment, service, null, true); } else { - util.deleteAndWait(NAMESPACE, deployment, service, ingress); + util.deleteAndWait(NAMESPACE, deployment, service, null); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/ingress.yaml similarity index 64% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/ingress.yaml index 9768097787..9635d979b7 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/ingress.yaml @@ -14,3 +14,11 @@ spec: name: spring-cloud-kubernetes-k8s-client-discovery-server port: number: 8080 + + - path: / + pathType: Prefix + backend: + service: + name: spring-cloud-kubernetes-discoveryserver + port: + number: 80 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml index c38af74d5f..522f92bae6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml @@ -29,5 +29,9 @@ spec: value: "DEBUG" - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_CATALOGSERVICESWATCHDELAY value: "3000" + - name: MANAGEMENT_ENDPOINT_HEALTH_SHOWCOMPONENTS + value: "ALWAYS" + - name: MANAGEMENT_ENDPOINT_HEALTH_SHOWDETAILS + value: "ALWAYS" ports: - containerPort: 8761 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml deleted file mode 100644 index d820d54e94..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-k8s-client-discovery-server/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: discoveryserver-ingress - namespace: default -spec: - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: spring-cloud-kubernetes-discoveryserver - port: - number: 80