Fix some existing issues (#1304)

This commit is contained in:
erabii
2023-04-19 16:43:39 +03:00
committed by GitHub
parent 21a2cb2484
commit e466c1cb3a
19 changed files with 585 additions and 37 deletions

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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()));
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
/**

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"));
}
}

View File

@@ -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")

View File

@@ -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();
}
}

View File

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

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}