Skip to content

Commit

Permalink
Fix 1583 (#1584)
Browse files Browse the repository at this point in the history
Fixes #1583
  • Loading branch information
wind57 authored Mar 8, 2024
1 parent c82315a commit 70bc54e
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public KubernetesClientHealthIndicator(PodUtils<V1Pod> utils) {

@Override
protected Map<String, Object> getDetails() {
V1Pod current = this.utils.currentPod().get();
V1Pod current = utils.currentPod().get();
if (current != null) {
Map<String, Object> details = CollectionUtils.newHashMap(8);
details.put(INSIDE, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public KubernetesClientInfoContributor(PodUtils<V1Pod> utils) {

@Override
public Map<String, Object> getDetails() {
V1Pod current = this.utils.currentPod().get();
V1Pod current = utils.currentPod().get();
if (current != null) {
Map<String, Object> details = CollectionUtils.newHashMap(7);
details.put(INSIDE, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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
Expand All @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
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);
}

}

}
Loading

0 comments on commit 70bc54e

Please sign in to comment.