diff --git a/docs/modules/ROOT/pages/discovery-client.adoc b/docs/modules/ROOT/pages/discovery-client.adoc index 3803101fd6..a6d8346490 100644 --- a/docs/modules/ROOT/pages/discovery-client.adoc +++ b/docs/modules/ROOT/pages/discovery-client.adoc @@ -279,6 +279,30 @@ spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true - Since http discovery has _two_ components : server and client, we strongly recommend to align versions between them, otherwise things might not work. - If you decide to disable catalog watcher, you need to disable it in both server and client. +Since version `5.0.0`, there is the possibility to cache the responses from a discovery client (we do it via the `@Cacheable` annotation). There are two properties to keep in mind here: + +[source] +---- +spring.cloud.kubernetes.discovery.cacheable.reactive.enabled +---- + +and + +[source] +---- +spring.cloud.kubernetes.discovery.cacheable.blocking.enabled +---- + +The first one enables the cacheable reactive client, and the second one, the cacheable blocking client. By default, the non-cacheable discovery clients are created; if you want the cacheable one, you need to toggle one of the above properties. For example: + +[source] +---- +spring.cloud.kubernetes.discovery.cacheable.reactive.enabled=true +---- + +will provide you the cacheable reactive discovery client. + + By default, we use the `Endpoints`(see https://kubernetes.io/docs/concepts/services-networking/service/#endpoints) API to find out the current state of services. There is another way though, via `EndpointSlices` (https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/). Such support can be enabled via a property: `spring.cloud.kubernetes.discovery.use-endpoint-slices=true` (by default it is `false`). Of course, your cluster has to support it also. As a matter of fact, if you enable this property, but your cluster does not support it, we will fail starting the application. If you decide to enable such support, you also need proper Role/ClusterRole set-up. For example: [source] diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration.java index 531bc6e16d..392919f17f 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration.java @@ -25,29 +25,20 @@ import io.kubernetes.client.openapi.apis.CoreV1Api; 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; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration; import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration; -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.commons.PodUtils; -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.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableReactiveDisabled; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableReactiveEnabled; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscovery; -import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.log.LogAccessor; /** * @author Ryan Baxter @@ -62,13 +53,10 @@ KubernetesClientDiscoveryClientSpelAutoConfiguration.class }) final class KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration { - private static final LogAccessor LOG = new LogAccessor( - LogFactory.getLog(KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration.class)); - @Bean @ConditionalOnMissingBean @ConditionalOnDiscoveryCacheableReactiveDisabled - KubernetesClientInformerReactiveDiscoveryClient kubernetesClientReactiveDiscoveryClient( + KubernetesClientInformerReactiveDiscoveryClient kubernetesClientInformerReactiveDiscoveryClient( List sharedInformerFactories, List> serviceListers, List> endpointsListers, List> serviceInformers, List> endpointsInformers, KubernetesDiscoveryProperties properties, @@ -82,21 +70,10 @@ KubernetesClientInformerReactiveDiscoveryClient kubernetesClientReactiveDiscover return new KubernetesClientInformerReactiveDiscoveryClient(blockingClient); } - @Bean - @ConditionalOnBean(KubernetesClientInformerReactiveDiscoveryClient.class) - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator( - KubernetesClientInformerReactiveDiscoveryClient reactiveClient, - DiscoveryClientHealthIndicatorProperties properties) { - return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); - } - - // Above two beans are created when cacheable is disabled - @Bean @ConditionalOnMissingBean @ConditionalOnDiscoveryCacheableReactiveEnabled - KubernetesClientCacheableInformerReactiveDiscoveryClient kubernetesClientCacheableReactiveDiscoveryClient( + KubernetesClientCacheableInformerReactiveDiscoveryClient kubernetesClientCacheableInformerReactiveDiscoveryClient( List sharedInformerFactories, List> serviceListers, List> endpointsListers, List> serviceInformers, List> endpointsInformers, KubernetesDiscoveryProperties properties, @@ -110,44 +87,4 @@ KubernetesClientCacheableInformerReactiveDiscoveryClient kubernetesClientCacheab return new KubernetesClientCacheableInformerReactiveDiscoveryClient(blockingClient); } - @Bean - @ConditionalOnMissingBean(KubernetesClientInformerReactiveDiscoveryClient.class) - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - ReactiveDiscoveryClientHealthIndicator reactiveDiscoveryClientHealthIndicator( - List sharedInformerFactories, List> serviceListers, - List> endpointsListers, List> serviceInformers, - List> endpointsInformers, - KubernetesDiscoveryProperties kubernetesDiscoveryProperties, CoreV1Api coreV1Api, - Predicate predicate, DiscoveryClientHealthIndicatorProperties properties) { - - KubernetesClientInformerDiscoveryClient blockingClient = new KubernetesClientInformerDiscoveryClient( - sharedInformerFactories, serviceListers, endpointsListers, serviceInformers, endpointsInformers, - kubernetesDiscoveryProperties, coreV1Api, predicate); - blockingClient.afterPropertiesSet(); - - KubernetesClientInformerReactiveDiscoveryClient reactiveClient = new KubernetesClientInformerReactiveDiscoveryClient( - blockingClient); - - return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); - } - - // Above two beans are created when cacheable is enabled. In this case, we can't make - // KubernetesClientInformerDiscoveryClient a @Bean, since blocking discovery might be - // disabled and we do not want to allow wiring of it. - // Nevertheless, we still need an instance of KubernetesClientInformerDiscoveryClient - // in order to create the ReactiveDiscoveryClientHealthIndicator and - // KubernetesClientCacheableInformerReactiveDiscoveryClient. - // As such, we create two of such instances in each bean. - - /** - * Post an event so that health indicator is initialized. - */ - @Bean - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer( - ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { - LOG.debug(() -> "Will publish InstanceRegisteredEvent from reactive implementation"); - return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); - } - } diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveHealthAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveHealthAutoConfiguration.java new file mode 100644 index 0000000000..b005e4fca7 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveHealthAutoConfiguration.java @@ -0,0 +1,107 @@ +/* + * Copyright 2019-present 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.util.List; +import java.util.function.Predicate; + +import io.kubernetes.client.informer.SharedInformer; +import io.kubernetes.client.informer.SharedInformerFactory; +import io.kubernetes.client.informer.cache.Lister; +import io.kubernetes.client.openapi.apis.CoreV1Api; +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; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration; +import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration; +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.commons.PodUtils; +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.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscovery; +import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer; +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 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnSpringCloudKubernetesReactiveDiscovery +@AutoConfigureBefore({ SimpleReactiveDiscoveryClientAutoConfiguration.class, + ReactiveCommonsClientAutoConfiguration.class }) +@AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class, + KubernetesDiscoveryPropertiesAutoConfiguration.class, + KubernetesClientDiscoveryClientSpelAutoConfiguration.class, + KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration.class }) +class KubernetesClientInformerReactiveHealthAutoConfiguration { + + private static final LogAccessor LOG = new LogAccessor( + LogFactory.getLog(KubernetesClientInformerReactiveHealthAutoConfiguration.class)); + + @Bean + @ConditionalOnBean(KubernetesClientInformerReactiveDiscoveryClient.class) + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + ReactiveDiscoveryClientHealthIndicator nonCacheableReactiveDiscoveryClientHealthIndicator( + KubernetesClientInformerReactiveDiscoveryClient reactiveClient, + DiscoveryClientHealthIndicatorProperties properties) { + return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); + } + + @Bean + @ConditionalOnMissingBean(KubernetesClientInformerReactiveDiscoveryClient.class) + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + ReactiveDiscoveryClientHealthIndicator cacheableReactiveDiscoveryClientHealthIndicator( + List sharedInformerFactories, List> serviceListers, + List> endpointsListers, List> serviceInformers, + List> endpointsInformers, + KubernetesDiscoveryProperties kubernetesDiscoveryProperties, CoreV1Api coreV1Api, + Predicate predicate, DiscoveryClientHealthIndicatorProperties properties) { + + KubernetesClientInformerDiscoveryClient blockingClient = new KubernetesClientInformerDiscoveryClient( + sharedInformerFactories, serviceListers, endpointsListers, serviceInformers, endpointsInformers, + kubernetesDiscoveryProperties, coreV1Api, predicate); + blockingClient.afterPropertiesSet(); + + KubernetesClientInformerReactiveDiscoveryClient reactiveClient = new KubernetesClientInformerReactiveDiscoveryClient( + blockingClient); + + return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); + } + + /** + * Post an event so that health indicator is initialized. + */ + @Bean + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer( + ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { + LOG.debug(() -> "Will publish InstanceRegisteredEvent from reactive implementation"); + return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-kubernetes-client-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 3527902071..c0993e185c 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-kubernetes-client-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -3,3 +3,4 @@ org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerDi org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerReactiveDiscoveryClientAutoConfiguration org.springframework.cloud.kubernetes.client.discovery.KubernetesClientDiscoveryClientSpelAutoConfiguration +org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerReactiveHealthAutoConfiguration diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerDiscoveryClientAutoConfigurationApplicationContextTests.java index 9ed6969100..d9a1e7b8a9 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerDiscoveryClientAutoConfigurationApplicationContextTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -71,8 +71,8 @@ void discoveryEnabledDefault() { assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -90,8 +90,8 @@ void discoveryEnabledDefaultWithSelectiveNamespaces() { assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -110,8 +110,8 @@ void discoveryEnabled() { assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -131,8 +131,8 @@ void discoveryEnabledWithSelectiveNamespaces() { // reactive only present assertThat(context).hasBean("reactiveIndicatorInitializer"); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); }); @@ -194,8 +194,8 @@ void kubernetesDiscoveryEnabledWithSelectiveNamespaces() { assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -245,10 +245,11 @@ void kubernetesDiscoveryBlockingEnabled() { assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); + // one from reactive, one from ours assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -264,10 +265,11 @@ void kubernetesDiscoveryBlockingEnabledWithSelectiveNamespaces() { assertThat(context).hasSingleBean(KubernetesClientInformerDiscoveryClient.class); assertThat(context).hasSingleBean(KubernetesClientInformerReactiveDiscoveryClient.class); + // one from blocking, one from reactive assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -285,8 +287,8 @@ void kubernetesDiscoveryBlockingDisabled() { assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -304,8 +306,8 @@ void kubernetesDiscoveryBlockingDisabledWithSelectiveNamespaces() { assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -324,8 +326,8 @@ void kubernetesDiscoveryHealthIndicatorEnabled() { assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -344,8 +346,8 @@ void kubernetesDiscoveryHealthIndicatorEnabledWithSelectiveNamespaces() { assertThat(context).getBeans(KubernetesDiscoveryClientHealthIndicatorInitializer.class).hasSize(2); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -398,8 +400,8 @@ void kubernetesDiscoveryHealthIndicatorEnabledHealthIndicatorMissing() { // reactive only present assertThat(context).hasBean("reactiveIndicatorInitializer"); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -419,8 +421,8 @@ void kubernetesDiscoveryHealthIndicatorEnabledHealthIndicatorMissingWithSelectiv assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); assertThat(context).hasBean("reactiveIndicatorInitializer"); assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); - // ours - assertThat(context).hasBean("kubernetesReactiveDiscoveryClientHealthIndicator"); + // reactive is enabled and non-cacheable is the default option + assertThat(context).hasBean("nonCacheableReactiveDiscoveryClientHealthIndicator"); // from commons, not ours assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); @@ -428,6 +430,33 @@ void kubernetesDiscoveryHealthIndicatorEnabledHealthIndicatorMissingWithSelectiv }); } + /** + *
+	 *     -
+	 * 
+ */ + @Test + void kubernetesDiscoveryReactiveCacheableEnabledBlockingDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.blocking.enabled=false", + "spring.cloud.kubernetes.discovery.cacheable.reactive.enabled=true", + "spring.cloud.kubernetes.discovery.namespaces=a,b"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesClientInformerDiscoveryClient.class); + // cacheable client is present + assertThat(context).hasSingleBean(KubernetesClientCacheableInformerReactiveDiscoveryClient.class); + + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).hasSize(2); + // reactive is enabled and cacheable is selected + assertThat(context).hasBean("cacheableReactiveDiscoveryClientHealthIndicator"); + // from commons, not ours + assertThat(context).hasBean("simpleReactiveDiscoveryClientHealthIndicator"); + + assertInformerBeansPresent(context, 2); + }); + } + /** * reactive is disabled and should not impact blocking in any way */ @@ -527,7 +556,8 @@ private void setup(String... properties) { KubernetesClientInformerAutoConfiguration.class, KubernetesClientInformerDiscoveryClientAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, - KubernetesClientDiscoveryClientSpelAutoConfiguration.class)) + KubernetesClientDiscoveryClientSpelAutoConfiguration.class, + KubernetesClientInformerReactiveHealthAutoConfiguration.class)) .withUserConfiguration( KubernetesClientInformerDiscoveryClientAutoConfigurationApplicationContextTests.ApiClientConfig.class) .withPropertyValues(properties); @@ -541,7 +571,8 @@ private void setupWithFilteredClassLoader(Class cls, String... properties) { UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class, KubernetesClientInformerDiscoveryClientAutoConfiguration.class, - KubernetesClientDiscoveryClientSpelAutoConfiguration.class)) + KubernetesClientDiscoveryClientSpelAutoConfiguration.class, + KubernetesClientInformerReactiveHealthAutoConfiguration.class)) .withUserConfiguration( KubernetesClientInformerDiscoveryClientAutoConfigurationApplicationContextTests.ApiClientConfig.class) .withClassLoader(new FilteredClassLoader(cls)) diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java index 3db1ffea71..c596c5b0d2 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesClientInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -470,7 +470,8 @@ private void setup(String... properties) { KubernetesClientInformerAutoConfiguration.class, KubernetesClientInformerDiscoveryClientAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, - KubernetesClientDiscoveryClientSpelAutoConfiguration.class)) + KubernetesClientDiscoveryClientSpelAutoConfiguration.class, + KubernetesClientInformerReactiveHealthAutoConfiguration.class)) .withUserConfiguration(ApiClientConfig.class) .withPropertyValues(properties); } @@ -483,7 +484,8 @@ private void setupWithFilteredClassLoader(String name, String... properties) { UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class, KubernetesClientInformerDiscoveryClientAutoConfiguration.class, - KubernetesClientDiscoveryClientSpelAutoConfiguration.class)) + KubernetesClientDiscoveryClientSpelAutoConfiguration.class, + KubernetesClientInformerReactiveHealthAutoConfiguration.class)) .withUserConfiguration(ApiClientConfig.class) .withClassLoader(new FilteredClassLoader(name)) .withPropertyValues(properties); diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesAbstractBlockingDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesAbstractBlockingDiscoveryClient.java new file mode 100644 index 0000000000..876edb4b1e --- /dev/null +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesAbstractBlockingDiscoveryClient.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-present 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.discovery; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.commons.discovery.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +abstract class KubernetesAbstractBlockingDiscoveryClient implements DiscoveryClient { + + private final RestTemplate rest; + + private final boolean emptyNamespaces; + + private final Set namespaces; + + private final String discoveryServerUrl; + + KubernetesAbstractBlockingDiscoveryClient(RestTemplate rest, + KubernetesDiscoveryProperties kubernetesDiscoveryProperties) { + if (!StringUtils.hasText(kubernetesDiscoveryProperties.discoveryServerUrl())) { + throw new DiscoveryServerUrlInvalidException(); + } + this.rest = rest; + this.emptyNamespaces = kubernetesDiscoveryProperties.namespaces().isEmpty(); + this.namespaces = kubernetesDiscoveryProperties.namespaces(); + this.discoveryServerUrl = kubernetesDiscoveryProperties.discoveryServerUrl(); + } + + @Override + public List getServices() { + Service[] services = rest.getForEntity(discoveryServerUrl + "/apps", Service[].class).getBody(); + if (services != null && services.length > 0) { + return Arrays.stream(services).filter(this::matchNamespaces).map(Service::name).toList(); + } + return List.of(); + } + + @Override + public List getInstances(String serviceId) { + DefaultKubernetesServiceInstance[] responseBody = rest + .getForEntity(discoveryServerUrl + "/apps/" + serviceId, DefaultKubernetesServiceInstance[].class) + .getBody(); + if (responseBody != null && responseBody.length > 0) { + return Arrays.stream(responseBody).filter(this::matchNamespaces).collect(Collectors.toList()); + } + return List.of(); + } + + @Override + public abstract String description(); + + private boolean matchNamespaces(DefaultKubernetesServiceInstance kubernetesServiceInstance) { + return emptyNamespaces || namespaces.contains(kubernetesServiceInstance.getNamespace()); + } + + private boolean matchNamespaces(Service service) { + return service.serviceInstances().isEmpty() + || service.serviceInstances().stream().anyMatch(this::matchNamespaces); + } + +} diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesAbstractReactiveDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesAbstractReactiveDiscoveryClient.java new file mode 100644 index 0000000000..7715c36f44 --- /dev/null +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesAbstractReactiveDiscoveryClient.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-present 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.discovery; + +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.commons.discovery.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * @author wind57 + */ +abstract class KubernetesAbstractReactiveDiscoveryClient implements ReactiveDiscoveryClient { + + private final WebClient webClient; + + KubernetesAbstractReactiveDiscoveryClient(WebClient.Builder webClientBuilder, + KubernetesDiscoveryProperties properties) { + if (!StringUtils.hasText(properties.discoveryServerUrl())) { + throw new DiscoveryServerUrlInvalidException(); + } + webClient = webClientBuilder.baseUrl(properties.discoveryServerUrl()).build(); + } + + @Override + public Flux getServices() { + return webClient.get() + .uri("/apps") + .exchangeToFlux(clientResponse -> clientResponse.bodyToFlux(Service.class).map(Service::name)); + } + + @Override + public Flux getInstances(String serviceId) { + return webClient.get() + .uri("/apps/" + serviceId) + .exchangeToFlux(clientResponse -> clientResponse.bodyToFlux(DefaultKubernetesServiceInstance.class)); + } + + @Override + public abstract String description(); + +} diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesCacheableDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesCacheableDiscoveryClient.java new file mode 100644 index 0000000000..53348567f9 --- /dev/null +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesCacheableDiscoveryClient.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-present 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.discovery; + +import java.util.List; + +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.web.client.RestTemplate; + +/** + * @author wind57 + */ +class KubernetesCacheableDiscoveryClient extends KubernetesAbstractBlockingDiscoveryClient { + + KubernetesCacheableDiscoveryClient(RestTemplate rest, KubernetesDiscoveryProperties kubernetesDiscoveryProperties) { + super(rest, kubernetesDiscoveryProperties); + } + + @Override + @Cacheable("k8s-blocking-discovery-services") + public List getServices() { + return super.getServices(); + } + + @Override + @Cacheable("k8s-blocking-discovery-instances") + public List getInstances(String serviceId) { + return super.getInstances(serviceId); + } + + @Override + public String description() { + return "Kubernetes Cacheable Discovery Client"; + } + +} diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesCacheableReactiveDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesCacheableReactiveDiscoveryClient.java new file mode 100644 index 0000000000..06540d22c8 --- /dev/null +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesCacheableReactiveDiscoveryClient.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-present 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.discovery; + +import reactor.core.publisher.Flux; + +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * @author wind57 + */ +class KubernetesCacheableReactiveDiscoveryClient extends KubernetesAbstractReactiveDiscoveryClient { + + KubernetesCacheableReactiveDiscoveryClient(WebClient.Builder webClientBuilder, + KubernetesDiscoveryProperties properties) { + super(webClientBuilder, properties); + } + + @Override + @Cacheable("k8s-reactive-discovery-services") + public Flux getServices() { + return super.getServices(); + } + + @Override + @Cacheable("k8s-reactive-discovery-instances") + public Flux getInstances(String serviceId) { + return super.getInstances(serviceId); + } + + @Override + public String description() { + return "Reactive Cacheable Kubernetes Discovery Client"; + } + +} diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java index 620200f898..7e5f192f93 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClient.java @@ -16,40 +16,16 @@ package org.springframework.cloud.kubernetes.discovery; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; -import org.springframework.cloud.kubernetes.commons.discovery.Service; -import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; /** * @author Ryan Baxter */ -public class KubernetesDiscoveryClient implements DiscoveryClient { - - private final RestTemplate rest; - - private final boolean emptyNamespaces; - - private final Set namespaces; - - private final String discoveryServerUrl; +final class KubernetesDiscoveryClient extends KubernetesAbstractBlockingDiscoveryClient { KubernetesDiscoveryClient(RestTemplate rest, KubernetesDiscoveryProperties kubernetesDiscoveryProperties) { - if (!StringUtils.hasText(kubernetesDiscoveryProperties.discoveryServerUrl())) { - throw new DiscoveryServerUrlInvalidException(); - } - this.rest = rest; - this.emptyNamespaces = kubernetesDiscoveryProperties.namespaces().isEmpty(); - this.namespaces = kubernetesDiscoveryProperties.namespaces(); - this.discoveryServerUrl = kubernetesDiscoveryProperties.discoveryServerUrl(); + super(rest, kubernetesDiscoveryProperties); } @Override @@ -57,33 +33,4 @@ public String description() { return "Kubernetes Discovery Client"; } - @Override - public List getInstances(String serviceId) { - DefaultKubernetesServiceInstance[] responseBody = rest - .getForEntity(discoveryServerUrl + "/apps/" + serviceId, DefaultKubernetesServiceInstance[].class) - .getBody(); - if (responseBody != null && responseBody.length > 0) { - return Arrays.stream(responseBody).filter(this::matchNamespaces).collect(Collectors.toList()); - } - return List.of(); - } - - @Override - public List getServices() { - Service[] services = rest.getForEntity(discoveryServerUrl + "/apps", Service[].class).getBody(); - if (services != null && services.length > 0) { - return Arrays.stream(services).filter(this::matchNamespaces).map(Service::name).toList(); - } - return List.of(); - } - - private boolean matchNamespaces(DefaultKubernetesServiceInstance kubernetesServiceInstance) { - return emptyNamespaces || namespaces.contains(kubernetesServiceInstance.getNamespace()); - } - - private boolean matchNamespaces(Service service) { - return service.serviceInstances().isEmpty() - || service.serviceInstances().stream().anyMatch(this::matchNamespaces); - } - } diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientBlockingAutoConfiguration.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientBlockingAutoConfiguration.java index ed39320394..aecb84738c 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientBlockingAutoConfiguration.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientBlockingAutoConfiguration.java @@ -23,6 +23,8 @@ 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.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableBlockingDisabled; +import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableBlockingEnabled; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesBlockingDiscovery; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesBlockingDiscoveryHealthInitializer; import org.springframework.context.ApplicationEventPublisher; @@ -44,13 +46,6 @@ RestTemplateBuilder restTemplateBuilder() { return new RestTemplateBuilder(); } - @Bean - @ConditionalOnMissingBean - KubernetesDiscoveryClient kubernetesDiscoveryClient(RestTemplateBuilder restTemplateBuilder, - KubernetesDiscoveryProperties properties) { - return new KubernetesDiscoveryClient(restTemplateBuilder.build(), properties); - } - @Bean @ConditionalOnMissingBean PodUtils kubernetesDiscoveryPodUtils() { @@ -64,4 +59,20 @@ KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer(PodUtil return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnDiscoveryCacheableBlockingDisabled + KubernetesDiscoveryClient kubernetesDiscoveryClient(RestTemplateBuilder restTemplateBuilder, + KubernetesDiscoveryProperties properties) { + return new KubernetesDiscoveryClient(restTemplateBuilder.build(), properties); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnDiscoveryCacheableBlockingEnabled + KubernetesCacheableDiscoveryClient kubernetesCacheableDiscoveryClient(RestTemplateBuilder restTemplateBuilder, + KubernetesDiscoveryProperties properties) { + return new KubernetesCacheableDiscoveryClient(restTemplateBuilder.build(), properties); + } + } diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientHealthAutoConfiguration.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientHealthAutoConfiguration.java new file mode 100644 index 0000000000..a9ba5345e1 --- /dev/null +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientHealthAutoConfiguration.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-present 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.discovery; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties; +import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator; +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.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscovery; +import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * @author wind57 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnSpringCloudKubernetesReactiveDiscovery +@EnableConfigurationProperties({ DiscoveryClientHealthIndicatorProperties.class, KubernetesDiscoveryProperties.class }) +@AutoConfigureAfter(KubernetesDiscoveryClientReactiveAutoConfiguration.class) +class KubernetesDiscoveryClientHealthAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + PodUtils kubernetesDiscoveryPodUtils() { + return new KubernetesDiscoveryPodUtils(); + } + + @Bean + @ConditionalOnMissingBean + WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + /** + * Post an event so that health indicator is initialized. + */ + @Bean + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer( + ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { + return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); + } + + /** + * unlike the blocking implementation, we need to register the health indicator. + */ + @Bean + @ConditionalOnBean(KubernetesReactiveDiscoveryClient.class) + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator( + KubernetesReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties) { + return new ReactiveDiscoveryClientHealthIndicator(client, properties); + } + + /** + * unlike the blocking implementation, we need to register the health indicator. + */ + @Bean + @ConditionalOnMissingBean(KubernetesReactiveDiscoveryClient.class) + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + ReactiveDiscoveryClientHealthIndicator reactiveDiscoveryClientHealthIndicator(WebClient.Builder webClientBuilder, + KubernetesDiscoveryProperties kubernetesDiscoveryProperties, + DiscoveryClientHealthIndicatorProperties properties) { + + KubernetesReactiveDiscoveryClient reactiveClient = new KubernetesReactiveDiscoveryClient(webClientBuilder, + kubernetesDiscoveryProperties); + + return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); + } + +} diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientReactiveAutoConfiguration.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientReactiveAutoConfiguration.java index 4a64ba8eca..0fa62a8c7b 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientReactiveAutoConfiguration.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientReactiveAutoConfiguration.java @@ -19,13 +19,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties; -import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator; -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.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableReactiveDisabled; +import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableReactiveEnabled; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscovery; -import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; @@ -40,12 +37,7 @@ class KubernetesDiscoveryClientReactiveAutoConfiguration { @Bean @ConditionalOnMissingBean - WebClient.Builder webClientBuilder() { - return WebClient.builder(); - } - - @Bean - @ConditionalOnMissingBean + @ConditionalOnDiscoveryCacheableReactiveDisabled KubernetesReactiveDiscoveryClient kubernetesReactiveDiscoveryClient(WebClient.Builder webClientBuilder, KubernetesDiscoveryProperties properties) { return new KubernetesReactiveDiscoveryClient(webClientBuilder, properties); @@ -53,28 +45,16 @@ KubernetesReactiveDiscoveryClient kubernetesReactiveDiscoveryClient(WebClient.Bu @Bean @ConditionalOnMissingBean - PodUtils kubernetesDiscoveryPodUtils() { - return new KubernetesDiscoveryPodUtils(); + @ConditionalOnDiscoveryCacheableReactiveEnabled + KubernetesCacheableReactiveDiscoveryClient kubernetesCacheableReactiveDiscoveryClient( + WebClient.Builder webClientBuilder, KubernetesDiscoveryProperties properties) { + return new KubernetesCacheableReactiveDiscoveryClient(webClientBuilder, properties); } - /** - * Post an event so that health indicator is initialized. - */ @Bean - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer( - ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { - return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); - } - - /** - * unlike the blocking implementation, we need to register the health indicator. - */ - @Bean - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator( - KubernetesReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties) { - return new ReactiveDiscoveryClientHealthIndicator(client, properties); + @ConditionalOnMissingBean + WebClient.Builder webClientBuilder() { + return WebClient.builder(); } } diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesReactiveDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesReactiveDiscoveryClient.java index 2da8a10afe..5594af7a0c 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesReactiveDiscoveryClient.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesReactiveDiscoveryClient.java @@ -16,28 +16,16 @@ package org.springframework.cloud.kubernetes.discovery; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; -import org.springframework.cloud.kubernetes.commons.discovery.Service; -import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.client.WebClient; /** * @author Ryan Baxter */ -public class KubernetesReactiveDiscoveryClient implements ReactiveDiscoveryClient { - - private final WebClient webClient; +final class KubernetesReactiveDiscoveryClient extends KubernetesAbstractReactiveDiscoveryClient { KubernetesReactiveDiscoveryClient(WebClient.Builder webClientBuilder, KubernetesDiscoveryProperties properties) { - if (!StringUtils.hasText(properties.discoveryServerUrl())) { - throw new DiscoveryServerUrlInvalidException(); - } - webClient = webClientBuilder.baseUrl(properties.discoveryServerUrl()).build(); + super(webClientBuilder, properties); } @Override @@ -45,18 +33,4 @@ public String description() { return "Reactive Kubernetes Discovery Client"; } - @Override - public Flux getInstances(String serviceId) { - return webClient.get() - .uri("/apps/" + serviceId) - .exchangeToFlux(clientResponse -> clientResponse.bodyToFlux(DefaultKubernetesServiceInstance.class)); - } - - @Override - public Flux getServices() { - return webClient.get() - .uri("/apps") - .exchangeToFlux(clientResponse -> clientResponse.bodyToFlux(Service.class).map(Service::name)); - } - } diff --git a/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 5b99b7c50b..596d49329b 100644 --- a/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientBlockingAutoConfiguration org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientReactiveAutoConfiguration org.springframework.cloud.kubernetes.discovery.KubernetesCatalogWatchAutoConfiguration +org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientHealthAutoConfiguration diff --git a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryAutoConfigurationTests.java b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryAutoConfigurationTests.java index 06247cb911..ed15589abc 100644 --- a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryAutoConfigurationTests.java @@ -266,6 +266,112 @@ void testFor1426Issue() { }); } + /** + *
+	 *     - no property related to cacheable in the blocking implementation is set, as such:
+	 *     - KubernetesDiscoveryClient is present
+	 *     - KubernetesCacheableDiscoveryClient is not present
+	 * 
+ */ + @Test + void blockingCacheableDefault() { + setupWithFilteredClassLoader(null, "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.discovery-server-url=http://k8sdiscoveryserver"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClient.class); + assertThat(context).doesNotHaveBean(KubernetesCacheableDiscoveryClient.class); + }); + } + + /** + *
+	 *     - cacheable in the blocking implementation = false, as such:
+	 *     - KubernetesDiscoveryClient is present
+	 *     - KubernetesCacheableDiscoveryClient is not present
+	 * 
+ */ + @Test + void blockingCacheableDisabled() { + setupWithFilteredClassLoader(null, "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.cacheable.blocking.enabled=false", + "spring.cloud.kubernetes.discovery.discovery-server-url=http://k8sdiscoveryserver"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClient.class); + assertThat(context).doesNotHaveBean(KubernetesCacheableDiscoveryClient.class); + }); + } + + /** + *
+	 *     - cacheable in the blocking implementation = true, as such:
+	 *     - KubernetesDiscoveryClient is not present
+	 *     - KubernetesCacheableDiscoveryClient is present
+	 * 
+ */ + @Test + void blockingCacheableEnabled() { + setupWithFilteredClassLoader(null, "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.cacheable.blocking.enabled=true", + "spring.cloud.kubernetes.discovery.discovery-server-url=http://k8sdiscoveryserver"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesDiscoveryClient.class); + assertThat(context).hasSingleBean(KubernetesCacheableDiscoveryClient.class); + }); + } + + /** + *
+	 *     - no property related to cacheable in the reactive implementation is set, as such:
+	 *     - KubernetesReactiveDiscoveryClient is present
+	 *     - KubernetesCacheableReactiveDiscoveryClient is not present
+	 * 
+ */ + @Test + void reactiveCacheableDefault() { + setupWithFilteredClassLoader(null, "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.discovery-server-url=http://k8sdiscoveryserver"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(KubernetesCacheableReactiveDiscoveryClient.class); + }); + } + + /** + *
+	 *     - cacheable in the reactive implementation = false, as such:
+	 *     - KubernetesReactiveDiscoveryClient is present
+	 *     - KubernetesCacheableReactiveDiscoveryClient is not present
+	 * 
+ */ + @Test + void reactiveCacheableDisabled() { + setupWithFilteredClassLoader(null, "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.cacheable.reactive.enabled=false", + "spring.cloud.kubernetes.discovery.discovery-server-url=http://k8sdiscoveryserver"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(KubernetesCacheableReactiveDiscoveryClient.class); + }); + } + + /** + *
+	 *     - cacheable in the reactive implementation = true, as such:
+	 *     - KubernetesReactiveDiscoveryClient is not present
+	 *     - KubernetesCacheableReactiveDiscoveryClient is present
+	 * 
+ */ + @Test + void reactiveCacheableEnabled() { + setupWithFilteredClassLoader(null, "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.cacheable.reactive.enabled=true", + "spring.cloud.kubernetes.discovery.discovery-server-url=http://k8sdiscoveryserver"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(KubernetesCacheableReactiveDiscoveryClient.class); + }); + } + private ApplicationContextRunner applicationContextRunner; private void setupWithFilteredClassLoader(Class cls, String... properties) { @@ -273,14 +379,16 @@ private void setupWithFilteredClassLoader(Class cls, String... properties) { if (cls != null) { applicationContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(KubernetesDiscoveryClientBlockingAutoConfiguration.class, - KubernetesDiscoveryClientReactiveAutoConfiguration.class)) + KubernetesDiscoveryClientReactiveAutoConfiguration.class, + KubernetesDiscoveryClientHealthAutoConfiguration.class)) .withClassLoader(new FilteredClassLoader(cls)) .withPropertyValues(properties); } else { applicationContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(KubernetesDiscoveryClientBlockingAutoConfiguration.class, - KubernetesDiscoveryClientReactiveAutoConfiguration.class)) + KubernetesDiscoveryClientReactiveAutoConfiguration.class, + KubernetesDiscoveryClientHealthAutoConfiguration.class)) .withPropertyValues(properties); } diff --git a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java index 98cb3667de..9f4b1c04c2 100644 --- a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfigurationTests.java @@ -35,7 +35,8 @@ class KubernetesDiscoveryClientAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class, ReactiveCommonsClientAutoConfiguration.class, KubernetesDiscoveryClientReactiveAutoConfiguration.class, - KubernetesDiscoveryClientBlockingAutoConfiguration.class)); + KubernetesDiscoveryClientBlockingAutoConfiguration.class, + KubernetesDiscoveryClientHealthAutoConfiguration.class)); @Test void shouldWorkWithDefaults() { diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfiguration.java index 498b952878..bf1bbf8fe7 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfiguration.java @@ -20,34 +20,23 @@ import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.client.KubernetesClient; -import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled; import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration; import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration; -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.commons.KubernetesNamespaceProvider; -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.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration; import org.springframework.cloud.kubernetes.commons.discovery.ServicePortSecureResolver; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableReactiveDisabled; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnDiscoveryCacheableReactiveEnabled; import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscovery; -import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import org.springframework.core.log.LogAccessor; /** * Auto configuration for reactive discovery client. @@ -63,9 +52,6 @@ Fabric8DiscoveryClientSpelAutoConfiguration.class }) final class Fabric8ReactiveDiscoveryClientAutoConfiguration { - private static final LogAccessor LOG = new LogAccessor( - LogFactory.getLog(Fabric8ReactiveDiscoveryClientAutoConfiguration.class)); - @Bean @ConditionalOnMissingBean @ConditionalOnDiscoveryCacheableReactiveDisabled @@ -78,16 +64,6 @@ Fabric8ReactiveDiscoveryClient fabric8ReactiveDiscoveryClient(KubernetesClient c return new Fabric8ReactiveDiscoveryClient(fabric8DiscoveryClient); } - @Bean - @ConditionalOnBean(Fabric8ReactiveDiscoveryClient.class) - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator( - Fabric8ReactiveDiscoveryClient reactiveClient, DiscoveryClientHealthIndicatorProperties properties) { - return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); - } - - // Above two beans are created when cacheable is disabled - @Bean @ConditionalOnMissingBean @ConditionalOnDiscoveryCacheableReactiveEnabled @@ -100,24 +76,6 @@ Fabric8CacheableReactiveDiscoveryClient fabric8CacheableReactiveDiscoveryClient( return new Fabric8CacheableReactiveDiscoveryClient(blockingClient); } - @Bean - @ConditionalOnMissingBean(Fabric8ReactiveDiscoveryClient.class) - @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer - ReactiveDiscoveryClientHealthIndicator kubernetesReactiveClientHealthIndicator(KubernetesClient client, - KubernetesDiscoveryProperties kubernetesDiscoveryProperties, Predicate predicate, - DiscoveryClientHealthIndicatorProperties properties, Environment environment) { - - KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(environment); - ServicePortSecureResolver servicePortSecureResolver = new ServicePortSecureResolver( - kubernetesDiscoveryProperties); - Fabric8DiscoveryClient fabric8DiscoveryClient = new Fabric8DiscoveryClient(client, - kubernetesDiscoveryProperties, servicePortSecureResolver, namespaceProvider, predicate); - - Fabric8ReactiveDiscoveryClient reactiveClient = new Fabric8ReactiveDiscoveryClient(fabric8DiscoveryClient); - - return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); - } - // Above two beans are created when cacheable is enabled. In this case, we can't make // Fabric8DiscoveryClient a @Bean, since blocking discovery might be disabled and we // do @@ -127,16 +85,4 @@ ReactiveDiscoveryClientHealthIndicator kubernetesReactiveClientHealthIndicator(K // Fabric8CacheableReactiveDiscoveryClient // As such, we create two of such instances in each bean. - /** - * Post an event so that health indicator is initialized. - */ - @Bean - @ConditionalOnClass(name = "org.springframework.boot.health.contributor.ReactiveHealthIndicator") - @ConditionalOnDiscoveryHealthIndicatorEnabled - KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer( - ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { - LOG.debug(() -> "Will publish InstanceRegisteredEvent from reactive implementation"); - return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); - } - } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryHealthAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryHealthAutoConfiguration.java new file mode 100644 index 0000000000..ff473ff2c6 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryHealthAutoConfiguration.java @@ -0,0 +1,107 @@ +/* + * Copyright 2013-present 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.fabric8.discovery; + +import java.util.function.Predicate; + +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled; +import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration; +import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration; +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.commons.KubernetesNamespaceProvider; +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.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration; +import org.springframework.cloud.kubernetes.commons.discovery.ServicePortSecureResolver; +import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscovery; +import org.springframework.cloud.kubernetes.commons.discovery.conditionals.ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.log.LogAccessor; + +/** + * Auto configuration for reactive discovery client. + * + * @author wind57 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnSpringCloudKubernetesReactiveDiscovery +@AutoConfigureBefore({ SimpleReactiveDiscoveryClientAutoConfiguration.class, + ReactiveCommonsClientAutoConfiguration.class }) +@AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class, + Fabric8DiscoveryClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class, + Fabric8DiscoveryClientSpelAutoConfiguration.class, Fabric8ReactiveDiscoveryClientAutoConfiguration.class }) +final class Fabric8ReactiveDiscoveryHealthAutoConfiguration { + + private static final LogAccessor LOG = new LogAccessor( + LogFactory.getLog(Fabric8ReactiveDiscoveryHealthAutoConfiguration.class)); + + // when cacheable is disabled + @Bean + @ConditionalOnBean(Fabric8ReactiveDiscoveryClient.class) + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + ReactiveDiscoveryClientHealthIndicator nonCacheableReactiveDiscoveryClientHealthIndicator( + Fabric8ReactiveDiscoveryClient reactiveClient, DiscoveryClientHealthIndicatorProperties properties) { + return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); + } + + // when cacheable is enabled + @Bean + @ConditionalOnMissingBean(Fabric8ReactiveDiscoveryClient.class) + @ConditionalOnSpringCloudKubernetesReactiveDiscoveryHealthInitializer + ReactiveDiscoveryClientHealthIndicator cacheableReactiveDiscoveryClientHealthIndicator(KubernetesClient client, + KubernetesDiscoveryProperties kubernetesDiscoveryProperties, Predicate predicate, + DiscoveryClientHealthIndicatorProperties properties, Environment environment) { + + KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(environment); + ServicePortSecureResolver servicePortSecureResolver = new ServicePortSecureResolver( + kubernetesDiscoveryProperties); + Fabric8DiscoveryClient fabric8DiscoveryClient = new Fabric8DiscoveryClient(client, + kubernetesDiscoveryProperties, servicePortSecureResolver, namespaceProvider, predicate); + + Fabric8ReactiveDiscoveryClient reactiveClient = new Fabric8ReactiveDiscoveryClient(fabric8DiscoveryClient); + + return new ReactiveDiscoveryClientHealthIndicator(reactiveClient, properties); + } + + /** + * Post an event so that health indicator is initialized. + */ + @Bean + @ConditionalOnClass(name = "org.springframework.boot.health.contributor.ReactiveHealthIndicator") + @ConditionalOnDiscoveryHealthIndicatorEnabled + KubernetesDiscoveryClientHealthIndicatorInitializer reactiveIndicatorInitializer( + ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { + LOG.debug(() -> "Will publish InstanceRegisteredEvent from reactive implementation"); + return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-kubernetes-fabric8-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 8cba896bfb..ca3dd8dce2 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -2,3 +2,5 @@ org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8CatalogWatchAutoCo org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientAutoConfiguration org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8ReactiveDiscoveryClientAutoConfiguration org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientSpelAutoConfiguration +org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8ReactiveDiscoveryClientAutoConfiguration +org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8ReactiveDiscoveryHealthAutoConfiguration diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java index 85544a11ff..e71346adfe 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -219,7 +219,8 @@ private void setup(String... properties) { ReactiveCommonsClientAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class, Fabric8ReactiveDiscoveryClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class, - Fabric8DiscoveryClientSpelAutoConfiguration.class)) + Fabric8DiscoveryClientSpelAutoConfiguration.class, + Fabric8ReactiveDiscoveryHealthAutoConfiguration.class)) .withPropertyValues(properties); } @@ -229,7 +230,8 @@ private void setupWithFilteredClassLoader(String name, String... properties) { ReactiveCommonsClientAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class, Fabric8ReactiveDiscoveryClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class, - Fabric8DiscoveryClientSpelAutoConfiguration.class)) + Fabric8DiscoveryClientSpelAutoConfiguration.class, + Fabric8ReactiveDiscoveryHealthAutoConfiguration.class)) .withClassLoader(new FilteredClassLoader(name)) .withPropertyValues(properties); } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationTests.java index f44da7b311..0680c0bce5 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ReactiveDiscoveryClientAutoConfigurationTests.java @@ -41,8 +41,8 @@ class Fabric8ReactiveDiscoveryClientAutoConfigurationTests { ReactiveCommonsClientAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class, Fabric8DiscoveryClientAutoConfiguration.class, Fabric8ReactiveDiscoveryClientAutoConfiguration.class, - KubernetesDiscoveryPropertiesAutoConfiguration.class, - Fabric8DiscoveryClientSpelAutoConfiguration.class)); + KubernetesDiscoveryPropertiesAutoConfiguration.class, Fabric8DiscoveryClientSpelAutoConfiguration.class, + Fabric8ReactiveDiscoveryHealthAutoConfiguration.class)); @Test void shouldWorkWithDefaults() {