diff --git a/pom.xml b/pom.xml index 3b7305ba..5d717cab 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 1.8 1.8 @@ -57,7 +56,6 @@ maven-surefire-plugin - 2.6 false diff --git a/spring-cloud-zookeeper-discovery/pom.xml b/spring-cloud-zookeeper-discovery/pom.xml index 37d43292..5cf0cf1b 100644 --- a/spring-cloud-zookeeper-discovery/pom.xml +++ b/spring-cloud-zookeeper-discovery/pom.xml @@ -58,6 +58,11 @@ spring-boot-starter-actuator true + + org.springframework.boot + spring-boot-starter-webflux + true + org.apache.curator curator-x-discovery @@ -178,6 +183,11 @@ jsonassert test + + io.projectreactor + reactor-test + test + diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryAutoConfiguration.java index ba56dffc..8e2d83c3 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryAutoConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryAutoConfiguration.java @@ -22,12 +22,12 @@ import org.apache.curator.x.discovery.ServiceDiscovery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.CommonsClientAutoConfiguration; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; @@ -36,19 +36,16 @@ import org.springframework.context.annotation.Configuration; /** * @author Spencer Gibb + * @author Tim Ysewyn * @since 1.1.0 */ @Configuration -@ConditionalOnBean(ZookeeperDiscoveryClientConfiguration.Marker.class) +@ConditionalOnDiscoveryEnabled @ConditionalOnZookeeperDiscoveryEnabled @AutoConfigureBefore({ CommonsClientAutoConfiguration.class, NoopDiscoveryClientAutoConfiguration.class }) -@AutoConfigureAfter({ ZookeeperDiscoveryClientConfiguration.class }) public class ZookeeperDiscoveryAutoConfiguration { - @Autowired(required = false) - private ZookeeperDependencies zookeeperDependencies; - @Autowired private CuratorFramework curator; @@ -59,26 +56,14 @@ public class ZookeeperDiscoveryAutoConfiguration { return new ZookeeperDiscoveryProperties(inetUtils); } - @Bean - @ConditionalOnMissingBean - // currently means auto-registration is false. That will change when - // ZookeeperServiceDiscovery is gone - public ZookeeperDiscoveryClient zookeeperDiscoveryClient( - ServiceDiscovery serviceDiscovery, - ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) { - return new ZookeeperDiscoveryClient(serviceDiscovery, this.zookeeperDependencies, - zookeeperDiscoveryProperties); - } - @Bean public ZookeeperServiceWatch zookeeperServiceWatch( ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) { - return new ZookeeperServiceWatch(this.curator, zookeeperDiscoveryProperties); + return new ZookeeperServiceWatch(curator, zookeeperDiscoveryProperties); } @Configuration - @ConditionalOnEnabledHealthIndicator("zookeeper") - @ConditionalOnClass(Endpoint.class) + @ConditionalOnClass({ Endpoint.class, HealthIndicator.class }) protected static class ZookeeperDiscoveryHealthConfig { @Autowired(required = false) @@ -86,12 +71,13 @@ public class ZookeeperDiscoveryAutoConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledHealthIndicator("zookeeper") public ZookeeperDiscoveryHealthIndicator zookeeperDiscoveryHealthIndicator( CuratorFramework curatorFramework, ServiceDiscovery serviceDiscovery, ZookeeperDiscoveryProperties properties) { return new ZookeeperDiscoveryHealthIndicator(curatorFramework, - serviceDiscovery, this.zookeeperDependencies, properties); + serviceDiscovery, zookeeperDependencies, properties); } } diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java index b70c2704..c5eac59a 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java @@ -16,7 +16,14 @@ package org.springframework.cloud.zookeeper.discovery; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.apache.curator.x.discovery.ServiceDiscovery; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -25,12 +32,30 @@ import org.springframework.context.annotation.Configuration; * Zookeeper. * * @author Spencer Gibb + * @author Tim Ysewyn * @since 1.0.0 */ @Configuration -@ConditionalOnProperty(value = "spring.cloud.zookeeper.discovery.enabled", matchIfMissing = true) +@ConditionalOnDiscoveryEnabled +@ConditionalOnBlockingDiscoveryEnabled +@ConditionalOnZookeeperDiscoveryEnabled +@AutoConfigureBefore({ ZookeeperDiscoveryAutoConfiguration.class }) public class ZookeeperDiscoveryClientConfiguration { + @Autowired(required = false) + private ZookeeperDependencies zookeeperDependencies; + + @Bean + @ConditionalOnMissingBean + // currently means auto-registration is false. That will change when + // ZookeeperServiceDiscovery is gone + public ZookeeperDiscoveryClient zookeeperDiscoveryClient( + ServiceDiscovery serviceDiscovery, + ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) { + return new ZookeeperDiscoveryClient(serviceDiscovery, zookeeperDependencies, + zookeeperDiscoveryProperties); + } + @Bean public Marker zookeeperDiscoveryClientMarker() { return new Marker(); diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration.java index 851f6c45..95b8e635 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/configclient/ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration.java @@ -18,6 +18,8 @@ package org.springframework.cloud.zookeeper.discovery.configclient; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator; import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration; @@ -34,6 +36,7 @@ import org.springframework.core.annotation.Order; * Helper for config client that wants to lookup the config server via discovery. * * @author Spencer Gibb + * @author Tim Ysewyn */ @ConditionalOnClass(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) @@ -41,6 +44,7 @@ import org.springframework.core.annotation.Order; @Import({ ZookeeperAutoConfiguration.class, ZookeeperDiscoveryClientConfiguration.class, CuratorServiceDiscoveryAutoConfiguration.class, ZookeeperDiscoveryAutoConfiguration.class }) +@EnableConfigurationProperties({DiscoveryClientHealthIndicatorProperties.class}) @Order(0) public class ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration { diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClient.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClient.java new file mode 100644 index 00000000..1f7881b8 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClient.java @@ -0,0 +1,123 @@ +/* + * 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.zookeeper.discovery.reactive; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties; +import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance; +import org.springframework.cloud.zookeeper.discovery.ZookeeperServiceInstance; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; + +/** + * Zookeeper version of {@link ReactiveDiscoveryClient}. Capable of resolving aliases from + * {@link org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies} to service names in Zookeeper. + * + * @author Tim Ysewyn + * @since 2.2.0 + */ +public class ZookeeperReactiveDiscoveryClient implements ReactiveDiscoveryClient { + + private static final Logger logger = LoggerFactory.getLogger(ZookeeperReactiveDiscoveryClient.class); + + private final ServiceDiscovery serviceDiscovery; + + private final ZookeeperDependencies zookeeperDependencies; + + private final ZookeeperDiscoveryProperties zookeeperDiscoveryProperties; + + public ZookeeperReactiveDiscoveryClient(ServiceDiscovery serviceDiscovery, + ZookeeperDependencies zookeeperDependencies, ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) { + this.serviceDiscovery = serviceDiscovery; + this.zookeeperDependencies = zookeeperDependencies; + this.zookeeperDiscoveryProperties = zookeeperDiscoveryProperties; + } + + @Override + public String description() { + return "Spring Cloud Zookeeper Reactive Discovery Client"; + } + + @Override + public Flux getInstances(String serviceId) { + String serviceIdToQuery = serviceIdToQuery(serviceId); + return Mono.justOrEmpty(serviceIdToQuery) + .flatMapMany(getInstancesFromZookeeper()) + .subscribeOn(Schedulers.boundedElastic()) + .map(zkInstance -> toZookeeperServiceInstance(serviceIdToQuery, zkInstance)); + } + + private Function>> getInstancesFromZookeeper() { + return service -> { + try { + return Flux.fromIterable(serviceDiscovery.queryForInstances(service)); + } + catch (Exception e) { + logger.error("Error getting instances from zookeeper. Possibly, no service has registered.", e); + return Flux.empty(); + } + }; + } + + private ZookeeperServiceInstance toZookeeperServiceInstance(String serviceId, + org.apache.curator.x.discovery.ServiceInstance zkInstanceServiceInstance) { + return new ZookeeperServiceInstance(serviceId, zkInstanceServiceInstance); + } + + @Override + public Flux getServices() { + return Flux.defer(getServicesFromZookeeper()) + .subscribeOn(Schedulers.boundedElastic()); + } + + private Supplier> getServicesFromZookeeper() { + return () -> { + try { + return Flux.fromIterable(serviceDiscovery.queryForNames()); + } + catch (Exception e) { + logger.error("Error getting services from zookeeper. Possibly, no service has registered.", e); + return Flux.empty(); + } + }; + } + + private String serviceIdToQuery(String serviceId) { + if (zookeeperDependencies != null + && zookeeperDependencies.hasDependencies()) { + String pathForAlias = zookeeperDependencies.getPathForAlias(serviceId); + return pathForAlias.isEmpty() ? serviceId : pathForAlias; + } + return serviceId; + } + + @Override + public int getOrder() { + return zookeeperDiscoveryProperties.getOrder(); + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientConfiguration.java new file mode 100644 index 00000000..0ab45e9d --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientConfiguration.java @@ -0,0 +1,74 @@ +/* + * 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.zookeeper.discovery.reactive; + +import org.apache.curator.x.discovery.ServiceDiscovery; + +import org.springframework.beans.factory.annotation.Autowired; +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.zookeeper.ZookeeperAutoConfiguration; +import org.springframework.cloud.zookeeper.discovery.ConditionalOnZookeeperDiscoveryEnabled; +import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties; +import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link org.springframework.cloud.client.discovery.ReactiveDiscoveryClient} configuration for + * Zookeeper. + * + * @author Tim Ysewyn + * @since 2.2.0 + */ +@Configuration +@ConditionalOnDiscoveryEnabled +@ConditionalOnReactiveDiscoveryEnabled +@ConditionalOnZookeeperDiscoveryEnabled +@AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class, ZookeeperAutoConfiguration.class}) +@AutoConfigureBefore(ReactiveCommonsClientAutoConfiguration.class) +public class ZookeeperReactiveDiscoveryClientConfiguration { + + @Autowired(required = false) + private ZookeeperDependencies zookeeperDependencies; + + @Bean + @ConditionalOnMissingBean + public ZookeeperReactiveDiscoveryClient zookeeperReactiveDiscoveryClient(ServiceDiscovery serviceDiscovery, + ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) { + return new ZookeeperReactiveDiscoveryClient(serviceDiscovery, zookeeperDependencies, zookeeperDiscoveryProperties); + } + + @Bean + @ConditionalOnClass(name = "org.springframework.boot.actuate.health.ReactiveHealthIndicator") + @ConditionalOnDiscoveryHealthIndicatorEnabled + public ReactiveDiscoveryClientHealthIndicator zookeeperReactiveDiscoveryClientHealthIndicator( + ZookeeperReactiveDiscoveryClient client, + DiscoveryClientHealthIndicatorProperties properties) { + return new ReactiveDiscoveryClientHealthIndicator(client, properties); + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/support/CuratorServiceDiscoveryAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/support/CuratorServiceDiscoveryAutoConfiguration.java index 30804428..5ae4fd4f 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/support/CuratorServiceDiscoveryAutoConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/support/CuratorServiceDiscoveryAutoConfiguration.java @@ -24,6 +24,7 @@ import org.apache.curator.x.discovery.details.JsonInstanceSerializer; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.zookeeper.discovery.ConditionalOnZookeeperDiscoveryEnabled; import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryAutoConfiguration; import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties; @@ -36,6 +37,7 @@ import org.springframework.context.annotation.Configuration; * @author Spencer Gibb */ @Configuration +@ConditionalOnDiscoveryEnabled @ConditionalOnZookeeperDiscoveryEnabled @AutoConfigureBefore({ ZookeeperDiscoveryAutoConfiguration.class, ZookeeperServiceRegistryAutoConfiguration.class }) diff --git a/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories index b53623ba..bf5b9e66 100644 --- a/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories @@ -10,12 +10,12 @@ org.springframework.cloud.zookeeper.discovery.watcher.DependencyWatcherAutoConfi org.springframework.cloud.zookeeper.serviceregistry.ZookeeperAutoServiceRegistrationAutoConfiguration,\ org.springframework.cloud.zookeeper.serviceregistry.ZookeeperServiceRegistryAutoConfiguration,\ org.springframework.cloud.zookeeper.support.CuratorServiceDiscoveryAutoConfiguration,\ -org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClientConfiguration +org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClientConfiguration, \ +org.springframework.cloud.zookeeper.discovery.reactive.ZookeeperReactiveDiscoveryClientConfiguration # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.zookeeper.discovery.dependency.DependencyEnvironmentPostProcessor - org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.zookeeper.discovery.configclient.ZookeeperDiscoveryClientConfigServiceBootstrapConfiguration diff --git a/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientConfigurationTests.java b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientConfigurationTests.java new file mode 100644 index 00000000..6fe87ca6 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientConfigurationTests.java @@ -0,0 +1,128 @@ +/* + * 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.zookeeper.discovery.reactive; + +import org.apache.curator.framework.CuratorFramework; +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.TestConfiguration; +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.zookeeper.discovery.ZookeeperDiscoveryAutoConfiguration; +import org.springframework.cloud.zookeeper.support.CuratorServiceDiscoveryAutoConfiguration; +import org.springframework.context.annotation.Bean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * @author Tim Ysewyn + */ +class ZookeeperReactiveDiscoveryClientConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(UtilAutoConfiguration.class, + ReactiveCommonsClientAutoConfiguration.class, + CuratorServiceDiscoveryAutoConfiguration.class, + ZookeeperDiscoveryAutoConfiguration.class, + ZookeeperReactiveDiscoveryClientConfiguration.class)) + .withUserConfiguration(MockedZookeeperConfiguration.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("zookeeperReactiveDiscoveryClient"); + 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("zookeeperReactiveDiscoveryClient"); + assertThat(context).doesNotHaveBean(ReactiveDiscoveryClient.class); + assertThat(context).doesNotHaveBean( + ReactiveDiscoveryClientHealthIndicator.class); + }); + } + + @Test + public void shouldNotHaveDiscoveryClientWhenCloudFoundryDiscoveryDisabled() { + contextRunner + .withPropertyValues("spring.cloud.zookeeper.discovery.enabled=false") + .run(context -> { + assertThat(context).doesNotHaveBean("zookeeperReactiveDiscoveryClient"); + 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); + }); + } + + @TestConfiguration + static class MockedZookeeperConfiguration { + + @Bean + CuratorFramework curator() { + return mock(CuratorFramework.class); + } + + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientTests.java b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientTests.java new file mode 100644 index 00000000..01394252 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/java/org/springframework/cloud/zookeeper/discovery/reactive/ZookeeperReactiveDiscoveryClientTests.java @@ -0,0 +1,127 @@ +/* + * 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.zookeeper.discovery.reactive; + +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryProperties; +import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +/** + * @author Tim Ysewyn + */ +@ExtendWith(MockitoExtension.class) +class ZookeeperReactiveDiscoveryClientTests { + + @Mock + private ServiceDiscovery zkClient; + + @Mock + private ZookeeperDependencies zookeeperDependencies; + + @Mock + private ZookeeperDiscoveryProperties zookeeperDiscoveryProperties; + + @Mock + private org.apache.curator.x.discovery.ServiceInstance serviceInstance; + + @InjectMocks + private ZookeeperReactiveDiscoveryClient client; + + @Test + public void verifyDefaults() { + when(zookeeperDiscoveryProperties.getOrder()).thenReturn(1); + assertThat(client.description()).isEqualTo("Spring Cloud Zookeeper Reactive Discovery Client"); + assertThat(client.getOrder()).isEqualTo(1); + } + + @Test + public void shouldReturnFluxOfServices() throws Exception { + when(zkClient.queryForNames()).thenReturn(singletonList("my-service")); + Flux services = this.client.getServices(); + StepVerifier.create(services).expectNext("my-service").expectComplete().verify(); + } + + @Test + public void shouldReturnEmptyFluxOfServicesWhenZookeeperFails() throws Exception { + when(zkClient.queryForNames()).thenThrow(new RuntimeException()); + Flux services = this.client.getServices(); + StepVerifier.create(services).expectNextCount(0).expectComplete().verify(); + } + + @Test + public void shouldReturnEmptyFluxForNonExistingService() { + Flux services = this.client.getInstances("nonexistent-service"); + StepVerifier.create(services).expectNextCount(0).expectComplete().verify(); + } + + @Test + public void shouldReturnEmptyFluxWhenZookeeperFails() throws Exception { + when(zkClient.queryForInstances("existing-service")).thenThrow(new RuntimeException()); + Flux services = this.client.getInstances("existing-service"); + StepVerifier.create(services).expectNextCount(0).expectComplete().verify(); + } + + @Test + public void shouldReturnFluxOfServiceInstances() throws Exception { + configureServiceInstance(); + when(zookeeperDependencies.hasDependencies()).thenReturn(false); + when(zkClient.queryForInstances("existing-service")).thenReturn(singletonList(serviceInstance)); + Flux services = this.client.getInstances("existing-service"); + StepVerifier.create(services).expectNextCount(1).expectComplete().verify(); + } + + @Test + public void shouldReturnFluxOfServiceInstancesWhenNotHavingPathForAlias() throws Exception { + configureServiceInstance(); + when(zookeeperDependencies.hasDependencies()).thenReturn(true); + when(zookeeperDependencies.getPathForAlias("existing-service")).thenReturn(""); + when(zkClient.queryForInstances("existing-service")).thenReturn(singletonList(serviceInstance)); + Flux services = this.client.getInstances("existing-service"); + StepVerifier.create(services).expectNextCount(1).expectComplete().verify(); + } + + @Test + public void shouldReturnFluxOfServiceInstancesWhenHavingPathForAlias() throws Exception { + configureServiceInstance(); + when(zookeeperDependencies.hasDependencies()).thenReturn(true); + when(zookeeperDependencies.getPathForAlias("existing-service")).thenReturn("path-for-existing-service"); + when(zkClient.queryForInstances("path-for-existing-service")).thenReturn(singletonList(serviceInstance)); + Flux services = this.client.getInstances("existing-service"); + StepVerifier.create(services).expectNextCount(1).expectComplete().verify(); + } + + private void configureServiceInstance() { + when(serviceInstance.getAddress()).thenReturn("http://localhost"); + when(serviceInstance.getPort()).thenReturn(80); + when(serviceInstance.buildUriSpec()).thenReturn("http://localhost:80"); + } + +}