From 450616d3faddd5452295b6a64aeed4fa823b790b Mon Sep 17 00:00:00 2001 From: erabii Date: Sat, 4 Feb 2023 03:05:28 +0200 Subject: [PATCH] fix 1207 for kubernetes native client (#1208) --- .../pom.xml | 5 + .../CatalogSharedInformerFactory.java | 39 ++++ ...netesDiscoveryClientAutoConfiguration.java | 132 ----------- ...entConfigClientBootstrapConfiguration.java | 2 +- ...sDiscoveryPropertiesAutoConfiguration.java | 28 +++ ...ormerDiscoveryClientAutoConfiguration.java | 103 ++++++++ ...oudKubernetesInformerFactoryProcessor.java | 4 +- ...bernetesCatalogWatchAutoConfiguration.java | 5 +- ...rnetesInformerReactiveDiscoveryClient.java | 2 +- ...ctiveDiscoveryClientAutoConfiguration.java | 64 ++--- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- ...nfigClientBootstrapConfigurationTests.java | 6 +- ...oConfigurationApplicationContextTests.java | 220 ++++++++++++++++++ ...oConfigurationApplicationContextTests.java | 111 +++++++++ ...oConfigurationApplicationContextTests.java | 194 +++++++++++++++ .../src/test/resources/logback-test.xml | 15 ++ ...bernetesCatalogWatchAutoConfiguration.java | 2 +- 17 files changed, 763 insertions(+), 172 deletions(-) create mode 100644 spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/CatalogSharedInformerFactory.java delete mode 100644 spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientAutoConfiguration.java create mode 100644 spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryPropertiesAutoConfiguration.java create mode 100644 spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfiguration.java create mode 100644 spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests.java create mode 100644 spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfigurationApplicationContextTests.java create mode 100644 spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java create mode 100644 spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml diff --git a/spring-cloud-kubernetes-client-discovery/pom.xml b/spring-cloud-kubernetes-client-discovery/pom.xml index d57c9477..6b12953e 100644 --- a/spring-cloud-kubernetes-client-discovery/pom.xml +++ b/spring-cloud-kubernetes-client-discovery/pom.xml @@ -65,6 +65,11 @@ reactor-test test + + org.springframework.cloud + spring-cloud-kubernetes-test-support + test + com.github.tomakehurst wiremock-jre8-standalone diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/CatalogSharedInformerFactory.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/CatalogSharedInformerFactory.java new file mode 100644 index 00000000..2e1477bb --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/CatalogSharedInformerFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019-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 io.kubernetes.client.informer.SharedInformerFactory; +import io.kubernetes.client.openapi.models.V1Endpoints; +import io.kubernetes.client.openapi.models.V1EndpointsList; +import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceList; +import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource; +import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer; +import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers; + +@KubernetesInformers({ + @KubernetesInformer(apiTypeClass = V1Service.class, apiListTypeClass = V1ServiceList.class, + groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1", + resourcePlural = "services")), + @KubernetesInformer(apiTypeClass = V1Endpoints.class, apiListTypeClass = V1EndpointsList.class, + groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1", + resourcePlural = "endpoints")) }) +public class CatalogSharedInformerFactory extends SharedInformerFactory { + + // TODO: optimization to ease memory pressure from continuous list&watch. + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientAutoConfiguration.java deleted file mode 100644 index cf277934..00000000 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientAutoConfiguration.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2013-2019 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 io.kubernetes.client.informer.SharedInformer; -import io.kubernetes.client.informer.SharedInformerFactory; -import io.kubernetes.client.informer.cache.Lister; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.models.V1Endpoints; -import io.kubernetes.client.openapi.models.V1EndpointsList; -import io.kubernetes.client.openapi.models.V1Service; -import io.kubernetes.client.openapi.models.V1ServiceList; -import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource; -import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer; -import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers; -import io.kubernetes.client.spring.extended.controller.config.KubernetesInformerAutoConfiguration; - -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.cloud.CloudPlatform; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.CommonsClientAutoConfiguration; -import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; -import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; -import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled; -import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; -import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration; -import org.springframework.cloud.kubernetes.commons.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.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -@Configuration(proxyBeanMethods = false) -@ConditionalOnKubernetesDiscoveryEnabled -@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class, - // So that CatalogSharedInformerFactory can be processed in prior to the default - // factory - KubernetesInformerAutoConfiguration.class }) -@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class }) -@EnableConfigurationProperties(KubernetesDiscoveryProperties.class) -public class KubernetesDiscoveryClientAutoConfiguration { - - @ConditionalOnClass({ HealthIndicator.class }) - @ConditionalOnDiscoveryEnabled - @ConditionalOnDiscoveryHealthIndicatorEnabled - @Configuration - public static class KubernetesDiscoveryClientHealthIndicatorConfiguration { - - @Bean - public KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer( - ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { - return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnBlockingDiscoveryEnabled - public static class KubernetesInformerDiscoveryConfiguration { - - @Bean - @ConditionalOnMissingBean - public SpringCloudKubernetesInformerFactoryProcessor discoveryInformerConfigurer( - KubernetesNamespaceProvider kubernetesNamespaceProvider, ApiClient apiClient, - CatalogSharedInformerFactory sharedInformerFactory, Environment environment) { - // Injecting KubernetesDiscoveryProperties here would cause it to be - // initialize too early - // Instead get the all-namespaces property value from the Environment directly - boolean allNamespaces = environment.getProperty("spring.cloud.kubernetes.discovery.all-namespaces", - Boolean.class, false); - return new SpringCloudKubernetesInformerFactoryProcessor(kubernetesNamespaceProvider, apiClient, - sharedInformerFactory, allNamespaces); - } - - @Bean - @ConditionalOnMissingBean - public CatalogSharedInformerFactory catalogSharedInformerFactory(ApiClient apiClient) { - return new CatalogSharedInformerFactory(); - } - - @Bean - @ConditionalOnMissingBean - public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient( - KubernetesNamespaceProvider kubernetesNamespaceProvider, - CatalogSharedInformerFactory sharedInformerFactory, Lister serviceLister, - Lister endpointsLister, SharedInformer serviceInformer, - SharedInformer endpointsInformer, KubernetesDiscoveryProperties properties) { - return new KubernetesInformerDiscoveryClient(kubernetesNamespaceProvider.getNamespace(), - sharedInformerFactory, serviceLister, endpointsLister, serviceInformer, endpointsInformer, - properties); - } - - @KubernetesInformers({ - @KubernetesInformer(apiTypeClass = V1Service.class, apiListTypeClass = V1ServiceList.class, - groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1", - resourcePlural = "services")), - @KubernetesInformer(apiTypeClass = V1Endpoints.class, apiListTypeClass = V1EndpointsList.class, - groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1", - resourcePlural = "endpoints")) }) - class CatalogSharedInformerFactory extends SharedInformerFactory { - - // TODO: optimization to ease memory pressure from continuous list&watch. - - } - - } - -} diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfiguration.java index 656750b7..954c1c14 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfiguration.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfiguration.java @@ -23,7 +23,7 @@ import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) @ConditionalOnProperty("spring.cloud.config.discovery.enabled") -@Import({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryClientAutoConfiguration.class }) +@Import({ KubernetesClientAutoConfiguration.class, KubernetesInformerDiscoveryClientAutoConfiguration.class }) public class KubernetesDiscoveryClientConfigClientBootstrapConfiguration { } diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryPropertiesAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryPropertiesAutoConfiguration.java new file mode 100644 index 00000000..3c9f6f85 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryPropertiesAutoConfiguration.java @@ -0,0 +1,28 @@ +/* + * 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 org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; + +/** + * @author wind57 + */ +@EnableConfigurationProperties(KubernetesDiscoveryProperties.class) +public class KubernetesDiscoveryPropertiesAutoConfiguration { + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfiguration.java new file mode 100644 index 00000000..150c247c --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfiguration.java @@ -0,0 +1,103 @@ +/* + * 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 io.kubernetes.client.informer.SharedInformer; +import io.kubernetes.client.informer.cache.Lister; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.models.V1Endpoints; +import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.spring.extended.controller.config.KubernetesInformerAutoConfiguration; + +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.cloud.client.CommonsClientAutoConfiguration; +import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; +import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled; +import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; +import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration; +import org.springframework.cloud.kubernetes.commons.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.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * @author wind57 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnDiscoveryEnabled +@ConditionalOnKubernetesDiscoveryEnabled +@ConditionalOnBlockingDiscoveryEnabled +@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class, + // So that CatalogSharedInformerFactory can be processed prior to the default + // factory + KubernetesInformerAutoConfiguration.class }) +@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class }) +public class KubernetesInformerDiscoveryClientAutoConfiguration { + + @Bean + @ConditionalOnClass({ HealthIndicator.class }) + @ConditionalOnDiscoveryHealthIndicatorEnabled + public KubernetesDiscoveryClientHealthIndicatorInitializer indicatorInitializer( + ApplicationEventPublisher applicationEventPublisher, PodUtils podUtils) { + return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher); + } + + @Bean + @ConditionalOnMissingBean + public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient( + KubernetesNamespaceProvider kubernetesNamespaceProvider, CatalogSharedInformerFactory sharedInformerFactory, + Lister serviceLister, Lister endpointsLister, + SharedInformer serviceInformer, SharedInformer endpointsInformer, + KubernetesDiscoveryProperties properties) { + return new KubernetesInformerDiscoveryClient(kubernetesNamespaceProvider.getNamespace(), sharedInformerFactory, + serviceLister, endpointsLister, serviceInformer, endpointsInformer, properties); + } + + @Bean + @ConditionalOnMissingBean + public CatalogSharedInformerFactory catalogSharedInformerFactory() { + return new CatalogSharedInformerFactory(); + } + + @Bean + @ConditionalOnMissingBean + public SpringCloudKubernetesInformerFactoryProcessor discoveryInformerConfigurer( + KubernetesNamespaceProvider kubernetesNamespaceProvider, ApiClient apiClient, + CatalogSharedInformerFactory sharedInformerFactory, Environment environment) { + // Injecting KubernetesDiscoveryProperties here would cause it to be + // initialized too early. + // Instead, get the all-namespaces property value from the Environment directly + boolean allNamespaces = environment.getProperty("spring.cloud.kubernetes.discovery.all-namespaces", + Boolean.class, false); + return new SpringCloudKubernetesInformerFactoryProcessor(kubernetesNamespaceProvider, apiClient, + sharedInformerFactory, allNamespaces); + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/SpringCloudKubernetesInformerFactoryProcessor.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/SpringCloudKubernetesInformerFactoryProcessor.java index ef4d574c..def53df5 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/SpringCloudKubernetesInformerFactoryProcessor.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/SpringCloudKubernetesInformerFactoryProcessor.java @@ -43,7 +43,7 @@ import org.springframework.core.ResolvableType; /** * @author Ryan Baxter */ -class SpringCloudKubernetesInformerFactoryProcessor extends KubernetesInformerFactoryProcessor { +public class SpringCloudKubernetesInformerFactoryProcessor extends KubernetesInformerFactoryProcessor { private static final Logger log = LoggerFactory.getLogger(SpringCloudKubernetesInformerFactoryProcessor.class); @@ -58,7 +58,7 @@ class SpringCloudKubernetesInformerFactoryProcessor extends KubernetesInformerFa private final KubernetesNamespaceProvider kubernetesNamespaceProvider; @Autowired - SpringCloudKubernetesInformerFactoryProcessor(KubernetesNamespaceProvider kubernetesNamespaceProvider, + public SpringCloudKubernetesInformerFactoryProcessor(KubernetesNamespaceProvider kubernetesNamespaceProvider, ApiClient apiClient, SharedInformerFactory sharedInformerFactory, boolean allNamespaces) { super(); this.apiClient = apiClient; diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfiguration.java index 52210ba1..1e4e6152 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfiguration.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.cloud.CloudPlatform; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryPropertiesAutoConfiguration; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKubernetesCatalogEnabled; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; @@ -39,13 +40,13 @@ import org.springframework.core.env.Environment; */ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled +@ConditionalOnKubernetesCatalogEnabled @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class }) +@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class }) class KubernetesCatalogWatchAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnKubernetesCatalogEnabled KubernetesCatalogWatch kubernetesCatalogWatch(CoreV1Api coreV1Api, ApiClient apiClient, KubernetesDiscoveryProperties properties, Environment environment) { return new KubernetesCatalogWatch(coreV1Api, apiClient, properties, diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClient.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClient.java index 83262c00..a02be9a4 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClient.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClient.java @@ -36,7 +36,7 @@ import org.springframework.util.Assert; */ public class KubernetesInformerReactiveDiscoveryClient implements ReactiveDiscoveryClient { - private KubernetesInformerDiscoveryClient kubernetesDiscoveryClient; + private final KubernetesInformerDiscoveryClient kubernetesDiscoveryClient; public KubernetesInformerReactiveDiscoveryClient(KubernetesNamespaceProvider kubernetesNamespaceProvider, SharedInformerFactory sharedInformerFactory, Lister serviceLister, diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfiguration.java index 08538f41..9f8d03c8 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfiguration.java @@ -19,13 +19,9 @@ package org.springframework.cloud.kubernetes.client.discovery.reactive; import io.kubernetes.client.informer.SharedInformer; import io.kubernetes.client.informer.SharedInformerFactory; import io.kubernetes.client.informer.cache.Lister; +import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.models.V1Endpoints; -import io.kubernetes.client.openapi.models.V1EndpointsList; import io.kubernetes.client.openapi.models.V1Service; -import io.kubernetes.client.openapi.models.V1ServiceList; -import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource; -import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer; -import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -43,12 +39,15 @@ import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIn import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator; import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration; import org.springframework.cloud.kubernetes.client.KubernetesClientPodUtils; -import org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.CatalogSharedInformerFactory; +import org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryPropertiesAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.SpringCloudKubernetesInformerFactoryProcessor; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKubernetesDiscoveryEnabled; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; /** * @author Ryan Baxter @@ -56,15 +55,28 @@ import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled +@ConditionalOnKubernetesDiscoveryEnabled @ConditionalOnReactiveDiscoveryEnabled @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -@ConditionalOnKubernetesDiscoveryEnabled @AutoConfigureBefore({ SimpleReactiveDiscoveryClientAutoConfiguration.class, ReactiveCommonsClientAutoConfiguration.class }) @AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class, - KubernetesDiscoveryClientAutoConfiguration.class }) + KubernetesDiscoveryPropertiesAutoConfiguration.class }) public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration { + @Bean + @ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator") + @ConditionalOnDiscoveryHealthIndicatorEnabled + public ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator( + KubernetesInformerReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties, + KubernetesClientPodUtils podUtils) { + ReactiveDiscoveryClientHealthIndicator healthIndicator = new ReactiveDiscoveryClientHealthIndicator(client, + properties); + InstanceRegisteredEvent event = new InstanceRegisteredEvent<>(podUtils.currentPod(), null); + healthIndicator.onApplicationEvent(event); + return healthIndicator; + } + @Bean @ConditionalOnMissingBean public KubernetesInformerReactiveDiscoveryClient kubernetesReactiveDiscoveryClient( @@ -77,29 +89,23 @@ public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration { } @Bean - @ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator") - @ConditionalOnDiscoveryHealthIndicatorEnabled - public ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator( - KubernetesInformerReactiveDiscoveryClient client, DiscoveryClientHealthIndicatorProperties properties, - KubernetesClientPodUtils podUtils) { - ReactiveDiscoveryClientHealthIndicator healthIndicator = new ReactiveDiscoveryClientHealthIndicator(client, - properties); - InstanceRegisteredEvent event = new InstanceRegisteredEvent(podUtils.currentPod(), null); - healthIndicator.onApplicationEvent(event); - return healthIndicator; + @ConditionalOnMissingBean + public CatalogSharedInformerFactory catalogSharedInformerFactory() { + return new CatalogSharedInformerFactory(); } - @KubernetesInformers({ - @KubernetesInformer(apiTypeClass = V1Service.class, apiListTypeClass = V1ServiceList.class, - groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1", - resourcePlural = "services")), - @KubernetesInformer(apiTypeClass = V1Endpoints.class, apiListTypeClass = V1EndpointsList.class, - groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1", - resourcePlural = "endpoints")) }) - class CatalogSharedInformerFactory extends SharedInformerFactory { - - // TODO: optimization to ease memory pressure from continuous list&watch. - + @Bean + @ConditionalOnMissingBean + public SpringCloudKubernetesInformerFactoryProcessor discoveryInformerConfigurer( + KubernetesNamespaceProvider kubernetesNamespaceProvider, ApiClient apiClient, + CatalogSharedInformerFactory sharedInformerFactory, Environment environment) { + // Injecting KubernetesDiscoveryProperties here would cause it to be + // initialized too early. + // Instead, get the all-namespaces property value from the Environment directly + boolean allNamespaces = environment.getProperty("spring.cloud.kubernetes.discovery.all-namespaces", + Boolean.class, false); + return new SpringCloudKubernetesInformerFactoryProcessor(kubernetesNamespaceProvider, apiClient, + sharedInformerFactory, allNamespaces); } } 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 608352b9..f058c16f 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 @@ -1,3 +1,4 @@ org.springframework.cloud.kubernetes.client.discovery.catalog.KubernetesCatalogWatchAutoConfiguration -org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientAutoConfiguration +org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClientAutoConfiguration +org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryPropertiesAutoConfiguration org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClientAutoConfiguration diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java index 3c506e43..3228927e 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests.java @@ -50,7 +50,7 @@ import static org.mockito.Mockito.when; @Configuration(proxyBeanMethods = false) @ConditionalOnProperty("spring.cloud.config.discovery.enabled") -@Import({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryClientAutoConfiguration.class }) +@Import({ KubernetesClientAutoConfiguration.class, KubernetesInformerDiscoveryClientAutoConfiguration.class }) public class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests { private AnnotationConfigApplicationContext context; @@ -83,13 +83,13 @@ public class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests { TestPropertyValues.of(env).applyTo(parent); parent.register(UtilAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EnvironmentKnobbler.class, KubernetesCommonsAutoConfiguration.class, - KubernetesClientAutoConfiguration.class, KubernetesDiscoveryClientAutoConfiguration.class, + KubernetesClientAutoConfiguration.class, KubernetesInformerDiscoveryClientAutoConfiguration.class, DiscoveryClientConfigServiceBootstrapConfiguration.class, ConfigClientProperties.class); parent.refresh(); this.context = new AnnotationConfigApplicationContext(); this.context.setParent(parent); this.context.register(PropertyPlaceholderAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class, - KubernetesDiscoveryClientAutoConfiguration.class); + KubernetesInformerDiscoveryClientAutoConfiguration.class); this.context.refresh(); } diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests.java new file mode 100644 index 00000000..7aca7cf9 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -0,0 +1,220 @@ +/* + * 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.io.StringReader; + +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.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer; +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; + +/** + * Test various conditionals for + * {@link KubernetesInformerDiscoveryClientAutoConfiguration} + * + * @author wind57 + */ +class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests { + + 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"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void discoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.enabled=true"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void discoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).doesNotHaveBean(CatalogSharedInformerFactory.class); + assertThat(context).doesNotHaveBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=true"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).doesNotHaveBean(CatalogSharedInformerFactory.class); + assertThat(context).doesNotHaveBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryBlockingEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.blocking.enabled=true"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryBlockingDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.blocking.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).doesNotHaveBean(CatalogSharedInformerFactory.class); + assertThat(context).doesNotHaveBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryHealthIndicatorEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.client.health-indicator.enabled=true"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryHealthIndicatorDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.client.health-indicator.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryHealthIndicatorEnabledHealthIndicatorMissing() { + setupWithFilteredClassLoader(HealthIndicator.class, "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.config.enabled=false", "spring.cloud.discovery.client.health-indicator.enabled=true"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + /** + * reactive is disabled and should not impact blocking in any way + */ + @Test + void reactiveDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.reactive.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class); + assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + private void setup(String... properties) { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(KubernetesInformerDiscoveryClientAutoConfiguration.class, + KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class)) + .withUserConfiguration(ApiClientConfig.class).withPropertyValues(properties); + } + + private void setupWithFilteredClassLoader(Class cls, String... properties) { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(KubernetesInformerDiscoveryClientAutoConfiguration.class, + KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(cls)).withUserConfiguration(ApiClientConfig.class) + .withPropertyValues(properties); + } + + @Configuration + static class ApiClientConfig { + + @Bean + @Primary + ApiClient apiClient() throws Exception { + container = Commons.container(); + container.start(); + + return Config.fromConfig(new StringReader(container.getKubeConfigYaml())); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfigurationApplicationContextTests.java new file mode 100644 index 00000000..8f9af495 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/catalog/KubernetesCatalogWatchAutoConfigurationApplicationContextTests.java @@ -0,0 +1,111 @@ +/* + * 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.catalog; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryPropertiesAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient; +import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test various conditionals for {@link KubernetesCatalogWatch} + * + * @author wind57 + */ +class KubernetesCatalogWatchAutoConfigurationApplicationContextTests { + + private ApplicationContextRunner applicationContextRunner; + + @Test + void discoveryEnabledDefault() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(KubernetesCatalogWatch.class)); + } + + @Test + void discoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(KubernetesCatalogWatch.class)); + } + + @Test + void discoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.enabled=false"); + applicationContextRunner.run(context -> assertThat(context).doesNotHaveBean(KubernetesCatalogWatch.class)); + } + + @Test + void kubernetesDiscoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=true"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(KubernetesCatalogWatch.class)); + } + + // disabling discovery has no impact on the catalog watch. + @Test + void kubernetesDiscoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=false"); + applicationContextRunner.run(context -> assertThat(context).hasSingleBean(KubernetesCatalogWatch.class)); + } + + /** + * both blocking and reactive configs are disabled, should not influence catalog + * watcher in any way. + */ + @Test + void disableBlockingAndReactive() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.blocking.enabled=false", "spring.cloud.discovery.reactive.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesCatalogWatch.class); + assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class); + }); + } + + /** + * both blocking and reactive configs are disabled, should not influence catalog + * watcher in any way. + */ + @Test + void disableKubernetesDiscovery() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).hasSingleBean(KubernetesCatalogWatch.class); + assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class); + }); + } + + private void setup(String... properties) { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(KubernetesCatalogWatchAutoConfiguration.class, + KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class)) + .withPropertyValues(properties); + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java new file mode 100644 index 00000000..08105ecc --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests.java @@ -0,0 +1,194 @@ +/* + * 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.reactive; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator; +import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration; +import org.springframework.cloud.commons.util.UtilAutoConfiguration; +import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.CatalogSharedInformerFactory; +import org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryPropertiesAutoConfiguration; +import org.springframework.cloud.kubernetes.client.discovery.SpringCloudKubernetesInformerFactoryProcessor; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test various conditionals for + * {@link KubernetesInformerReactiveDiscoveryClientAutoConfiguration} + * + * @author wind57 + */ +class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationContextTests { + + private ApplicationContextRunner applicationContextRunner; + + @Test + void discoveryEnabledDefault() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false"); + applicationContextRunner.run(context -> { + // simple from commons and ours + assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void discoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.enabled=true"); + applicationContextRunner.run(context -> { + // simple from commons and ours + assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void discoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.enabled=false"); + applicationContextRunner.run(context -> { + // simple from commons and ours + assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); + assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(CatalogSharedInformerFactory.class); + assertThat(context).doesNotHaveBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=true"); + applicationContextRunner.run(context -> { + // simple from commons and ours + assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesDiscoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.kubernetes.discovery.enabled=false"); + applicationContextRunner.run(context -> { + // only ours as the "simple" one from commons is not picked-up + assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class); + assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(CatalogSharedInformerFactory.class); + assertThat(context).doesNotHaveBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesReactiveDiscoveryEnabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.reactive.enabled=true"); + applicationContextRunner.run(context -> { + // simple from commons and ours + assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void kubernetesReactiveDiscoveryDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.reactive.enabled=false"); + applicationContextRunner.run(context -> { + assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); + assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean(CatalogSharedInformerFactory.class); + assertThat(context).doesNotHaveBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + /** + * blocking is disabled, and it should not impact reactive in any way. + */ + @Test + void blockingDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.blocking.enabled=false"); + applicationContextRunner.run(context -> { + // simple from commons and ours + assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void healthDisabled() { + setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.client.health-indicator.enabled=false"); + applicationContextRunner.run(context -> { + // simple from commons + assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + @Test + void healthEnabledClassNotPresent() { + setupWithFilteredClassLoader("org.springframework.boot.actuate.health.ReactiveHealthIndicator", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false", + "spring.cloud.discovery.client.health-indicator.enabled=false"); + applicationContextRunner.run(context -> { + // simple from commons + assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class); + assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class); + assertThat(context).hasSingleBean(CatalogSharedInformerFactory.class); + assertThat(context).hasSingleBean(SpringCloudKubernetesInformerFactoryProcessor.class); + }); + } + + private void setup(String... properties) { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class, + KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class, + UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class)) + .withPropertyValues(properties); + } + + private void setupWithFilteredClassLoader(String name, String... properties) { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class, + KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class, + UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(name)).withPropertyValues(properties); + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml new file mode 100644 index 00000000..ee243343 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchAutoConfiguration.java index d4f56566..58334dea 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchAutoConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatchAutoConfiguration.java @@ -38,13 +38,13 @@ import org.springframework.core.env.Environment; */ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled +@ConditionalOnKubernetesCatalogEnabled @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @AutoConfigureAfter({ Fabric8AutoConfiguration.class }) public class KubernetesCatalogWatchAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnKubernetesCatalogEnabled public KubernetesCatalogWatch kubernetesCatalogWatch(KubernetesClient client, KubernetesDiscoveryProperties properties, Environment environment) { return new KubernetesCatalogWatch(client, properties, new KubernetesNamespaceProvider(environment));