Initialize DiscoveryClient (#766)
* Initialize discovery client properly Fixes #517
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user