Initialize DiscoveryClient (#766)

* Initialize discovery client properly Fixes #517
This commit is contained in:
Ryan Baxter
2021-03-26 15:00:18 -04:00
committed by GitHub
parent 664b4bfda0
commit 0d7f0d5962
13 changed files with 201 additions and 12 deletions

View File

@@ -30,16 +30,23 @@ import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInfo
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers;
import io.kubernetes.client.spring.extended.controller.config.KubernetesInformerAutoConfiguration;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties;
import org.springframework.cloud.kubernetes.commons.PodUtils;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -53,6 +60,20 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties(KubernetesDiscoveryProperties.class)
public class KubernetesDiscoveryClientAutoConfiguration {
@ConditionalOnClass({ HealthIndicator.class })
@ConditionalOnDiscoveryEnabled
@ConditionalOnDiscoveryHealthIndicatorEnabled
@Configuration
public static class KubernetesDiscoveryClientHealthIndicatorConfiguration {
@Bean
public KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer(
ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) {
return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBlockingDiscoveryEnabled
public static class KubernetesInformerDiscoveryConfiguration {

View File

@@ -36,9 +36,11 @@ import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEna
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator;
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.KubernetesClientPodUtils;
import org.springframework.cloud.kubernetes.client.discovery.ConditionalOnKubernetesDiscoveryEnabled;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientAutoConfiguration;
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesEnabled;
@@ -77,8 +79,13 @@ public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator")
@ConditionalOnDiscoveryHealthIndicatorEnabled
public ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator(
KubernetesInformerReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties) {
return new ReactiveDiscoveryClientHealthIndicator(client, properties);
KubernetesInformerReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties,
KubernetesClientPodUtils podUtils) {
ReactiveDiscoveryClientHealthIndicator healthIndicator = new ReactiveDiscoveryClientHealthIndicator(client,
properties);
InstanceRegisteredEvent event = new InstanceRegisteredEvent(podUtils.currentPod(), null);
healthIndicator.onApplicationEvent(event);
return healthIndicator;
}
@KubernetesInformers({

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2013-2021 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.commons.discovery;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.kubernetes.commons.PodUtils;
import org.springframework.context.ApplicationEventPublisher;
/**
* @author Ryan Baxter
*/
public class KubernetesDiscoveryClientHealthIndicatorInitializer implements InitializingBean {
private PodUtils podUtils;
private ApplicationEventPublisher applicationEventPublisher;
public KubernetesDiscoveryClientHealthIndicatorInitializer(PodUtils podUtils,
ApplicationEventPublisher applicationEventPublisher) {
this.podUtils = podUtils;
this.applicationEventPublisher = applicationEventPublisher;
}
public void initialize() {
this.applicationEventPublisher.publishEvent(new InstanceRegisteredEvent<>(podUtils.currentPod(), null));
}
@Override
public void afterPropertiesSet() {
this.initialize();
}
}

View File

@@ -18,18 +18,24 @@ package org.springframework.cloud.kubernetes.fabric8.discovery;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.cloud.kubernetes.commons.ConditionalOnKubernetesEnabled;
import org.springframework.cloud.kubernetes.commons.PodUtils;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.fabric8.Fabric8AutoConfiguration;
import org.springframework.cloud.kubernetes.fabric8.registry.KubernetesRegistration;
import org.springframework.cloud.kubernetes.fabric8.registry.KubernetesServiceRegistry;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -81,6 +87,20 @@ public class KubernetesDiscoveryClientAutoConfiguration {
return new KubernetesDiscoveryProperties();
}
@ConditionalOnClass({ HealthIndicator.class })
@ConditionalOnDiscoveryEnabled
@ConditionalOnDiscoveryHealthIndicatorEnabled
@Configuration
public static class KubernetesDiscoveryClientHealthIndicatorConfiguration {
@Bean
public KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer(
ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) {
return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnKubernetesDiscoveryEnabled

View File

@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.After;
import org.junit.Test;
@@ -27,12 +28,14 @@ import org.junit.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.kubernetes.commons.PodUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Oleg Vyukov
@@ -90,6 +93,13 @@ public class KubernetesCatalogServicesWatchConfigurationTest {
return mock(KubernetesClient.class);
}
@Bean
PodUtils podUtils() {
PodUtils<Pod> podPodUtils = mock(PodUtils.class);
when(podPodUtils.currentPod()).thenReturn(() -> mock(Pod.class));
return podPodUtils;
}
}
}

View File

@@ -20,18 +20,21 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.kubernetes.commons.PodUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Ryan Dawson
@@ -90,6 +93,13 @@ public class KubernetesDiscoveryClientAutoConfigurationPropertiesTests {
return mock(KubernetesClient.class);
}
@Bean
PodUtils podUtils() {
PodUtils<Pod> podPodUtils = mock(PodUtils.class);
when(podPodUtils.currentPod()).thenReturn(() -> mock(Pod.class));
return podPodUtils;
}
}
}

View File

@@ -0,0 +1,8 @@
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: "*"

View File

@@ -25,6 +25,7 @@ import org.junit.runner.RunWith;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.Is.is;
@RequiresKubernetes
@RunWith(Arquillian.class)
@@ -55,4 +56,10 @@ public class ServicesIT {
.body("serviceId", hasItems("discovery-service-a"));
}
@Test
public void testHealthEndpoint() {
given().baseUri(String.format("%s://%s:%d", PROTOCOL, HOST, PORT)).contentType("application/json")
.get("actuator/health").then().statusCode(200).body("components.discoveryComposite.status", is("UP"));
}
}

View File

@@ -0,0 +1,8 @@
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: "*"

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.client.reactive.discovery.it;
import java.io.IOException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
@@ -59,12 +60,6 @@ public class ReactiveDiscoveryClientIT {
private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME = "spring-cloud-kubernetes-client-reactive-discovery-it";
private static final String WIREMOCK_HOST = "localhost";
private static final String WIREMOCK_PATH = "/wiremock";
private static final int WIREMOCK_PORT = 80;
private static final String NAMESPACE = "default";
private ApiClient client;
@@ -100,6 +95,7 @@ public class ReactiveDiscoveryClientIT {
try {
deployReactiveDiscoveryIt();
testLoadBalancer();
testHealth();
}
catch (Exception e) {
e.printStackTrace();
@@ -121,7 +117,20 @@ public class ReactiveDiscoveryClientIT {
private void testLoadBalancer() throws Exception {
// Check to make sure the controller deployment is ready
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, NAMESPACE);
RestTemplate rest = createRestTemplate();
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
// available and we get a 503, we just need to wait a bit
await().timeout(Duration.ofSeconds(60))
.until(() -> rest.getForEntity("http://localhost:80/reactive-discovery-it/services", String.class)
.getStatusCode().is2xxSuccessful());
String result = rest.getForObject("http://localhost:80/reactive-discovery-it/services", String.class);
assertThat(Arrays.stream(result.split(",")).anyMatch(s -> "servicea-wiremock".equalsIgnoreCase(s))).isTrue();
}
private RestTemplate createRestTemplate() {
RestTemplate rest = new RestTemplateBuilder().build();
rest.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
@@ -137,14 +146,26 @@ public class ReactiveDiscoveryClientIT {
}
});
return rest;
}
public void testHealth() {
RestTemplate rest = createRestTemplate();
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
// available and we get a 503, we just need to wait a bit
await().timeout(Duration.ofSeconds(60))
.until(() -> rest.getForEntity("http://localhost:80/reactive-discovery-it/services", String.class)
.until(() -> rest
.getForEntity("http://localhost:80/reactive-discovery-it/actuator/health", String.class)
.getStatusCode().is2xxSuccessful());
String result = rest.getForObject("http://localhost:80/reactive-discovery-it/services", String.class);
assertThat(Arrays.stream(result.split(",")).anyMatch(s -> "servicea-wiremock".equalsIgnoreCase(s))).isTrue();
Map<String, Object> health = rest.getForObject("http://localhost:80/reactive-discovery-it/actuator/health",
Map.class);
Map<String, Object> components = (Map) health.get("components");
assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue();
Map<String, Object> discoveryComposite = (Map) components.get("discoveryComposite");
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
}
@After

View File

@@ -50,6 +50,31 @@
</dependencies>
<profiles>
<profile>
<id>skaffold</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>${env.IMAGE}</name>
</image>
<goal>build-image</goal>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build-image</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>imagename</id>
<activation>

View File

@@ -6,7 +6,7 @@ build:
artifacts:
- image: springcloud/spring-cloud-kubernetes-core-k8s-client-it
custom:
buildCommand: "../../mvnw clean install"
buildCommand: "../../mvnw clean install -Pskaffold"
dependencies:
paths:
- src

View File

@@ -150,6 +150,10 @@ public class ActuatorEndpointIT {
assertThat(details.containsKey("podIp")).isTrue();
assertThat(details.containsKey("podName")).isTrue();
assertThat(details.containsKey("serviceAccount")).isTrue();
assertThat(components.containsKey("discoveryComposite")).isTrue();
Map<String, Object> discoveryComposite = (Map) components.get("discoveryComposite");
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
}
@Test