-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix 1583 #1584
Fix 1583 #1584
Changes from 128 commits
032014e
c3d0ad2
10889fd
8f0375a
96ebf42
db8403e
90a5345
b041c00
9580444
a34ac47
6613f78
1b3eae9
4275382
618f25a
100a9cd
4b9056b
315a85b
ed3c264
446d630
6821a28
9e95a8a
02840b1
7e31d65
926f4d7
344e1d4
e91ac12
ba6e088
49025e8
a647bb3
16b09ab
c8d8a72
591f340
c331bd0
f48b22b
900843d
76ecdd5
b69d4a6
df2ddcc
3ff1cdd
bd89cc8
39ef510
d389953
b676bac
b1473c9
83ff113
33dfbb7
316af58
425d30b
763280a
3afbaef
cb97ba1
b33edc8
7a1fdf1
a8beed9
2e445d9
5b51db1
b0ce76c
918bb7f
c629951
ccfc0bf
0f3fa24
b60a283
901cb66
251002b
fedeb17
004f95b
10168be
dc811cf
3dfe77d
619c8d0
12ca211
a91c1e1
be2e809
1c84e33
1319fe7
9424c2e
cddd550
f8d34f2
31d2fb8
fa9fd09
22f3f51
bb44f4b
9714f2c
0f9cb3d
6e60174
78c5575
5eb5c51
48f7cb0
8a18085
e4cab39
ba4cab0
bbc6e30
0e9f1ad
8a5243c
cbf83c4
3dd0260
1408d44
2925ee1
af3a12b
f5a27dc
494454a
add0f67
b6a4b45
00a559d
882f9c1
6c9071e
09c10bb
3615396
116c199
f1447cd
14b165b
3ce8fac
41eaa8d
4f89298
ee1031d
f6707d5
9930c7d
0043006
4e292ec
3129276
1c90fc2
fa0888a
bf89ebb
4eb0951
dd4e91d
ea5ec18
d708e25
d479efa
439fe01
420bfa8
ab680d1
7e975ef
56b9648
e64557c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,6 +58,9 @@ public class KubernetesClientPodUtils implements PodUtils<V1Pod> { | |
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. introduce a new constructor that takes a |
||
// 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()); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return environment.containsProperty(ENV_SERVICE_HOST) || utils.isInsideKubernetes(); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<EnvReader> envReaderMockedStatic; | ||
|
||
private static MockedStatic<Paths> 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); | ||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<EnvReader> envReaderMockedStatic; | ||
|
||
private static MockedStatic<Paths> 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); | ||
} | ||
|
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deprecate this constructor