Switch from Ribbon to SC LoadBalancer (#240)

* WIP

* Remove repeated dependency.

* Gitignore fix.

* Implement Spring Cloud LoadBalancer-based DependencyFeignClientAutoConfiguration and DependencyLoadBalancerAutoConfiguration.

* Fix dependency-based configurations.
Add ZookeeperServiceInstanceListSupplier.
Add LoadBalancerZookeeperAutoConfiguration.

* Adjust tests to changes in code.

* Adjust tests to changes in code. Add missing license Strings. Refactor.

* Add BlockingDependencyLoadBalancerConfiguration. Remove condition from ZookeeperDependenciesAutoConfiguration.

* Switch EmptyResponse to chooseDefault(). Fix DependencyRestTemplateAutoConfiguration. Change return type for BlockingDependencyLoadBalancerConfiguration.

* Fix zookeeper-specific BlockingLoadBalancerClient implementation.

* Remove zookeeper-specific LoadBalancerClient and ReactorExchangeFilterFunction implementations following a team discussion. Remove Ribbon-specific entries from docs. Add javadocs.

Co-authored-by: Marcin Grzejszczak <marcin@grzejszczak.pl>
This commit is contained in:
Olga Maciaszek-Sharma
2020-02-22 14:40:17 +01:00
committed by GitHub
parent fe4a48c5ec
commit 59c0010be3
37 changed files with 468 additions and 639 deletions

View File

@@ -28,9 +28,8 @@ This project provides Zookeeper integrations for Spring Boot applications throug
autoconfiguration and binding to the Spring Environment and other Spring programming model
idioms. With a few annotations, you can quickly enable and configure the common patterns
inside your application and build large distributed systems with Zookeeper based
components. The provided patterns include Service Discovery and Configuration. Integration
with Spring Cloud Netflix provides Intelligent Routing (Zuul), Client Side Load Balancing
(Ribbon), and Circuit Breaker (Hystrix).
components. The provided patterns include Service Discovery and Configuration. The project
also provides client-side load-balancing via integration with Spring Cloud LoadBalancer.
:doctype: book
@@ -51,17 +50,16 @@ with Spring Cloud Netflix provides Intelligent Routing (Zuul), Client Side Load
== Zookeeper overview
ZooKeeper is a centralized service for maintaining configuration information, naming,
providing distributed synchronization, and providing group services. See the
https://zookeeper.apache.org[Zookeeper site] for more information. Spring Cloud Zookeeper
also builds on the https://curator.apache.org[Apache Curator] project, which started life
at Netflix.
ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
See the
https://zookeeper.apache.org[Zookeeper site] for more information.
Spring Cloud Zookeeper also builds on the https://curator.apache.org[Apache Curator] project, which started life at Netflix.
== Spring Cloud Zookeeper Features
Spring Cloud Zookeeper includes the following features:
* Spring Cloud `DiscoveryClient` implementation (supports Ribbon and Zuul)
* Spring Cloud `DiscoveryClient` implementation (supports Spring Cloud LoadBalancer)
* Zookeeper-based `PropertySource` loaded during the 'bootstrap' phase
== Running the Sample

View File

@@ -6,17 +6,16 @@ include::_attributes.adoc[]
== Zookeeper overview
ZooKeeper is a centralized service for maintaining configuration information, naming,
providing distributed synchronization, and providing group services. See the
https://zookeeper.apache.org[Zookeeper site] for more information. Spring Cloud Zookeeper
also builds on the https://curator.apache.org[Apache Curator] project, which started life
at Netflix.
ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
See the
https://zookeeper.apache.org[Zookeeper site] for more information.
Spring Cloud Zookeeper also builds on the https://curator.apache.org[Apache Curator] project, which started life at Netflix.
== Spring Cloud Zookeeper Features
Spring Cloud Zookeeper includes the following features:
* Spring Cloud `DiscoveryClient` implementation (supports Ribbon and Zuul)
* Spring Cloud `DiscoveryClient` implementation (supports Spring Cloud LoadBalancer)
* Zookeeper-based `PropertySource` loaded during the 'bootstrap' phase
== Running the Sample

View File

@@ -5,8 +5,7 @@
|spring.cloud.zookeeper.block-until-connected-unit | | The unit of time related to blocking on connection to Zookeeper.
|spring.cloud.zookeeper.block-until-connected-wait | 10 | Wait time to block on connection to Zookeeper.
|spring.cloud.zookeeper.connect-string | localhost:2181 | Connection string to the Zookeeper cluster.
|spring.cloud.zookeeper.default-health-endpoint | | Default health endpoint that will be checked to verify that a dependency is alive.
|spring.cloud.zookeeper.dependencies | | Mapping of alias to ZookeeperDependency. From Ribbon perspective the alias is actually serviceID since Ribbon can't accept nested structures in serviceID.
|spring.cloud.zookeeper.dependencies | | Mapping of alias to ZookeeperDependency. From LoadBalancer perspective the alias is actually serviceID since SC LoadBalancer can't accept nested structures in serviceID.
|spring.cloud.zookeeper.dependency-configurations | |
|spring.cloud.zookeeper.dependency-names | |
|spring.cloud.zookeeper.discovery.enabled | true |

View File

@@ -5,6 +5,5 @@ This project provides Zookeeper integrations for Spring Boot applications throug
autoconfiguration and binding to the Spring Environment and other Spring programming model
idioms. With a few annotations, you can quickly enable and configure the common patterns
inside your application and build large distributed systems with Zookeeper based
components. The provided patterns include Service Discovery and Configuration. Integration
with Spring Cloud Netflix provides Intelligent Routing (Zuul), Client Side Load Balancing
(Ribbon), and Circuit Breaker (Hystrix).
components. The provided patterns include Service Discovery and Configuration. The project
also provides client-side load-balancing via integration with Spring Cloud LoadBalancer.

View File

@@ -6,8 +6,8 @@ Spring Cloud Zookeeper provides http://zookeeper.apache.org/[Apache Zookeeper] i
## Features
* Service Discovery: instances can be registered with Zookeeper and clients can discover the instances using Spring-managed beans
* Supports Ribbon, the client side load-balancer via Spring Cloud Netflix
* Supports Zuul, a dynamic router and filter via Spring Cloud Netflix
* Supports Spring Cloud LoadBalancer - client-side load-balancing solution
* Supports Spring Cloud OpenFeign
* Distributed Configuration: using Zookeeper as a data store
## Quick Start

View File

@@ -164,14 +164,14 @@ public String serviceUrl() {
== Using Spring Cloud Zookeeper with Spring Cloud Netflix Components
Spring Cloud Netflix supplies useful tools that work regardless of which `DiscoveryClient`
implementation you use. Feign, Turbine, Ribbon, and Zuul all work with Spring Cloud
Zookeeper.
implementation you use.
Feign and Spring Cloud LoadBalancer also work with Spring Cloud Zookeeper.
=== Ribbon with Zookeeper
=== Spring Cloud LoadBalancer with Zookeeper
Spring Cloud Zookeeper provides an implementation of Ribbon's `ServerList`. When you use
the `spring-cloud-starter-zookeeper-discovery`, Ribbon is autoconfigured to use the
`ZookeeperServerList` by default.
Spring Cloud Zookeeper provides an implementation of Spring Cloud LoadBalancer `ServiceInstanceListSupplier`.
When you use the `spring-cloud-starter-zookeeper-discovery`, Spring Cloud LoadBalancer is autoconfigured to use the
`ZookeeperServiceInstanceListSupplier` by default.
[[spring-cloud-zookeeper-service-registry]]
== Spring Cloud Zookeeper and Service Registry
@@ -201,15 +201,13 @@ public void registerThings() {
=== Instance Status
Netflix Eureka supports having instances that are `OUT_OF_SERVICE` registered with the
server. These instances are not returned as active service instances. This is useful for
behaviors such as blue/green deployments. (Note that the Curator Service Discovery recipe
does not support this behavior.) Taking advantage of the flexible payload has let Spring
Cloud Zookeeper implement `OUT_OF_SERVICE` by updating some specific metadata and then
filtering on that metadata in the Ribbon `ZookeeperServerList`. The `ZookeeperServerList`
filters out all non-null instance statuses that do not equal `UP`. If the instance status
field is empty, it is considered to be `UP` for backwards compatibility. To change the
status of an instance, make a `POST` with `OUT_OF_SERVICE` to the `ServiceRegistry`
Netflix Eureka supports having instances that are `OUT_OF_SERVICE` registered with the server.
These instances are not returned as active service instances.
This is useful for behaviors such as blue/green deployments.
(Note that the Curator Service Discovery recipe does not support this behavior.) Taking advantage of the flexible payload has let Spring Cloud Zookeeper implement `OUT_OF_SERVICE` by updating some specific metadata and then filtering on that metadata in the Spring Cloud LoadBalancer `ZookeeperServiceInstanceListSupplier`.
The `ZookeeperServiceInstanceListSupplier` filters out all non-null instance statuses that do not equal `UP`.
If the instance status field is empty, it is considered to be `UP` for backwards compatibility.
To change the status of an instance, make a `POST` with `OUT_OF_SERVICE` to the `ServiceRegistry`
instance status actuator endpoint, as shown in the following example:
[source,sh,indent=0]
@@ -290,14 +288,14 @@ name is `spring.cloud.zookeeper.dependencies`.
[[spring-cloud-zookeeper-dependencies-setting-up-aliases]]
==== Aliases
Below the root property you have to represent each dependency as an alias. This is due to
the constraints of Ribbon, which requires that the application ID be placed in the URL.
Consequently, you cannot pass any complex path, suchas `/myApp/myRoute/name`). The alias
is the name you use instead of the `serviceId` for `DiscoveryClient`, `Feign`, or
Below the root property you have to represent each dependency as an alias.
This is due to the constraints of Spring Cloud LoadBalancer, which requires that the application ID be placed in the URL.
Consequently, you cannot pass any complex path, suchas `/myApp/myRoute/name`).
The alias is the name you use instead of the `serviceId` for `DiscoveryClient`, `Feign`, or
`RestTemplate`.
In the previous examples, the aliases are `newsletter` and `mailing`. The following
example shows Feign usage with a `newsletter` alias:
In the previous examples, the aliases are `newsletter` and `mailing`.
The following example shows Feign usage with a `newsletter` alias:
[source,java,indent=0]
----
@@ -310,19 +308,18 @@ public interface NewsletterService {
==== Path
The path is represented by the `path` YAML property and is the path under which the
dependency is registered under Zookeeper. As described in the
<<spring-cloud-zookeeper-dependencies-setting-up-aliases,previous section>>, Ribbon
operates on URLs. As a result, this path is not compliant with its requirement.
The path is represented by the `path` YAML property and is the path under which the dependency is registered under Zookeeper.
As described in the
<<spring-cloud-zookeeper-dependencies-setting-up-aliases,previous section>>, Spring Cloud LoadBalancer operates on URLs.
As a result, this path is not compliant with its requirement.
That is why Spring Cloud Zookeeper maps the alias to the proper path.
==== Load Balancer Type
The load balancer type is represented by `loadBalancerType` YAML property.
If you know what kind of load-balancing strategy has to be applied when calling this
particular dependency, you can provide it in the YAML file, and it is automatically
applied. You can choose one of the following load balancing strategies:
If you know what kind of load-balancing strategy has to be applied when calling this particular dependency, you can provide it in the YAML file, and it is automatically applied.
You can choose one of the following load balancing strategies:
* STICKY: Once chosen, the instance is always called.
* RANDOM: Picks an instance randomly.
@@ -412,29 +409,15 @@ example:
[[spring-cloud-zookeeper-dependencies-configuring]]
=== Configuring Spring Cloud Zookeeper Dependencies
You can set the following properties to enable or disable parts of Zookeeper Dependencies
functionalities:
You can set the following properties to enable or disable parts of Zookeeper Dependencies functionalities:
* `spring.cloud.zookeeper.dependencies`: If you do not set this property, you cannot use
Zookeeper Dependencies.
* `spring.cloud.zookeeper.dependency.ribbon.enabled` (enabled by default): Ribbon requires
either explicit global configuration or a particular one for a dependency. By turning on
this property, runtime load balancing strategy resolution is possible, and you can use the
`loadBalancerType` section of the Zookeeper Dependencies. The configuration that needs
this property has an implementation of `LoadBalancerClient` that delegates to the
`ILoadBalancer` presented in the next bullet.
* `spring.cloud.zookeeper.dependency.ribbon.loadbalancer` (enabled by default): Thanks to
this property, the custom `ILoadBalancer` knows that the part of the URI passed to Ribbon
might actually be the alias that has to be resolved to a proper path in Zookeeper. Without
this property, you cannot register applications under nested paths.
* `spring.cloud.zookeeper.dependency.headers.enabled` (enabled by default): This property
registers a `RibbonClient` that automatically appends appropriate headers and content
types with their versions, as presented in the Dependency configuration. Without this
setting, those two parameters do not work.
* `spring.cloud.zookeeper.dependency.resttemplate.enabled` (enabled by default): When
enabled, this property modifies the request headers of a `@LoadBalanced`-annotated
`RestTemplate` such that it passes headers and content type with the version set in
dependency configuration. Without this setting, those two parameters do not work.
* `spring.cloud.zookeeper.dependencies`: If you do not set this property, you cannot use Zookeeper Dependencies.
* `spring.cloud.zookeeper.dependency.loadbalancer.enabled` (enabled by default): Turns on Zookeeper-specific custom load-balancing strategies, including `ZookeeperServiceInstanceListSupplier` and dependency-based load-balanced `RestTemplate` setup.
* `spring.cloud.zookeeper.dependency.headers.enabled` (enabled by default): This property registers a `FeignBlockingLoadBalancerClient` that automatically appends appropriate headers and content types with their versions, as presented in the Dependency configuration.
Without this setting, those two parameters do not work.
* `spring.cloud.zookeeper.dependency.resttemplate.enabled` (enabled by default): When enabled, this property modifies the request headers of a `@LoadBalanced`-annotated
`RestTemplate` such that it passes headers and content type with the version set in dependency configuration.
Without this setting, those two parameters do not work.
[[spring-cloud-zookeeper-dependency-watcher]]
== Spring Cloud Zookeeper Dependency Watcher

View File

@@ -4,7 +4,6 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>3.0.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
@@ -169,6 +168,7 @@
<spring-cloud-config.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-config.version>
<spring-cloud-netflix.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-openfeign.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-openfeign.version>
<hibernate-validator.version>6.1.1.Final</hibernate-validator.version>
</properties>
<profiles>

View File

@@ -30,6 +30,10 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>

View File

@@ -18,58 +18,62 @@ package org.springframework.cloud.zookeeper.config;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Enrique Recarte Llorens
* @author Olga Maciaszek-Sharma
*/
public class ZookeeperPropertySourceLocatorFailFastTests {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void setUp() throws Exception {
public void setUp() {
// This system property makes Curator fail faster, otherwise it takes 15 seconds
// to trigger a retry
System.setProperty("curator-default-connection-timeout", "0");
}
@After
public void tearDown() throws Exception {
public void tearDown() {
System.clearProperty("curator-default-connection-timeout");
}
@Test
public void testFailFastFalseLoadsTheApplicationContext() throws Exception {
new SpringApplicationBuilder().sources(Config.class).web(WebApplicationType.NONE)
.run("--spring.application.name=testZookeeperPropertySourceLocatorFailFast",
"--spring.cloud.zookeeper.config.connectString=localhost:2188",
"--spring.cloud.zookeeper.baseSleepTimeMs=0",
"--spring.cloud.zookeeper.maxRetries=0",
"--spring.cloud.zookeeper.maxSleepMs=0",
"--spring.cloud.zookeeper.blockUntilConnectedWait=0",
"--spring.cloud.zookeeper.config.failFast=false");
public void testFailFastFalseLoadsTheApplicationContext() {
assertThatCode(() -> {
new SpringApplicationBuilder().sources(Config.class)
.web(WebApplicationType.NONE)
.run("--spring.application.name=testZookeeperPropertySourceLocatorFailFast",
"--spring.cloud.zookeeper.config.connectString=localhost:2188",
"--spring.cloud.zookeeper.baseSleepTimeMs=0",
"--spring.cloud.zookeeper.maxRetries=0",
"--spring.cloud.zookeeper.maxSleepMs=0",
"--spring.cloud.zookeeper.blockUntilConnectedWait=0",
"--spring.cloud.zookeeper.config.failFast=false");
}).doesNotThrowAnyException();
}
@Test
public void testFailFastTrueDoesNotLoadTheApplicationContext() throws Exception {
expectedException.expect(Exception.class);
new SpringApplicationBuilder().sources(Config.class).web(WebApplicationType.NONE)
.run("--spring.application.name=testZookeeperPropertySourceLocatorFailFast",
"--spring.cloud.zookeeper.config.connectString=localhost:2188",
"--spring.cloud.zookeeper.baseSleepTimeMs=0",
"--spring.cloud.zookeeper.maxRetries=0",
"--spring.cloud.zookeeper.maxSleepMs=0",
"--spring.cloud.zookeeper.blockUntilConnectedWait=0",
"--spring.cloud.zookeeper.config.failFast=true");
public void testFailFastTrueDoesNotLoadTheApplicationContext() {
assertThatThrownBy(() -> {
new SpringApplicationBuilder().sources(Config.class)
.web(WebApplicationType.NONE)
.run("--spring.application.name=testZookeeperPropertySourceLocatorFailFast",
"--spring.cloud.zookeeper.config.connectString=localhost:2188",
"--spring.cloud.zookeeper.baseSleepTimeMs=0",
"--spring.cloud.zookeeper.maxRetries=0",
"--spring.cloud.zookeeper.maxSleepMs=0",
"--spring.cloud.zookeeper.blockUntilConnectedWait=0",
"--spring.cloud.zookeeper.config.failFast=true");
})
.isNotNull();
}
@SpringBootApplication

View File

@@ -26,6 +26,15 @@
</build>
<dependencies>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
@@ -50,6 +59,11 @@
<artifactId>spring-cloud-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>

View File

@@ -38,6 +38,11 @@
<artifactId>spring-cloud-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
@@ -73,6 +78,11 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
@@ -88,11 +98,6 @@
<artifactId>spring-cloud-config-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2015-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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Wrapper annotation to enable Spring Cloud LoadBalancer for Zookeeper.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@ConditionalOnProperty(value = "spring.cloud.zookeeper.loadbalancer.enabled", matchIfMissing = true)
public @interface ConditionalOnLoadBalancerForZookeeperEnabled {
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2015-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;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
import org.springframework.cloud.zookeeper.ConditionalOnZookeeperEnabled;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependenciesAutoConfiguration;
import org.springframework.context.annotation.Configuration;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} that sets up Spring Cloud LoadBalancer for Zookeeper.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnZookeeperEnabled
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
@ConditionalOnLoadBalancerForZookeeperEnabled
@AutoConfigureAfter({LoadBalancerAutoConfiguration.class, ZookeeperDependenciesAutoConfiguration.class})
@LoadBalancerClients(defaultConfiguration = ZookeeperLoadBalancerConfiguration.class)
public class LoadBalancerZookeeperAutoConfiguration {
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2015-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;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* Zookeeper-specific {@link ServiceInstanceListSupplier} that provides a delegate that
* filters available instances based on status retrieved from Zookeeper.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
@Configuration(proxyBeanMethods = false)
public class ZookeeperLoadBalancerConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnMissingBean
public ServiceInstanceListSupplier zookeeperDiscoveryClientServiceInstanceListSupplier(
DiscoveryClient discoveryClient, Environment env,
ApplicationContext context,
ZookeeperDependencies zookeeperDependencies) {
DiscoveryClientServiceInstanceListSupplier firstDelegate = new DiscoveryClientServiceInstanceListSupplier(
discoveryClient, env);
ZookeeperServiceInstanceListSupplier secondDelegate = new ZookeeperServiceInstanceListSupplier(firstDelegate,
zookeeperDependencies);
ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context
.getBeanProvider(LoadBalancerCacheManager.class);
if (cacheManagerProvider.getIfAvailable() != null) {
return new CachingServiceInstanceListSupplier(secondDelegate,
cacheManagerProvider.getIfAvailable());
}
return secondDelegate;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2015-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;
import java.util.ArrayList;
import java.util.List;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.zookeeper.support.StatusConstants.INSTANCE_STATUS_KEY;
import static org.springframework.cloud.zookeeper.support.StatusConstants.STATUS_UP;
/**
* A {@link ServiceInstanceListSupplier} implementation that filters available instances based on status retrieved from Zookeeper.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
public class ZookeeperServiceInstanceListSupplier implements ServiceInstanceListSupplier {
private final ServiceInstanceListSupplier delegate;
private final String serviceId;
public ZookeeperServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
ZookeeperDependencies zookeeperDependencies) {
this.delegate = delegate;
this.serviceId = getServiceIdFromDepsOrClientName(delegate
.getServiceId(), zookeeperDependencies);
}
private String getServiceIdFromDepsOrClientName(String delegateServiceId,
ZookeeperDependencies zookeeperDependencies) {
String serviceIdFromDeps = zookeeperDependencies
.getPathForAlias(delegateServiceId);
return StringUtils.hasText(serviceIdFromDeps) ? serviceIdFromDeps
: delegateServiceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
return delegate.get().map(this::filteredByZookeeperStatusUp);
}
private List<ServiceInstance> filteredByZookeeperStatusUp(List<ServiceInstance> serviceInstances) {
ArrayList<ServiceInstance> filteredInstances = new ArrayList<>();
for (ServiceInstance serviceInstance : serviceInstances) {
if (serviceInstance instanceof ZookeeperServiceInstance) {
org.apache.curator.x.discovery.ServiceInstance<ZookeeperInstance> zookeeperServiceInstance = ((ZookeeperServiceInstance) serviceInstance)
.getServiceInstance();
String instanceStatus = null;
if (zookeeperServiceInstance.getPayload() != null
&& zookeeperServiceInstance.getPayload().getMetadata() != null) {
instanceStatus = zookeeperServiceInstance.getPayload().getMetadata()
.get(INSTANCE_STATUS_KEY);
}
if (!StringUtils.hasText(instanceStatus) // backwards compatibility
|| instanceStatus.equalsIgnoreCase(STATUS_UP)) {
filteredInstances.add(serviceInstance);
}
}
}
return filteredInstances;
}
}

View File

@@ -1,111 +0,0 @@
/*
* Copyright 2015-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.dependency;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* LoadBalancer that delegates to other rules depending on the provided load balancing
* strategy in the {@link ZookeeperDependency#getLoadBalancerType()}.
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class DependenciesBasedLoadBalancer /*extends DynamicServerListLoadBalancer*/ {
private static final Log log = LogFactory.getLog(DependenciesBasedLoadBalancer.class);
//private final Map<String, IRule> ruleCache = new ConcurrentHashMap<>();
private final ZookeeperDependencies zookeeperDependencies;
public DependenciesBasedLoadBalancer(ZookeeperDependencies zookeeperDependencies/*,
ServerList<?> serverList, IClientConfig config, IPing iPing*/) {
//super(config);
this.zookeeperDependencies = zookeeperDependencies;
//setServersList(serverList.getInitialListOfServers());
//setPing(iPing);
//setServerListImpl(serverList);
}
// FIXME: 3.0.0
//@Override
//public Server chooseServer(Object key) {
// String keyAsString;
// if ("default".equals(key)) { // this is the default hint, use name instead
// keyAsString = getName();
// }
// else {
// keyAsString = (String) key;
// }
// ZookeeperDependency dependency = this.zookeeperDependencies
// .getDependencyForAlias(keyAsString);
// log.debug(String.format("Current dependencies are [%s]",
// this.zookeeperDependencies));
// if (dependency == null) {
// log.debug(String.format(
// "No dependency found for alias [%s] - will use the default rule which is [%s]",
// keyAsString, this.rule));
// return this.rule.choose(key);
// }
// cacheEntryIfMissing(keyAsString, dependency);
// log.debug(String.format(
// "Will try to retrieve dependency for key [%s]. Current cache contents [%s]",
// keyAsString, this.ruleCache));
// updateListOfServers();
// return this.ruleCache.get(keyAsString).choose(key);
//}
//
//private void cacheEntryIfMissing(String keyAsString, ZookeeperDependency dependency) {
// if (!this.ruleCache.containsKey(keyAsString)) {
// log.debug(String.format("Cache doesn't contain entry for [%s]", keyAsString));
// this.ruleCache.put(keyAsString,
// chooseRuleForLoadBalancerType(dependency.getLoadBalancerType()));
// }
//}
//
//private IRule chooseRuleForLoadBalancerType(LoadBalancerType type) {
// switch (type) {
// case ROUND_ROBIN:
// return getRoundRobinRule();
// case RANDOM:
// return getRandomRule();
// case STICKY:
// return getStickyRule();
// default:
// throw new IllegalArgumentException("Unknown load balancer type " + type);
// }
//}
//
//private RoundRobinRule getRoundRobinRule() {
// return new RoundRobinRule(this);
//}
//
//private IRule getRandomRule() {
// RandomRule randomRule = new RandomRule();
// randomRule.setLoadBalancer(this);
// return randomRule;
//}
//
//private IRule getStickyRule() {
// StickyRule stickyRule = new StickyRule(getRoundRobinRule());
// stickyRule.setLoadBalancer(this);
// return stickyRule;
//}
}

View File

@@ -16,80 +16,99 @@
package org.springframework.cloud.zookeeper.discovery.dependency;
import feign.Client;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import feign.Client;
import feign.Request;
import feign.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration;
import org.springframework.cloud.zookeeper.ConditionalOnZookeeperEnabled;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* Configuration for ensuring that headers are set for a given dependency when Feign is
* used.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDependenciesPassed
@ConditionalOnZookeeperEnabled
@ConditionalOnProperty(value = "spring.cloud.zookeeper.dependency.headers.enabled", matchIfMissing = true)
@ConditionalOnClass({ Client.class/*, LoadBalancerFeignClient.class*/ })
/*@AutoConfigureAfter({ RibbonAutoConfiguration.class,
FeignRibbonClientAutoConfiguration.class })*/
@ConditionalOnClass({Client.class, FeignBlockingLoadBalancerClient.class})
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@AutoConfigureAfter({FeignLoadBalancerAutoConfiguration.class, BlockingLoadBalancerClientAutoConfiguration.class})
public class DependencyFeignClientAutoConfiguration {
// FIXME: 3.0.0
//@Autowired(required = false)
//private LoadBalancerFeignClient ribbonClient;
//
//@Autowired
//private ZookeeperDependencies zookeeperDependencies;
//
//@Autowired
//private CachingSpringLoadBalancerFactory loadBalancerFactory;
//
//@Autowired
//private SpringClientFactory springClientFactory;
//
//@Bean
//@Primary
//Client dependencyBasedFeignClient() {
// return new LoadBalancerFeignClient(new Client.Default(null, null),
// this.loadBalancerFactory, this.springClientFactory) {
//
// @Override
// public Response execute(Request request, Request.Options options)
// throws IOException {
// URI asUri = URI.create(request.url());
// String clientName = asUri.getHost();
// ZookeeperDependency dependencyForAlias = DependencyFeignClientAutoConfiguration.this.zookeeperDependencies
// .getDependencyForAlias(clientName);
// Map<String, Collection<String>> headers = getUpdatedHeadersIfPossible(
// request, dependencyForAlias);
// if (DependencyFeignClientAutoConfiguration.this.ribbonClient != null) {
// return DependencyFeignClientAutoConfiguration.this.ribbonClient
// .execute(request(request, headers), options);
// }
// return super.execute(request(request, headers), options);
// }
//
// private Request request(Request request,
// Map<String, Collection<String>> headers) {
// return Request.create(request.method(), request.url(), headers,
// request.body(), request.charset());
// }
//
// private Map<String, Collection<String>> getUpdatedHeadersIfPossible(
// Request request, ZookeeperDependency dependencyForAlias) {
// if (dependencyForAlias != null) {
// return Collections.unmodifiableMap(new HashMap<>(
// dependencyForAlias.getUpdatedHeaders(request.headers())));
// }
// return request.headers();
// }
//
// };
//}
private final FeignBlockingLoadBalancerClient feignLoadBalancerClient;
private final ZookeeperDependencies zookeeperDependencies;
private final BlockingLoadBalancerClient loadBalancerClient;
public DependencyFeignClientAutoConfiguration(@Autowired(required = false) FeignBlockingLoadBalancerClient feignLoadBalancerClient,
ZookeeperDependencies zookeeperDependencies, BlockingLoadBalancerClient loadBalancerClient) {
this.feignLoadBalancerClient = feignLoadBalancerClient;
this.zookeeperDependencies = zookeeperDependencies;
this.loadBalancerClient = loadBalancerClient;
}
@Bean
@Primary
Client dependencyBasedFeignClient() {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null),
loadBalancerClient) {
@Override
public Response execute(Request request, Request.Options options)
throws IOException {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
ZookeeperDependency dependencyForAlias = DependencyFeignClientAutoConfiguration.this.zookeeperDependencies
.getDependencyForAlias(clientName);
Map<String, Collection<String>> headers = getUpdatedHeadersIfPossible(
request, dependencyForAlias);
if (DependencyFeignClientAutoConfiguration.this.feignLoadBalancerClient != null) {
return DependencyFeignClientAutoConfiguration.this.feignLoadBalancerClient
.execute(request(request, headers), options);
}
return super.execute(request(request, headers), options);
}
private Request request(Request request,
Map<String, Collection<String>> headers) {
return Request.create(request.httpMethod(), request.url(), headers,
request.body(), request.charset(), request.requestTemplate());
}
private Map<String, Collection<String>> getUpdatedHeadersIfPossible(
Request request, ZookeeperDependency dependencyForAlias) {
if (dependencyForAlias != null) {
return Collections.unmodifiableMap(new HashMap<>(
dependencyForAlias.getUpdatedHeaders(request.headers())));
}
return request.headers();
}
};
}
}

View File

@@ -26,9 +26,11 @@ import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.zookeeper.ConditionalOnZookeeperEnabled;
import org.springframework.cloud.zookeeper.discovery.ConditionalOnLoadBalancerForZookeeperEnabled;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
@@ -41,12 +43,15 @@ import org.springframework.web.client.RestTemplate;
* Customizes RestTemplate to support passing of params from dependency.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnZookeeperEnabled
@ConditionalOnLoadBalancerForZookeeperEnabled
@ConditionalOnDependenciesPassed
@ConditionalOnProperty(value = "spring.cloud.zookeeper.dependency.resttemplate.enabled", matchIfMissing = true)
@ConditionalOnBean(RestTemplate.class)
public class DependencyRestTemplateAutoConfiguration {
@Autowired

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2015-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.dependency;
/**
* @author Marcin Grzejszczak
* @author Spencer Gibb
* @since 1.0.0
*/
public enum LoadBalancerType {
/**
* Valid load balancer types.
*/
STICKY, RANDOM, ROUND_ROBIN
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2015-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.dependency;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Load balancing rule that returns always the same instance.
*
* Ported from {@code org.apache.curator.x.discovery.strategies.StickyStrategy}
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class StickyRule /*extends AbstractLoadBalancerRule*/ {
private static final Log log = LogFactory.getLog(StickyRule.class);
//private final IRule masterStrategy;
//private final AtomicReference<Server> ourInstance = new AtomicReference<>(null);
private final AtomicInteger instanceNumber = new AtomicInteger(-1);
// FIXME: 3.0.0
//public StickyRule(IRule masterStrategy) {
// this.masterStrategy = masterStrategy;
//}
//
//@Override
//public void initWithNiwsConfig(IClientConfig iClientConfig) {
//
//}
//
//@Override
//public Server choose(Object key) {
// final List<Server> instances = getLoadBalancer().getServerList(true);
// log.debug(String.format("Instances taken from load balancer [%s]", instances));
// Server localOurInstance = this.ourInstance.get();
// log.debug(String.format("Current saved instance [%s]", localOurInstance));
// if (!instances.contains(localOurInstance)) {
// this.ourInstance.compareAndSet(localOurInstance, null);
// }
// if (this.ourInstance.get() == null) {
// Server instance = this.masterStrategy.choose(key);
// if (this.ourInstance.compareAndSet(null, instance)) {
// this.instanceNumber.incrementAndGet();
// }
// }
// return this.ourInstance.get();
//}
/**
* Each time a new instance is picked, an internal counter is incremented. This way
* you can track when/if the instance changes. The instance can change when the
* selected instance is not in the current list of instances returned by the instance
* provider
* @return instance number
*/
public int getInstanceNumber() {
return this.instanceNumber.get();
}
}

View File

@@ -24,7 +24,6 @@ import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.zookeeper.discovery.dependency.StubsConfiguration.DependencyPath;
import org.springframework.util.StringUtils;
@@ -35,6 +34,7 @@ import static org.springframework.cloud.zookeeper.discovery.DependencyPathUtils.
* Representation of this service's dependencies in Zookeeper.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
@ConfigurationProperties("spring.cloud.zookeeper")
@@ -46,17 +46,11 @@ public class ZookeeperDependencies {
private String prefix = "";
/**
* Mapping of alias to ZookeeperDependency. From Ribbon perspective the alias is
* actually serviceID since Ribbon can't accept nested structures in serviceID.
* Mapping of alias to ZookeeperDependency. From LoadBalancer perspective the alias is
* actually serviceID since SC LoadBalancer can't accept nested structures in serviceID.
*/
private Map<String, ZookeeperDependency> dependencies = new LinkedHashMap<>();
/**
* Default health endpoint that will be checked to verify that a dependency is alive.
*/
@Value("${spring.cloud.zookeeper.dependency.ribbon.loadbalancer.defaulthealthendpoint:/health}")
private String defaultHealthEndpoint;
@PostConstruct
public void init() {
if (StringUtils.hasText(this.prefix)) {
@@ -153,10 +147,6 @@ public class ZookeeperDependencies {
return this.dependencies;
}
public String getDefaultHealthEndpoint() {
return this.defaultHealthEndpoint;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@@ -165,17 +155,11 @@ public class ZookeeperDependencies {
this.dependencies = dependencies;
}
public void setDefaultHealthEndpoint(String defaultHealthEndpoint) {
this.defaultHealthEndpoint = defaultHealthEndpoint;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("ZookeeperDependencies{");
sb.append("prefix='").append(this.prefix).append('\'');
sb.append(", dependencies=").append(this.dependencies);
sb.append(", defaultHealthEndpoint='").append(this.defaultHealthEndpoint)
.append('\'');
sb.append('}');
return sb.toString();
}

View File

@@ -28,12 +28,12 @@ import org.springframework.context.annotation.Configuration;
* Provides AutoConfiguration for Zookeeper dependency set up in properties.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnZookeeperEnabled
@ConditionalOnDependenciesPassed
@AutoConfigureAfter(ZookeeperAutoConfiguration.class)
public class ZookeeperDependenciesAutoConfiguration {

View File

@@ -29,6 +29,7 @@ import static java.util.Collections.singletonList;
*
* @author Marcin Grzejszczak
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
public class ZookeeperDependency {
@@ -43,11 +44,6 @@ public class ZookeeperDependency {
*/
private String path;
/**
* Type of load balancer that should be used for this particular dependency.
*/
private LoadBalancerType loadBalancerType = LoadBalancerType.ROUND_ROBIN;
/**
* Content type template with {@code $version} placeholder which will be filled by the
* {@link ZookeeperDependency#version} variable.
@@ -89,11 +85,9 @@ public class ZookeeperDependency {
public ZookeeperDependency() {
}
public ZookeeperDependency(String path, LoadBalancerType loadBalancerType,
String contentTypeTemplate, String version,
public ZookeeperDependency(String path, String contentTypeTemplate, String version,
Map<String, Collection<String>> headers, boolean required, String stubs) {
this.path = path;
this.loadBalancerType = loadBalancerType;
this.contentTypeTemplate = contentTypeTemplate;
this.version = version;
this.headers = headers;
@@ -182,10 +176,6 @@ public class ZookeeperDependency {
return this.path;
}
public LoadBalancerType getLoadBalancerType() {
return this.loadBalancerType;
}
public String getContentTypeTemplate() {
return this.contentTypeTemplate;
}
@@ -214,10 +204,6 @@ public class ZookeeperDependency {
this.path = path;
}
public void setLoadBalancerType(LoadBalancerType loadBalancerType) {
this.loadBalancerType = loadBalancerType;
}
public void setContentTypeTemplate(String contentTypeTemplate) {
this.contentTypeTemplate = contentTypeTemplate;
}
@@ -246,7 +232,6 @@ public class ZookeeperDependency {
public String toString() {
final StringBuffer sb = new StringBuffer("ZookeeperDependency{");
sb.append("path='").append(this.path).append('\'');
sb.append(", loadBalancerType=").append(this.loadBalancerType);
sb.append(", contentTypeTemplate='").append(this.contentTypeTemplate)
.append('\'');
sb.append(", version='").append(this.version).append('\'');

View File

@@ -1,6 +1,7 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryAutoConfiguration,\
org.springframework.cloud.zookeeper.discovery.dependency.DependencyFeignClientAutoConfiguration,\
org.springframework.cloud.zookeeper.discovery.dependency.DependencyRestTemplateAutoConfiguration,\
org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependenciesAutoConfiguration,\
org.springframework.cloud.zookeeper.discovery.watcher.DependencyWatcherAutoConfiguration,\
@@ -8,8 +9,8 @@ org.springframework.cloud.zookeeper.serviceregistry.ZookeeperAutoServiceRegistra
org.springframework.cloud.zookeeper.serviceregistry.ZookeeperServiceRegistryAutoConfiguration,\
org.springframework.cloud.zookeeper.support.CuratorServiceDiscoveryAutoConfiguration,\
org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClientConfiguration, \
org.springframework.cloud.zookeeper.discovery.LoadBalancerZookeeperAutoConfiguration, \
org.springframework.cloud.zookeeper.discovery.reactive.ZookeeperReactiveDiscoveryClientConfiguration
# org.springframework.cloud.zookeeper.discovery.dependency.DependencyFeignClientAutoConfiguration,\
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\

View File

@@ -32,17 +32,15 @@ import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryCl
import org.springframework.cloud.zookeeper.discovery.test.CommonTestConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZookeeperDiscoveryAutoRegistrationFalseTests.Config.class, properties = {
@@ -85,15 +83,4 @@ public class ZookeeperDiscoveryAutoRegistrationFalseTests {
}
@Controller
@Profile("ribbon")
class PingController {
@RequestMapping("/ping")
String ping() {
return "pong";
}
}
}

View File

@@ -110,7 +110,7 @@ public class ZookeeperDiscoveryHealthIndicatorWithNestedStructureTests {
}
@Bean
TestLoadBalancedClient testRibbonClient(@LoadBalanced RestTemplate restTemplate,
TestLoadBalancedClient testLoadBalancedClient(@LoadBalanced RestTemplate restTemplate,
@Value("${spring.application.name}") String springAppName) {
return new TestLoadBalancedClient(restTemplate, springAppName);
}

View File

@@ -45,7 +45,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
"feign.hystrix.enabled=false",
"spring.cloud.zookeeper.discovery.uriSpec={scheme}://{address}:{port}/contextPath",
"spring.cloud.zookeeper.discovery.instance-ssl-port=8443" }, webEnvironment = RANDOM_PORT)
@ActiveProfiles("ribbon")
@ActiveProfiles("loadbalancer")
@DirtiesContext
public class ZookeeperDiscoverySecurePortTests {
@@ -73,7 +73,7 @@ public class ZookeeperDiscoverySecurePortTests {
@Configuration
@EnableAutoConfiguration
@Import(CommonTestConfig.class)
@Profile("ribbon")
@Profile("loadbalancer")
static class Config {
}

View File

@@ -59,10 +59,9 @@ import static org.springframework.cloud.zookeeper.discovery.test.TestLoadBalance
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZookeeperDiscoveryTests.Config.class, properties = {
"feign.hystrix.enabled=false",
"spring.cloud.zookeeper.discovery.uri-spec={scheme}://{address}:{port}/contextPath",
"management.endpoints.web.exposure.include=*" }, webEnvironment = RANDOM_PORT)
@ActiveProfiles("ribbon")
@ActiveProfiles("loadbalancer")
@DirtiesContext
public class ZookeeperDiscoveryTests {
@@ -85,7 +84,7 @@ public class ZookeeperDiscoveryTests {
Registration registration;
@Test
public void should_find_the_app_by_its_name_via_Ribbon() {
public void should_find_the_app_by_its_name_via_LoadBalancer() {
// expect:
then(registeredServiceStatusViaServiceName()).isEqualTo("UP");
}
@@ -98,12 +97,12 @@ public class ZookeeperDiscoveryTests {
ServiceInstance instance = instances.get(0);
// expect:
then(registeredServiceStatus(instance)).isEqualTo("UP");
then(instance.getInstanceId()).isEqualTo("ribbon-instance-id-123");
then(instance.getInstanceId()).isEqualTo("loadbalancer-instance-id-123");
then(instance.getMetadata().get("testMetadataKey"))
.isEqualTo("testMetadataValue");
then(instance).isInstanceOf(ZookeeperServiceInstance.class);
ZookeeperServiceInstance zkInstance = (ZookeeperServiceInstance) instance;
then(zkInstance.getServiceInstance().getId()).isEqualTo("ribbon-instance-id-123");
then(zkInstance.getServiceInstance().getId()).isEqualTo("loadbalancer-instance-id-123");
}
@Test
@@ -148,7 +147,7 @@ public class ZookeeperDiscoveryTests {
.isEqualTo(this.registration.getHost());
}
@FeignClient("ribbonApp")
@FeignClient("loadBalancerApp")
public interface IdUsingFeignClient {
@RequestMapping(method = RequestMethod.GET, value = "/hi")
@@ -160,12 +159,12 @@ public class ZookeeperDiscoveryTests {
@EnableAutoConfiguration
@Import(CommonTestConfig.class)
@EnableFeignClients(clients = { IdUsingFeignClient.class })
@Profile("ribbon")
@Profile("loadbalancer")
@RestController
static class Config {
@Bean
TestLoadBalancedClient testRibbonClient(@LoadBalanced RestTemplate restTemplate,
TestLoadBalancedClient testLoadBalancedClient(@LoadBalanced RestTemplate restTemplate,
@Value("${spring.application.name}") String springAppName) {
return new TestLoadBalancedClient(restTemplate, springAppName);
}
@@ -178,7 +177,7 @@ public class ZookeeperDiscoveryTests {
}
@Controller
@Profile("ribbon")
@Profile("loadbalancer")
class PingController {
@RequestMapping("/ping")

View File

@@ -64,8 +64,7 @@ public class ZookeeprDiscoveryNonWebAppTests {
}
@Test
public void should_work_when_using_web_client_without_the_web_environment()
throws Exception {
public void should_work_when_using_web_client_without_the_web_environment() {
SpringApplication producerApp = new SpringApplicationBuilder(HelloProducer.class)
.web(WebApplicationType.SERVLET).build();
SpringApplication clientApplication = new SpringApplicationBuilder(

View File

@@ -45,7 +45,7 @@ import static org.assertj.core.api.BDDAssertions.then;
public class DependencyConfig {
@Bean
TestLoadBalancedClient testRibbonClient(@LoadBalanced RestTemplate restTemplate) {
TestLoadBalancedClient testLoadBalancedClient(@LoadBalanced RestTemplate restTemplate) {
return new TestLoadBalancedClient(restTemplate);
}

View File

@@ -1,135 +0,0 @@
/*
* Copyright 2015-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.dependency;
import java.net.URI;
import java.util.List;
import java.util.concurrent.Callable;
import com.jayway.awaitility.Awaitility;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.test.TestingServer;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.zookeeper.ZookeeperProperties;
import org.springframework.cloud.zookeeper.discovery.TestServiceRegistrar;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.SocketUtils;
import org.springframework.web.client.RestTemplate;
/**
* @author Marcin Grzejszczak
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StickyRuleTests.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("loadbalancerclient")
public class StickyRuleTests {
@Autowired
LoadBalancerClient loadBalancerClient;
@Autowired
DiscoveryClient discoveryClient;
@Test
@Ignore // FIXME: 3.0.0
public void should_use_sticky_load_balancing_strategy_taken_from_Zookeeper_dependencies() {
// given:
System.setProperty(
"spring.cloud.zookeeper.dependency.ribbon.loadbalancer.checkping",
"false");
// expect:
thereAreTwoRegisteredServices();
URI uri = getUriForAlias();
Awaitility.await().until(uriMatchesTwice(uri));
}
private Callable<Boolean> uriMatchesTwice(final URI uri) {
return new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return uriMatches() && uriMatches();
}
private boolean uriMatches() {
return uri != null && uri.equals(getUriForAlias());
}
};
}
private boolean thereAreTwoRegisteredServices() {
List<ServiceInstance> instances = this.discoveryClient.getInstances("someAlias");
return instances != null && instances.size() == 2;
}
private URI getUriForAlias() {
ServiceInstance alias = this.loadBalancerClient.choose("someAlias");
return alias != null ? alias.getUri() : null;
}
@Configuration
@EnableAutoConfiguration
@Profile("loadbalancerclient")
static class Config {
@Bean
@LoadBalanced
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean(destroyMethod = "close")
TestingServer testingServer() throws Exception {
return new TestingServer(SocketUtils.findAvailableTcpPort());
}
@Bean
ZookeeperProperties zookeeperProperties() throws Exception {
ZookeeperProperties zookeeperProperties = new ZookeeperProperties();
zookeeperProperties
.setConnectString("localhost:" + testingServer().getPort());
return zookeeperProperties;
}
@Bean(initMethod = "start", destroyMethod = "stop")
TestServiceRegistrar serviceOne(CuratorFramework curatorFramework) {
return new TestServiceRegistrar(SocketUtils.findAvailableTcpPort(),
curatorFramework);
}
@Bean(initMethod = "start", destroyMethod = "stop")
TestServiceRegistrar serviceTwo(CuratorFramework curatorFramework) {
return new TestServiceRegistrar(SocketUtils.findAvailableTcpPort(),
curatorFramework);
}
}
}

View File

@@ -32,14 +32,14 @@ import static org.assertj.core.api.BDDAssertions.then;
public class ZookeeperDependenciesTests {
private static final ZookeeperDependency EXPECTED_DEPENDENCY = new ZookeeperDependency(
"path", LoadBalancerType.RANDOM, "contentTypeTemplate", "version",
"path", "contentTypeTemplate", "version",
defaultHeader(), false, "");
private static final Map<String, ZookeeperDependency> DEPENDENCIES = defaultDependencies();
private static Map<String, Collection<String>> defaultHeader() {
return Collections.singletonMap("header",
(Collection<String>) Collections.singletonList("value"));
Collections.singletonList("value"));
}
private static Map<String, ZookeeperDependency> defaultDependencies() {

View File

@@ -17,7 +17,6 @@
package org.springframework.cloud.zookeeper.discovery.dependency;
import java.util.List;
import java.util.concurrent.Callable;
import org.junit.Ignore;
import org.junit.Test;
@@ -41,11 +40,12 @@ import static org.springframework.cloud.zookeeper.discovery.test.TestLoadBalance
/**
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZookeeperDiscoveryWithDependenciesIntegrationTests.Config.class, properties = {
"feign.hystrix.enabled=false", "debug=true",
"management.endpoints.web.exposure.include=*" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
"debug=true",
"management.endpoints.web.exposure.include=*"}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("dependencies")
public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
@@ -69,12 +69,7 @@ public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
// given:
final DiscoveryClient discoveryClient = this.discoveryClient;
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return !discoveryClient.getInstances("nameWithoutAlias").isEmpty();
}
});
await().until(() -> !discoveryClient.getInstances("nameWithoutAlias").isEmpty());
}
@Test
@@ -94,12 +89,9 @@ public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
// given:
final IdUsingFeignClient idUsingFeignClient = this.idUsingFeignClient;
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
then(idUsingFeignClient.getBeans()).isNotEmpty();
return true;
}
await().until(() -> {
then(idUsingFeignClient.getBeans()).isNotEmpty();
return true;
});
}
@@ -107,12 +99,7 @@ public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
@Test
public void should_find_a_collaborator_via_load_balanced_rest_template_by_using_its_alias_from_dependencies() {
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return callingServiceAtBeansEndpointIsNotEmpty();
}
});
await().until(() -> callingServiceAtBeansEndpointIsNotEmpty());
}
@Ignore // FIXME 2.0.0
@@ -121,24 +108,18 @@ public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
// given:
final AliasUsingFeignClient aliasUsingFeignClient = this.aliasUsingFeignClient;
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
then(aliasUsingFeignClient.getBeans()).isNotEmpty();
return true;
}
await().until(() -> {
then(aliasUsingFeignClient.getBeans()).isNotEmpty();
return true;
});
}
@Test
public void should_have_headers_from_dependencies_attached_to_the_request_via_load_balanced_rest_template() {
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
callingServiceToCheckIfHeadersArePassed();
return true;
}
await().until(() -> {
callingServiceToCheckIfHeadersArePassed();
return true;
});
}
@@ -148,12 +129,9 @@ public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
// given:
final AliasUsingFeignClient aliasUsingFeignClient = this.aliasUsingFeignClient;
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
aliasUsingFeignClient.checkHeaders();
return true;
}
await().until(() -> {
aliasUsingFeignClient.checkHeaders();
return true;
});
}
@@ -164,12 +142,7 @@ public class ZookeeperDiscoveryWithDependenciesIntegrationTests {
List<ServiceInstance> instances = discoveryClient.getInstances("someAlias");
final ServiceInstance instance = instances.get(0);
// expect:
await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return callingServiceViaUrlOnBeansEndpointIsNotEmpty(instance);
}
});
await().until(() -> callingServiceViaUrlOnBeansEndpointIsNotEmpty(instance));
}
@Test

View File

@@ -50,7 +50,7 @@ public class ZookeeperDiscoveryWithDyingDependenciesTests {
// Issue: #45
@Test
public void should_refresh_a_dependency_in_Ribbon_when_the_dependency_has_deregistered_and_registered_in_Zookeeper()
public void should_refresh_a_dependency_in_LoadBalancer_when_the_dependency_has_deregistered_and_registered_in_Zookeeper()
throws Exception {
ConfigurableApplicationContext serverContext = null;
ConfigurableApplicationContext clientContext = null;

View File

@@ -43,8 +43,7 @@ public class CommonTestConfig {
}
@Bean
ZookeeperProperties zookeeperProperties(TestingServer testingServer)
throws Exception {
ZookeeperProperties zookeeperProperties(TestingServer testingServer) {
ZookeeperProperties zookeeperProperties = new ZookeeperProperties();
zookeeperProperties.setConnectString("localhost:" + testingServer.getPort());
return zookeeperProperties;

View File

@@ -1,13 +1,13 @@
spring:
application:
name: ribbonApp
name: loadBalancerApp
cloud:
zookeeper:
discovery:
metadata:
testMetadataKey: testMetadataValue
uriSpec: "{scheme}://{address}:{port}/contextPath"
instance-id: ribbon-instance-id-123
instance-id: loadbalancer-instance-id-123
management:
security:
enabled: false

View File

@@ -1,4 +1,4 @@
testInstance.ribbon.ServerListRefreshInterval: 100
testInstance.loadbalancer.ServerListRefreshInterval: 100
logging:
level:
org.apache.zookeeper.ClientCnxn: ERROR