diff --git a/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/ConditionalOnKubernetesEnabled.java b/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/ConditionalOnKubernetesEnabled.java
new file mode 100644
index 0000000000..a9bbcfc228
--- /dev/null
+++ b/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/ConditionalOnKubernetesEnabled.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019-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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ * Provides a more succinct conditional spring.cloud.kubernetes.enabled.
+ *
+ * @author Tim Ysewyn
+ * @since 2.2.0
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
+public @interface ConditionalOnKubernetesEnabled {
+
+}
diff --git a/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/KubernetesAutoConfiguration.java b/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/KubernetesAutoConfiguration.java
index f235c9cd0f..077db0dc3a 100644
--- a/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/KubernetesAutoConfiguration.java
+++ b/spring-cloud-kubernetes-core/src/main/java/org/springframework/cloud/kubernetes/KubernetesAutoConfiguration.java
@@ -29,7 +29,6 @@
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -39,9 +38,10 @@
*
* @author Ioannis Canellos
* @author EddĂș MelĂ©ndez
+ * @author Tim Ysewyn
*/
@Configuration
-@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
+@ConditionalOnKubernetesEnabled
@EnableConfigurationProperties(KubernetesClientProperties.class)
public class KubernetesAutoConfiguration {
diff --git a/spring-cloud-kubernetes-discovery/pom.xml b/spring-cloud-kubernetes-discovery/pom.xml
index d641e21049..c2a028dd1e 100644
--- a/spring-cloud-kubernetes-discovery/pom.xml
+++ b/spring-cloud-kubernetes-discovery/pom.xml
@@ -42,6 +42,21 @@
spring-boot-autoconfigure
true
+
+ org.springframework.boot
+ spring-boot-actuator
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ true
+
org.springframework.cloud
spring-cloud-commons
@@ -125,6 +140,11 @@
${spring-cloud-config.version}
test
+
+ io.projectreactor
+ reactor-test
+ test
+
diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/ConditionalOnKubernetesDiscoveryEnabled.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/ConditionalOnKubernetesDiscoveryEnabled.java
new file mode 100644
index 0000000000..0bbdcbbc9f
--- /dev/null
+++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/ConditionalOnKubernetesDiscoveryEnabled.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019-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.discovery;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ * Provides a more succinct conditional
+ * spring.cloud.kubernetes.discovery.enabled.
+ *
+ * @author Tim Ysewyn
+ * @since 2.2.0
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ConditionalOnProperty(value = "spring.cloud.kubernetes.discovery.enabled",
+ matchIfMissing = true)
+public @interface ConditionalOnKubernetesDiscoveryEnabled {
+
+}
diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java
index 97060f651f..ede5d7697f 100644
--- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java
+++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java
@@ -18,12 +18,15 @@
import io.fabric8.kubernetes.client.KubernetesClient;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
+import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.kubernetes.ConditionalOnKubernetesEnabled;
+import org.springframework.cloud.kubernetes.KubernetesAutoConfiguration;
import org.springframework.cloud.kubernetes.registry.KubernetesRegistration;
import org.springframework.cloud.kubernetes.registry.KubernetesServiceRegistry;
import org.springframework.context.annotation.Bean;
@@ -37,9 +40,10 @@
*/
@Configuration
@ConditionalOnDiscoveryEnabled
-@ConditionalOnProperty(name = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
+@ConditionalOnKubernetesEnabled
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class })
+@AutoConfigureAfter({ KubernetesAutoConfiguration.class })
public class KubernetesDiscoveryClientAutoConfiguration {
@Bean
@@ -72,18 +76,6 @@ public KubernetesClientServicesFunction servicesFunction(
}
}
- @Bean
- @ConditionalOnMissingBean
- @ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.enabled",
- matchIfMissing = true)
- public KubernetesDiscoveryClient kubernetesDiscoveryClient(KubernetesClient client,
- KubernetesDiscoveryProperties properties,
- KubernetesClientServicesFunction kubernetesClientServicesFunction,
- DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
- return new KubernetesDiscoveryClient(client, properties,
- kubernetesClientServicesFunction, isServicePortSecureResolver);
- }
-
@Bean
public KubernetesServiceRegistry getServiceRegistry() {
return new KubernetesServiceRegistry();
@@ -100,4 +92,21 @@ public KubernetesDiscoveryProperties getKubernetesDiscoveryProperties() {
return new KubernetesDiscoveryProperties();
}
+ @Configuration
+ @ConditionalOnBlockingDiscoveryEnabled
+ @ConditionalOnKubernetesDiscoveryEnabled
+ public static class KubernetesDiscoveryClientConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public KubernetesDiscoveryClient kubernetesDiscoveryClient(
+ KubernetesClient client, KubernetesDiscoveryProperties properties,
+ KubernetesClientServicesFunction kubernetesClientServicesFunction,
+ DefaultIsServicePortSecureResolver isServicePortSecureResolver) {
+ return new KubernetesDiscoveryClient(client, properties,
+ kubernetesClientServicesFunction, isServicePortSecureResolver);
+ }
+
+ }
+
}
diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClient.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClient.java
new file mode 100644
index 0000000000..039ca1ee98
--- /dev/null
+++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClient.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019-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.discovery.reactive;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import reactor.core.publisher.Flux;
+import reactor.core.scheduler.Schedulers;
+
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
+import org.springframework.cloud.kubernetes.discovery.KubernetesClientServicesFunction;
+import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClient;
+import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
+import org.springframework.util.Assert;
+
+/**
+ * Kubernetes implementation of {@link ReactiveDiscoveryClient}. Currently relies on the
+ * {@link KubernetesDiscoveryClient} for feature parity.
+ *
+ * @author Tim Ysewyn
+ */
+public class KubernetesReactiveDiscoveryClient implements ReactiveDiscoveryClient {
+
+ private final KubernetesDiscoveryClient kubernetesDiscoveryClient;
+
+ public KubernetesReactiveDiscoveryClient(KubernetesClient client,
+ KubernetesDiscoveryProperties properties,
+ KubernetesClientServicesFunction kubernetesClientServicesFunction) {
+ this.kubernetesDiscoveryClient = new KubernetesDiscoveryClient(client, properties,
+ kubernetesClientServicesFunction);
+ }
+
+ @Override
+ public String description() {
+ return "Kubernetes Reactive Discovery Client";
+ }
+
+ @Override
+ public Flux getInstances(String serviceId) {
+ Assert.notNull(serviceId,
+ "[Assertion failed] - the object argument must not be null");
+ return Flux
+ .defer(() -> Flux
+ .fromIterable(kubernetesDiscoveryClient.getInstances(serviceId)))
+ .subscribeOn(Schedulers.boundedElastic());
+ }
+
+ @Override
+ public Flux getServices() {
+ return Flux
+ .defer(() -> Flux.fromIterable(kubernetesDiscoveryClient.getServices()))
+ .subscribeOn(Schedulers.boundedElastic());
+ }
+
+}
diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfiguration.java
new file mode 100644
index 0000000000..2e38c3ca37
--- /dev/null
+++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfiguration.java
@@ -0,0 +1,76 @@
+/*
+ * 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.discovery.reactive;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
+import org.springframework.cloud.client.ConditionalOnDiscoveryHealthIndicatorEnabled;
+import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
+import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration;
+import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.client.discovery.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.ConditionalOnKubernetesEnabled;
+import org.springframework.cloud.kubernetes.discovery.ConditionalOnKubernetesDiscoveryEnabled;
+import org.springframework.cloud.kubernetes.discovery.KubernetesClientServicesFunction;
+import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Auto configuration for reactive discovery client.
+ *
+ * @author Tim Ysewyn
+ */
+@Configuration
+@ConditionalOnDiscoveryEnabled
+@ConditionalOnReactiveDiscoveryEnabled
+@ConditionalOnKubernetesEnabled
+@ConditionalOnKubernetesDiscoveryEnabled
+@AutoConfigureBefore({ SimpleReactiveDiscoveryClientAutoConfiguration.class,
+ ReactiveCommonsClientAutoConfiguration.class })
+@AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class,
+ KubernetesDiscoveryClientAutoConfiguration.class })
+public class KubernetesReactiveDiscoveryClientAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public KubernetesReactiveDiscoveryClient kubernetesReactiveDiscoveryClient(
+ KubernetesClient client, KubernetesDiscoveryProperties properties,
+ KubernetesClientServicesFunction kubernetesClientServicesFunction) {
+ return new KubernetesReactiveDiscoveryClient(client, properties,
+ kubernetesClientServicesFunction);
+ }
+
+ @Bean
+ @ConditionalOnClass(
+ name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator")
+ @ConditionalOnDiscoveryHealthIndicatorEnabled
+ public ReactiveDiscoveryClientHealthIndicator kubernetesReactiveDiscoveryClientHealthIndicator(
+ KubernetesReactiveDiscoveryClient client,
+ DiscoveryClientHealthIndicatorProperties properties) {
+ return new ReactiveDiscoveryClientHealthIndicator(client, properties);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring.factories
index 37f0dbc8f4..dea3148dd9 100644
--- a/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-kubernetes-discovery/src/main/resources/META-INF/spring.factories
@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.kubernetes.discovery.KubernetesCatalogWatchAutoConfiguration, \
-org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientAutoConfiguration
+org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientAutoConfiguration, \
+org.springframework.cloud.kubernetes.discovery.reactive.KubernetesReactiveDiscoveryClientAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientConfigClientBootstrapConfiguration
diff --git a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java
new file mode 100644
index 0000000000..9a2c7e6239
--- /dev/null
+++ b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientAutoConfigurationTests.java
@@ -0,0 +1,127 @@
+/*
+ * 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.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.ReactiveCommonsClientAutoConfiguration;
+import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
+import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator;
+import org.springframework.cloud.commons.util.UtilAutoConfiguration;
+import org.springframework.cloud.kubernetes.KubernetesAutoConfiguration;
+import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientAutoConfiguration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Tim Ysewyn
+ */
+class KubernetesReactiveDiscoveryClientAutoConfigurationTests {
+
+ private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class,
+ ReactiveCommonsClientAutoConfiguration.class,
+ KubernetesAutoConfiguration.class,
+ KubernetesDiscoveryClientAutoConfiguration.class,
+ KubernetesReactiveDiscoveryClientAutoConfiguration.class));
+
+ @Test
+ public void shouldWorkWithDefaults() {
+ contextRunner.run(context -> {
+ assertThat(context).hasSingleBean(ReactiveDiscoveryClient.class);
+ assertThat(context)
+ .hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+ @Test
+ public void shouldNotHaveDiscoveryClientWhenDiscoveryDisabled() {
+ contextRunner.withPropertyValues("spring.cloud.discovery.enabled=false")
+ .run(context -> {
+ assertThat(context)
+ .doesNotHaveBean("kubernetesReactiveDiscoveryClient");
+ assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class);
+ assertThat(context).doesNotHaveBean(
+ ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+ @Test
+ public void shouldNotHaveDiscoveryClientWhenReactiveDiscoveryDisabled() {
+ contextRunner.withPropertyValues("spring.cloud.discovery.reactive.enabled=false")
+ .run(context -> {
+ assertThat(context)
+ .doesNotHaveBean("kubernetesReactiveDiscoveryClient");
+ assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class);
+ assertThat(context).doesNotHaveBean(
+ ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+ @Test
+ public void shouldNotHaveDiscoveryClientWhenKubernetesDisabled() {
+ contextRunner.withPropertyValues("spring.cloud.kubernetes.enabled=false")
+ .run(context -> {
+ assertThat(context)
+ .doesNotHaveBean("kubernetesReactiveDiscoveryClient");
+ assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class);
+ assertThat(context).doesNotHaveBean(
+ ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+ @Test
+ public void shouldNotHaveDiscoveryClientWhenKubernetesDiscoveryDisabled() {
+ contextRunner
+ .withPropertyValues("spring.cloud.kubernetes.discovery.enabled=false")
+ .run(context -> {
+ assertThat(context)
+ .doesNotHaveBean("kubernetesReactiveDiscoveryClient");
+ assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class);
+ assertThat(context).doesNotHaveBean(
+ ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+ @Test
+ public void worksWithoutWebflux() {
+ contextRunner
+ .withClassLoader(
+ new FilteredClassLoader("org.springframework.web.reactive"))
+ .run(context -> {
+ assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class);
+ assertThat(context).doesNotHaveBean(
+ ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+ @Test
+ public void worksWithoutActuator() {
+ contextRunner
+ .withClassLoader(
+ new FilteredClassLoader("org.springframework.boot.actuate"))
+ .run(context -> {
+ assertThat(context).hasSingleBean(ReactiveDiscoveryClient.class);
+ assertThat(context).doesNotHaveBean(
+ ReactiveDiscoveryClientHealthIndicator.class);
+ });
+ }
+
+}
diff --git a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java
new file mode 100644
index 0000000000..fa35fa2349
--- /dev/null
+++ b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/reactive/KubernetesReactiveDiscoveryClientTests.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2019-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.discovery.reactive;
+
+import java.util.HashMap;
+
+import io.fabric8.kubernetes.api.model.Endpoints;
+import io.fabric8.kubernetes.api.model.EndpointsBuilder;
+import io.fabric8.kubernetes.api.model.EndpointsList;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import io.fabric8.kubernetes.api.model.ServiceListBuilder;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
+
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
+import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
+import org.springframework.cloud.kubernetes.discovery.support.KubernetesExtension;
+import org.springframework.cloud.kubernetes.discovery.support.KubernetesExtension.Client;
+import org.springframework.cloud.kubernetes.discovery.support.KubernetesExtension.Server;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Tim Ysewyn
+ */
+@ExtendWith(KubernetesExtension.class)
+class KubernetesReactiveDiscoveryClientTests {
+
+ @BeforeEach
+ public void setup(@Client KubernetesClient kubernetesClient) {
+ // Configure the kubernetes master url to point to the mock server
+ System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY,
+ kubernetesClient.getConfiguration().getMasterUrl());
+ System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
+ System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
+ System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY,
+ "false");
+ System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
+ }
+
+ @Test
+ public void verifyDefaults(@Client KubernetesClient kubernetesClient) {
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ assertThat(client.description())
+ .isEqualTo("Kubernetes Reactive Discovery Client");
+ assertThat(client.getOrder()).isEqualTo(ReactiveDiscoveryClient.DEFAULT_ORDER);
+ }
+
+ @Test
+ public void shouldReturnFluxOfServices(@Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200, new ServiceListBuilder().addNewItem().withNewMetadata()
+ .withName("s1").withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().endItem().addNewItem().withNewMetadata()
+ .withName("s2").withLabels(new HashMap() {
+ {
+ put("label", "value");
+ put("label2", "value2");
+ }
+ }).endMetadata().endItem().addNewItem().withNewMetadata()
+ .withName("s3").endMetadata().endItem().build())
+ .once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux services = client.getServices();
+ StepVerifier.create(services).expectNext("s1", "s2", "s3").expectComplete()
+ .verify();
+ }
+
+ @Test
+ public void shouldReturnEmptyFluxOfServicesWhenNoInstancesFound(
+ @Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200, new ServiceListBuilder().build()).once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux services = client.getServices();
+ StepVerifier.create(services).expectNextCount(0).expectComplete().verify();
+ }
+
+ @Test
+ public void shouldReturnEmptyFluxForNonExistingService(
+ @Client KubernetesClient kubernetesClient) {
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux instances = client.getInstances("nonexistent-service");
+ StepVerifier.create(instances).expectNextCount(0).expectComplete().verify();
+ }
+
+ @Test
+ public void shouldReturnEmptyFluxWhenServiceHasNoSubsets(
+ @Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200,
+ new ServiceListBuilder().addNewItem().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().endItem().build())
+ .once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux instances = client.getInstances("existing-service");
+ StepVerifier.create(instances).expectNextCount(0).expectComplete().verify();
+ }
+
+ @Test
+ public void shouldReturnFlux(@Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200,
+ new ServiceListBuilder().addNewItem().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().endItem().build())
+ .once();
+
+ Endpoints endPoints = new EndpointsBuilder().withNewMetadata()
+ .withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
+ .addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
+ .endTargetRef().endAddress().addNewPort("http", 80, "TCP").endSubset()
+ .build();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/endpoints/existing-service")
+ .andReturn(200, endPoints).once();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/services/existing-service")
+ .andReturn(200,
+ new ServiceBuilder().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().build())
+ .once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux instances = client.getInstances("existing-service");
+ StepVerifier.create(instances).expectNextCount(1).expectComplete().verify();
+ }
+
+ @Test
+ public void shouldReturnFluxWithPrefixedMetadata(
+ @Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200,
+ new ServiceListBuilder().addNewItem().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().endItem().build())
+ .once();
+
+ Endpoints endPoints = new EndpointsBuilder().withNewMetadata()
+ .withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
+ .addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
+ .endTargetRef().endAddress().addNewPort("http", 80, "TCP").endSubset()
+ .build();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/endpoints/existing-service")
+ .andReturn(200, endPoints).once();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/services/existing-service")
+ .andReturn(200,
+ new ServiceBuilder().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().build())
+ .once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ properties.getMetadata().setAnnotationsPrefix("annotation.");
+ properties.getMetadata().setLabelsPrefix("label.");
+ properties.getMetadata().setPortsPrefix("port.");
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux instances = client.getInstances("existing-service");
+ StepVerifier.create(instances).expectNextCount(1).expectComplete().verify();
+ }
+
+ @Test
+ public void shouldReturnFluxWhenServiceHasMultiplePortsAndPrimaryPortNameIsSet(
+ @Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200,
+ new ServiceListBuilder().addNewItem().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().endItem().build())
+ .once();
+
+ Endpoints endPoints = new EndpointsBuilder().withNewMetadata()
+ .withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
+ .addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
+ .endTargetRef().endAddress().addNewPort("http", 80, "TCP")
+ .addNewPort("https", 443, "TCP").endSubset().build();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/endpoints/existing-service")
+ .andReturn(200, endPoints).once();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/services/existing-service")
+ .andReturn(200,
+ new ServiceBuilder().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().build())
+ .once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ properties.setPrimaryPortName("https");
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux instances = client.getInstances("existing-service");
+ StepVerifier.create(instances).expectNextCount(1).expectComplete().verify();
+ }
+
+ @Test
+ public void shouldReturnFluxOfServicesAcrossAllNamespaces(
+ @Client KubernetesClient kubernetesClient,
+ @Server KubernetesServer kubernetesServer) {
+ kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
+ .andReturn(200,
+ new ServiceListBuilder().addNewItem().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().endItem().build())
+ .once();
+
+ Endpoints endpoints = new EndpointsBuilder().withNewMetadata()
+ .withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
+ .addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
+ .endTargetRef().endAddress().addNewPort("http", 80, "TCP")
+ .addNewPort("https", 443, "TCP").endSubset().build();
+
+ EndpointsList endpointsList = new EndpointsList();
+ endpointsList.setItems(singletonList(endpoints));
+
+ kubernetesServer.expect().get().withPath(
+ "/api/v1/endpoints?fieldSelector=metadata.name%3Dexisting-service")
+ .andReturn(200, endpointsList).once();
+
+ kubernetesServer.expect().get()
+ .withPath("/api/v1/namespaces/test/services/existing-service")
+ .andReturn(200,
+ new ServiceBuilder().withNewMetadata()
+ .withName("existing-service")
+ .withLabels(new HashMap() {
+ {
+ put("label", "value");
+ }
+ }).endMetadata().build())
+ .once();
+
+ KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
+ properties.setAllNamespaces(true);
+ ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(
+ kubernetesClient, properties, KubernetesClient::services);
+ Flux instances = client.getInstances("existing-service");
+ StepVerifier.create(instances).expectNextCount(1).expectComplete().verify();
+ }
+
+}
diff --git a/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/support/KubernetesExtension.java b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/support/KubernetesExtension.java
new file mode 100644
index 0000000000..4ef954be02
--- /dev/null
+++ b/spring-cloud-kubernetes-discovery/src/test/java/org/springframework/cloud/kubernetes/discovery/support/KubernetesExtension.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019-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.discovery.support;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+/**
+ * @author Tim Ysewyn
+ */
+public class KubernetesExtension
+ implements ParameterResolver, BeforeEachCallback, AfterEachCallback {
+
+ private final KubernetesServer mockServer = new KubernetesServer();
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext,
+ ExtensionContext context) {
+ return (parameterContext.getParameter().isAnnotationPresent(Server.class)
+ && KubernetesServer.class
+ .isAssignableFrom(parameterContext.getParameter().getType()))
+ || (parameterContext.getParameter().isAnnotationPresent(Client.class)
+ && KubernetesClient.class.isAssignableFrom(
+ parameterContext.getParameter().getType()));
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ mockServer.before();
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) throws Exception {
+ mockServer.after();
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) throws ParameterResolutionException {
+ if (parameterContext.getParameter().isAnnotationPresent(Client.class)) {
+ return mockServer.getClient();
+ }
+ else {
+ return mockServer;
+ }
+ }
+
+ /**
+ * Enables injection of kubernetes server to test.
+ */
+ @Target({ ElementType.PARAMETER })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Server {
+
+ }
+
+ /**
+ * Enables injection of kubernetes client to test.
+ */
+ @Target({ ElementType.PARAMETER })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Client {
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-integration-tests/simple-core/src/test/java/org/springframework/cloud/kubernetes/it/GreetingAndHealthIT.java b/spring-cloud-kubernetes-integration-tests/simple-core/src/test/java/org/springframework/cloud/kubernetes/it/GreetingAndHealthIT.java
index dae83c382d..c21a13776f 100644
--- a/spring-cloud-kubernetes-integration-tests/simple-core/src/test/java/org/springframework/cloud/kubernetes/it/GreetingAndHealthIT.java
+++ b/spring-cloud-kubernetes-integration-tests/simple-core/src/test/java/org/springframework/cloud/kubernetes/it/GreetingAndHealthIT.java
@@ -46,7 +46,7 @@ public void testGreetingEndpoint() {
public void testHealthEndpoint() {
given().baseUri(String.format("%s://%s:%d", PROTOCOL, HOST, PORT))
.contentType("application/json").get("actuator/health").then()
- .statusCode(200).body("details.kubernetes.details.inside", is(true));
+ .statusCode(200).body("components.kubernetes.details.inside", is(true));
}
}