Fix some existing issues (#1304)
This commit is contained in:
2
.github/workflows/maven.yaml
vendored
2
.github/workflows/maven.yaml
vendored
@@ -141,7 +141,7 @@ jobs:
|
||||
CURRENT_INDEX: ${{ matrix.current_index }}
|
||||
NUMBER_OF_JOBS: ${{ matrix.number_of_jobs }}
|
||||
run: |
|
||||
|
||||
|
||||
# - find all tests
|
||||
# - exclude Fabric8IstionIT
|
||||
# - only take classes that have @Test inside them
|
||||
|
||||
@@ -209,8 +209,12 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
return serviceLister.list().stream().filter(service -> matchesServiceLabels(service, properties))
|
||||
.map(s -> s.getMetadata().getName()).distinct().toList();
|
||||
List<String> services = serviceLister.list().stream()
|
||||
.filter(service -> matchesServiceLabels(service, properties)).map(s -> s.getMetadata().getName())
|
||||
.distinct().toList();
|
||||
|
||||
LOG.debug(() -> "will return services : " + services);
|
||||
return services;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@@ -233,4 +237,9 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
+ " services) , discovery client is now available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return properties.order();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.kubernetes.client.informer.SharedInformerFactory;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
@@ -44,6 +45,7 @@ import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscover
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
@@ -58,13 +60,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
KubernetesClientInformerAutoConfiguration.class })
|
||||
public class KubernetesInformerDiscoveryClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass({ HealthIndicator.class })
|
||||
@ConditionalOnDiscoveryHealthIndicatorEnabled
|
||||
public KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer(
|
||||
ApplicationEventPublisher applicationEventPublisher, PodUtils<?> podUtils) {
|
||||
return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher);
|
||||
}
|
||||
private static final LogAccessor LOG = new LogAccessor(
|
||||
LogFactory.getLog(KubernetesInformerDiscoveryClientAutoConfiguration.class));
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient(
|
||||
@@ -76,6 +73,21 @@ public class KubernetesInformerDiscoveryClientAutoConfiguration {
|
||||
serviceLister, endpointsLister, serviceInformer, endpointsInformer, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation of this bean triggers publishing an InstanceRegisteredEvent. In turn,
|
||||
* there is the CommonsClientAutoConfiguration::DiscoveryClientHealthIndicator, that
|
||||
* implements 'ApplicationListener' that will catch this event. It also registers a
|
||||
* bean of type DiscoveryClientHealthIndicator via ObjectProvider.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnClass({ HealthIndicator.class })
|
||||
@ConditionalOnDiscoveryHealthIndicatorEnabled
|
||||
public KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer(
|
||||
ApplicationEventPublisher applicationEventPublisher, PodUtils<?> podUtils) {
|
||||
LOG.debug(() -> "Will publish InstanceRegisteredEvent from blocking implementation");
|
||||
return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient(
|
||||
|
||||
@@ -49,12 +49,8 @@ public class KubernetesInformerReactiveDiscoveryClient implements ReactiveDiscov
|
||||
serviceInformer, endpointsInformer, properties);
|
||||
}
|
||||
|
||||
KubernetesInformerReactiveDiscoveryClient(SharedInformerFactory sharedInformerFactory,
|
||||
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
|
||||
SharedInformer<V1Service> serviceInformer, SharedInformer<V1Endpoints> endpointsInformer,
|
||||
KubernetesDiscoveryProperties properties) {
|
||||
this.kubernetesDiscoveryClient = new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister,
|
||||
endpointsLister, serviceInformer, endpointsInformer, properties);
|
||||
KubernetesInformerReactiveDiscoveryClient(KubernetesInformerDiscoveryClient kubernetesDiscoveryClient) {
|
||||
this.kubernetesDiscoveryClient = kubernetesDiscoveryClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.kubernetes.client.informer.SharedInformerFactory;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
@@ -39,12 +40,19 @@ import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscov
|
||||
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientPodUtils;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
|
||||
import org.springframework.cloud.kubernetes.commons.PodUtils;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKubernetesDiscoveryEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer.RegisteredEventSource;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
@@ -61,15 +69,18 @@ import org.springframework.context.annotation.Configuration;
|
||||
KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class })
|
||||
public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator")
|
||||
@ConditionalOnDiscoveryHealthIndicatorEnabled
|
||||
private static final LogAccessor LOG = new LogAccessor(
|
||||
LogFactory.getLog(KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class));
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator(
|
||||
KubernetesInformerReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties,
|
||||
KubernetesClientPodUtils podUtils) {
|
||||
ReactiveDiscoveryClientHealthIndicator healthIndicator = new ReactiveDiscoveryClientHealthIndicator(client,
|
||||
properties);
|
||||
InstanceRegisteredEvent<?> event = new InstanceRegisteredEvent<>(podUtils.currentPod(), null);
|
||||
InstanceRegisteredEvent<RegisteredEventSource> event = new InstanceRegisteredEvent<>(
|
||||
new RegisteredEventSource("kubernetes", podUtils.isInsideKubernetes(), podUtils.currentPod().get()),
|
||||
null);
|
||||
healthIndicator.onApplicationEvent(event);
|
||||
return healthIndicator;
|
||||
}
|
||||
@@ -84,13 +95,43 @@ public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
|
||||
serviceLister, endpointsLister, serviceInformer, endpointsInformer, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post an event so that health indicator is initialized.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator")
|
||||
@ConditionalOnDiscoveryHealthIndicatorEnabled
|
||||
KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer(
|
||||
ApplicationEventPublisher applicationEventPublisher, PodUtils<?> podUtils) {
|
||||
LOG.debug(() -> "Will publish InstanceRegisteredEvent from reactive implementation");
|
||||
return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher);
|
||||
}
|
||||
|
||||
/**
|
||||
* unlike the blocking implementation, we need to register the health indicator.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator")
|
||||
@ConditionalOnDiscoveryHealthIndicatorEnabled
|
||||
ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator(
|
||||
KubernetesInformerReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties) {
|
||||
return new ReactiveDiscoveryClientHealthIndicator(client, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
KubernetesInformerReactiveDiscoveryClient kubernetesClientReactiveDiscoveryClient(
|
||||
KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient) {
|
||||
return new KubernetesInformerReactiveDiscoveryClient(kubernetesClientInformerDiscoveryClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient(
|
||||
SharedInformerFactory sharedInformerFactory, Lister<V1Service> serviceLister,
|
||||
Lister<V1Endpoints> endpointsLister, SharedInformer<V1Service> serviceInformer,
|
||||
SharedInformer<V1Endpoints> endpointsInformer, KubernetesDiscoveryProperties properties) {
|
||||
return new KubernetesInformerReactiveDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister,
|
||||
return new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister,
|
||||
serviceInformer, endpointsInformer, properties);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery.reactive;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
import io.kubernetes.client.informer.SharedIndexInformer;
|
||||
import io.kubernetes.client.informer.SharedInformerFactory;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.util.Config;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
@@ -31,6 +37,10 @@ import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfigura
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesCommonsAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -44,6 +54,13 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
|
||||
|
||||
private ApplicationContextRunner applicationContextRunner;
|
||||
|
||||
private static K3sContainer container;
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
container.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoveryEnabledDefault() {
|
||||
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false");
|
||||
@@ -190,7 +207,7 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
|
||||
KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class))
|
||||
.withPropertyValues(properties);
|
||||
.withUserConfiguration(ApiClientConfig.class).withPropertyValues(properties);
|
||||
}
|
||||
|
||||
private void setupWithFilteredClassLoader(String name, String... properties) {
|
||||
@@ -200,7 +217,22 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
|
||||
KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(name)).withPropertyValues(properties);
|
||||
.withUserConfiguration(ApiClientConfig.class).withClassLoader(new FilteredClassLoader(name))
|
||||
.withPropertyValues(properties);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ApiClientConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
ApiClient apiClient() throws Exception {
|
||||
container = Commons.container();
|
||||
container.start();
|
||||
|
||||
return Config.fromConfig(new StringReader(container.getKubeConfigYaml()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -128,6 +129,7 @@ public class KubernetesClientLoadBalancerServiceModeTests {
|
||||
// Mock this so the real implementation does not try to connect to the K8S API
|
||||
// Server
|
||||
KubernetesInformerDiscoveryClient client = mock(KubernetesInformerDiscoveryClient.class);
|
||||
Mockito.when(client.getOrder()).thenReturn(0);
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,10 @@ public final class KubernetesDiscoveryClientHealthIndicatorInitializer {
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
LOG.debug(() -> "publishing InstanceRegisteredEvent");
|
||||
this.applicationEventPublisher.publishEvent(new InstanceRegisteredEvent<>(
|
||||
InstanceRegisteredEvent<RegisteredEventSource> instanceRegisteredEvent = new InstanceRegisteredEvent<>(
|
||||
new RegisteredEventSource("kubernetes", podUtils.isInsideKubernetes(), podUtils.currentPod().get()),
|
||||
null));
|
||||
null);
|
||||
this.applicationEventPublisher.publishEvent(instanceRegisteredEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -39,6 +39,12 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.discovery.it;
|
||||
|
||||
import io.kubernetes.client.openapi.models.V1Pod;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer.RegisteredEventSource;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@Component
|
||||
public class DiscoveryApplicationListener implements ApplicationListener<InstanceRegisteredEvent<?>> {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(DiscoveryApplicationListener.class));
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
|
||||
V1Pod pod = (V1Pod) ((RegisteredEventSource) event.getSource()).pod();
|
||||
LOG.info(() -> "received InstanceRegisteredEvent from pod with 'app' label value : "
|
||||
+ pod.getMetadata().getLabels().get("app"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.kubernetes.client.discovery.it;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -32,8 +33,10 @@ public class DiscoveryController {
|
||||
|
||||
private final KubernetesInformerDiscoveryClient discoveryClient;
|
||||
|
||||
public DiscoveryController(KubernetesInformerDiscoveryClient discoveryClient) {
|
||||
this.discoveryClient = discoveryClient;
|
||||
public DiscoveryController(ObjectProvider<KubernetesInformerDiscoveryClient> discoveryClient) {
|
||||
KubernetesInformerDiscoveryClient[] local = new KubernetesInformerDiscoveryClient[1];
|
||||
discoveryClient.ifAvailable(x -> local[0] = x);
|
||||
this.discoveryClient = local[0];
|
||||
}
|
||||
|
||||
@GetMapping("/services")
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.discovery.it;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@RestController
|
||||
public class ReactiveDiscoveryController {
|
||||
|
||||
private final KubernetesInformerReactiveDiscoveryClient reactiveDiscoveryClient;
|
||||
|
||||
public ReactiveDiscoveryController(
|
||||
ObjectProvider<KubernetesInformerReactiveDiscoveryClient> reactiveDiscoveryClient) {
|
||||
KubernetesInformerReactiveDiscoveryClient[] local = new KubernetesInformerReactiveDiscoveryClient[1];
|
||||
reactiveDiscoveryClient.ifAvailable(x -> local[0] = x);
|
||||
this.reactiveDiscoveryClient = local[0];
|
||||
}
|
||||
|
||||
@GetMapping("/reactive/services")
|
||||
public Mono<List<String>> allServices() {
|
||||
return reactiveDiscoveryClient.getServices().collectList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
@@ -31,6 +31,7 @@ import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.Container;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
@@ -210,6 +211,9 @@ class KubernetesClientDiscoveryClientIT {
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
@Disabled
|
||||
// TODO will be fixed by this issue :
|
||||
// https://github.com/spring-cloud/spring-cloud-kubernetes/issues/1289
|
||||
void testSpecificNamespace() {
|
||||
util.createNamespace(NAMESPACE_A);
|
||||
util.createNamespace(NAMESPACE_B);
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.discovery;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1EnvVar;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.Container;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.boot.test.json.BasicJsonTester;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class KubernetesClientDiscoveryHealthIT {
|
||||
|
||||
private static final String REACTIVE_STATUS = "$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].status";
|
||||
|
||||
private static final String BLOCKING_STATUS = "$.components.discoveryComposite.components.discoveryClient.status";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final String IMAGE_NAME = "spring-cloud-kubernetes-client-discovery-it";
|
||||
|
||||
private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(
|
||||
KubernetesClientDiscoveryHealthIT.class);
|
||||
|
||||
private static Util util;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(IMAGE_NAME, K3S);
|
||||
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
|
||||
|
||||
util = new Util(K3S);
|
||||
util.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void after() throws Exception {
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive is disabled, only blocking is active. As such,
|
||||
* KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post
|
||||
* an InstanceRegisteredEvent.
|
||||
*
|
||||
* We assert for logs and call '/health' endpoint to see that blocking discovery
|
||||
* client was initialized.
|
||||
*/
|
||||
@Test
|
||||
void testBlockingConfiguration() {
|
||||
|
||||
manifests(true, false, Phase.CREATE);
|
||||
|
||||
assertLogStatement("Will publish InstanceRegisteredEvent from blocking implementation");
|
||||
assertLogStatement("publishing InstanceRegisteredEvent");
|
||||
assertLogStatement("Discovery Client has been initialized");
|
||||
assertLogStatement(
|
||||
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-client-discovery-it");
|
||||
|
||||
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
|
||||
|
||||
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(BLOCKING_STATUS)
|
||||
.isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathArrayValue(
|
||||
"$.components.discoveryComposite.components.discoveryClient.details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-client-discovery-it", "kubernetes");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS);
|
||||
|
||||
manifests(true, false, Phase.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Both blocking and reactive are enabled.
|
||||
*/
|
||||
@Test
|
||||
void testDefaultConfiguration() {
|
||||
|
||||
manifests(false, false, Phase.CREATE);
|
||||
|
||||
assertLogStatement("Will publish InstanceRegisteredEvent from blocking implementation");
|
||||
assertLogStatement("publishing InstanceRegisteredEvent");
|
||||
assertLogStatement("Discovery Client has been initialized");
|
||||
assertLogStatement(
|
||||
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-client-discovery-it");
|
||||
|
||||
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
|
||||
|
||||
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathStringValue("$.components.discoveryComposite.components.discoveryClient.status")
|
||||
.isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathArrayValue(
|
||||
"$.components.discoveryComposite.components.discoveryClient.details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-client-discovery-it", "kubernetes");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(
|
||||
"$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].status")
|
||||
.isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
|
||||
"$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-client-discovery-it", "kubernetes");
|
||||
|
||||
manifests(false, false, Phase.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive is enabled, blocking is disabled. As such,
|
||||
* KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post
|
||||
* an InstanceRegisteredEvent.
|
||||
*
|
||||
* We assert for logs and call '/health' endpoint to see that blocking discovery
|
||||
* client was initialized.
|
||||
*/
|
||||
@Test
|
||||
void testReactiveConfiguration() {
|
||||
|
||||
manifests(false, true, Phase.CREATE);
|
||||
|
||||
assertLogStatement("Will publish InstanceRegisteredEvent from reactive implementation");
|
||||
assertLogStatement("publishing InstanceRegisteredEvent");
|
||||
assertLogStatement("Discovery Client has been initialized");
|
||||
assertLogStatement(
|
||||
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-client-discovery-it");
|
||||
|
||||
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
|
||||
|
||||
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(REACTIVE_STATUS)
|
||||
.isEqualTo("UP");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
|
||||
"$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-client-discovery-it", "kubernetes");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS);
|
||||
|
||||
// test for services also:
|
||||
|
||||
WebClient servicesClient = builder().baseUrl("http://localhost/reactive/services").build();
|
||||
|
||||
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertThat(servicesResult).contains("spring-cloud-kubernetes-client-discovery-it");
|
||||
Assertions.assertThat(servicesResult).contains("kubernetes");
|
||||
|
||||
manifests(false, true, Phase.DELETE);
|
||||
}
|
||||
|
||||
private static void manifests(boolean disableReactive, boolean disableBlocking, Phase phase) {
|
||||
V1Deployment deployment = (V1Deployment) util.yaml("kubernetes-discovery-deployment.yaml");
|
||||
V1Service service = (V1Service) util.yaml("kubernetes-discovery-service.yaml");
|
||||
V1Ingress ingress = (V1Ingress) util.yaml("kubernetes-discovery-ingress.yaml");
|
||||
|
||||
List<V1EnvVar> envVars = new ArrayList<>(
|
||||
Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv())
|
||||
.orElse(List.of()));
|
||||
|
||||
V1EnvVar debugLevelForCommons = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY").value("DEBUG");
|
||||
|
||||
if (!disableBlocking) {
|
||||
V1EnvVar debugBlockingEnvVar = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH").value("DEBUG");
|
||||
|
||||
V1EnvVar debugLevelForBlocking = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY").value("DEBUG");
|
||||
|
||||
envVars.add(debugBlockingEnvVar);
|
||||
envVars.add(debugLevelForBlocking);
|
||||
}
|
||||
|
||||
if (!disableReactive) {
|
||||
V1EnvVar debugReactiveEnvVar = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE").value("DEBUG");
|
||||
|
||||
V1EnvVar debugLevelForReactive = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY_REACTIVE")
|
||||
.value("DEBUG");
|
||||
|
||||
V1EnvVar debugLevelForBlocking = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY").value("DEBUG");
|
||||
|
||||
envVars.add(debugReactiveEnvVar);
|
||||
envVars.add(debugLevelForBlocking);
|
||||
envVars.add(debugLevelForReactive);
|
||||
}
|
||||
|
||||
if (disableBlocking) {
|
||||
V1EnvVar disableBlockingEnvVar = new V1EnvVar().name("SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED")
|
||||
.value("FALSE");
|
||||
envVars.add(disableBlockingEnvVar);
|
||||
}
|
||||
|
||||
if (disableReactive) {
|
||||
V1EnvVar disableReactiveEnvVar = new V1EnvVar().name("SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED")
|
||||
.value("FALSE");
|
||||
envVars.add(disableReactiveEnvVar);
|
||||
}
|
||||
|
||||
envVars.add(debugLevelForCommons);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
}
|
||||
else if (phase.equals(Phase.DELETE)) {
|
||||
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
|
||||
}
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
private void assertLogStatement(String message) {
|
||||
try {
|
||||
String appPodName = K3S.execInContainer("sh", "-c",
|
||||
"kubectl get pods -l app=" + IMAGE_NAME + " -o=name --no-headers | tr -d '\n'").getStdout();
|
||||
|
||||
Container.ExecResult execResult = K3S.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim());
|
||||
String ok = execResult.getStdout();
|
||||
Assertions.assertThat(ok).contains(message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,9 +49,30 @@
|
||||
<version>${wiremock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -61,15 +82,15 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>1.16.3</version>
|
||||
<version>1.18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<version>1.16.3</version>
|
||||
<version>1.18.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -132,6 +132,9 @@ public final class Util {
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof ApiException apiException) {
|
||||
System.out.println(apiException.getResponseBody());
|
||||
}
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user