Merge branch 'master' into 2.0.x
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
releaser:
|
||||
maven:
|
||||
buildCommand: ./scripts/build.sh
|
||||
buildCommand: ./scripts/build.sh {{systemProps}}
|
||||
|
||||
@@ -669,8 +669,9 @@ in order for the Hystrix Dashboard to make a successful connection to the stream
|
||||
|
||||
Looking at an individual instances Hystrix data is not very useful in terms of the overall health of the system. https://github.com/Netflix/Turbine[Turbine] is an application that aggregates all of the relevant `/hystrix.stream` endpoints into a combined `/turbine.stream` for use in the Hystrix Dashboard. Individual instances are located via Eureka. Running Turbine is as simple as annotating your main class with the `@EnableTurbine` annotation (e.g. using spring-cloud-starter-netflix-turbine to set up the classpath). All of the documented configuration properties from https://github.com/Netflix/Turbine/wiki/Configuration-(1.x)[the Turbine 1 wiki] apply. The only difference is that the `turbine.instanceUrlSuffix` does not need the port prepended as this is handled automatically unless `turbine.instanceInsertPort=false`.
|
||||
|
||||
NOTE: By default, Turbine looks for the `/hystrix.stream` endpoint on a registered instance by looking up its `homePageUrl` entry in Eureka, then appending `/hystrix.stream` to it. This means that if `spring-boot-actuator` is running on its own port (which is the default), the call to `/hystrix.stream` will fail.
|
||||
To make turbine find the Hystrix stream at the correct port, you need to add `management.port` to the instances' metadata:
|
||||
NOTE: By default, Turbine looks for the `/hystrix.stream` endpoint on a registered instance by looking up its `hostName` and `port` entries in Eureka, then appending `/hystrix.stream` to it.
|
||||
If the instance's metadata contains `management.port`, it will be used instead of the `port` value for the `/hystrix.stream` endpoint.
|
||||
By default, metadata entry `management.port` is equal to the `management.port` configuration property, it can be overridden though with following configuration:
|
||||
----
|
||||
eureka:
|
||||
instance:
|
||||
@@ -955,7 +956,33 @@ zuul:
|
||||
threadPoolKeyPrefix: zuulgw
|
||||
----
|
||||
|
||||
[[how-to-provdie-a-key-to-ribbon]]
|
||||
=== How to Provide a Key to Ribbon's `IRule`
|
||||
|
||||
If you need to provide your own `IRule` implementation to handle a special routing requirement like a canary test,
|
||||
you probably want to pass some information to the `choose` method of `IRule`.
|
||||
|
||||
.com.netflix.loadbalancer.IRule.java
|
||||
----
|
||||
public interface IRule{
|
||||
public Server choose(Object key);
|
||||
:
|
||||
----
|
||||
|
||||
You can provide some information that will be used to choose a target server by your `IRule` implementation like
|
||||
the following:
|
||||
|
||||
----
|
||||
RequestContext.getCurrentContext()
|
||||
.set(FilterConstants.LOAD_BALANCER_KEY, "canary-test");
|
||||
----
|
||||
|
||||
If you put any object into the `RequestContext` with a key `FilterConstants.LOAD_BALANCER_KEY`, it will
|
||||
be passed to the `choose` method of `IRule` implementation. Above code must be executed before `RibbonRoutingFilter`
|
||||
is executed and Zuul's pre filter is the best place to do that. You can easily access HTTP headers and query parameters
|
||||
via `RequestContext` in pre filter, so it can be used to determine `LOAD_BALANCER_KEY` that will be passed to Ribbon.
|
||||
If you don't put any value with `LOAD_BALANCER_KEY` in `RequestContext`, null will be passed as a parameter of `choose`
|
||||
method.
|
||||
|
||||
[[spring-cloud-feign]]
|
||||
== Declarative REST Client: Feign
|
||||
@@ -1247,7 +1274,7 @@ protected interface HystrixClient {
|
||||
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
|
||||
@Override
|
||||
public HystrixClient create(Throwable cause) {
|
||||
return new HystrixClientWithFallBackFactory() {
|
||||
return new HystrixClient() {
|
||||
@Override
|
||||
public Hello iFailSometimes() {
|
||||
return new Hello("fallback; reason was: " + cause.getMessage());
|
||||
@@ -2086,6 +2113,17 @@ class MyFallbackProvider implements FallbackProvider {
|
||||
}
|
||||
----
|
||||
|
||||
=== Zuul Timeouts
|
||||
|
||||
If you want to configure the socket timeouts and read timeouts for requests proxied through
|
||||
Zuul there are two options based on your configuration.
|
||||
|
||||
If Zuul is using service discovery than you need to configure these timeouts via Ribbon properties,
|
||||
`ribbon.ReadTimeout` and `ribbon.SocketTimeout`.
|
||||
|
||||
If you have configured Zuul routes by specifying URLs than you will need to use
|
||||
`zuul.host.connect-timeout-millis` and `zuul.host.socket-timeout-millis`.
|
||||
|
||||
[[zuul-redirect-location-rewrite]]
|
||||
=== Rewriting `Location` header
|
||||
|
||||
@@ -2694,6 +2732,27 @@ https://github.com/spring-projects/spring-retry[Spring Retry] on your applicatio
|
||||
When Spring Retry is present load balanced `RestTemplates`, Feign, and Zuul will automatically
|
||||
retry any failed requests (assuming you configuration allows it to).
|
||||
|
||||
==== BackOff Policies
|
||||
By default no backoff policy is used when retrying requests. If you would like to configure
|
||||
a backoff policy you will need to create a bean of type `LoadBalancedBackOffPolicyFactory`
|
||||
which will be used to create a `BackOffPolicy` for a given service.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
@Bean
|
||||
LoadBalancedBackOffPolicyFactory backOffPolciyFactory() {
|
||||
return new LoadBalancedBackOffPolicyFactory() {
|
||||
@Override
|
||||
public BackOffPolicy createBackOffPolicy(String service) {
|
||||
return new ExponentialBackOffPolicy();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
==== Configuration
|
||||
|
||||
Anytime Ribbon is used with Spring Retry you can control the retry functionality by configuring
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
(cd spring-cloud-netflix-hystrix-contract && ../mvnw clean install)
|
||||
./mvnw clean install
|
||||
(cd spring-cloud-netflix-hystrix-contract && ../mvnw clean install -B -Pdocs ${@})
|
||||
./mvnw clean install -B -Pdocs ${@}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
@@ -32,11 +33,13 @@ import com.netflix.loadbalancer.ILoadBalancer;
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class CachingSpringLoadBalancerFactory {
|
||||
|
||||
private final SpringClientFactory factory;
|
||||
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
|
||||
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
|
||||
private boolean enableRetry = false;
|
||||
|
||||
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
|
||||
@@ -44,19 +47,35 @@ public class CachingSpringLoadBalancerFactory {
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(factory);
|
||||
this.loadBalancedBackOffPolicyFactory = null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.0x
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, boolean enableRetry) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.enableRetry = enableRetry;
|
||||
this.loadBalancedBackOffPolicyFactory = null;
|
||||
}
|
||||
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
this.enableRetry = true;
|
||||
}
|
||||
|
||||
public FeignLoadBalancer create(String clientName) {
|
||||
@@ -67,7 +86,7 @@ public class CachingSpringLoadBalancerFactory {
|
||||
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
|
||||
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
|
||||
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
|
||||
loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
|
||||
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
|
||||
this.cache.put(clientName, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
|
||||
@@ -65,8 +66,9 @@ public class FeignRibbonClientAutoConfiguration {
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
|
||||
SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory retryPolicyFactory) {
|
||||
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, true);
|
||||
LoadBalancedRetryPolicyFactory retryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -23,6 +23,7 @@ import feign.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
@@ -32,6 +33,8 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import com.netflix.client.DefaultLoadBalancerRetryHandler;
|
||||
@@ -48,12 +51,26 @@ import com.netflix.loadbalancer.Server;
|
||||
public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser {
|
||||
|
||||
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
|
||||
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
|
||||
this.loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
}
|
||||
|
||||
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ?
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,6 +89,8 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
|
||||
}
|
||||
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
|
||||
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
|
||||
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
|
||||
|
||||
@@ -35,6 +35,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
@@ -102,6 +104,13 @@ public class RibbonAutoConfiguration {
|
||||
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancedBackOffPolicyFactory loadBalancedBackoffPolicyFactory() {
|
||||
return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public PropertiesFactory propertiesFactory() {
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
@@ -151,9 +152,11 @@ public class HttpClientRibbonConfiguration {
|
||||
public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient(
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
ILoadBalancer loadBalancer, RetryHandler retryHandler,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient) {
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(
|
||||
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory);
|
||||
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory,
|
||||
loadBalancedBackOffPolicyFactory);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
@@ -35,6 +36,8 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -51,7 +54,11 @@ import com.netflix.loadbalancer.Server;
|
||||
public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient
|
||||
implements ServiceInstanceChooser {
|
||||
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
|
||||
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableRibbonLoadBalancingHttpClient(IClientConfig config,
|
||||
ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
@@ -59,6 +66,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
@@ -66,6 +75,15 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request,
|
||||
final IClientConfig configOverride) throws Exception {
|
||||
@@ -137,6 +155,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable
|
||||
? new NeverRetryPolicy()
|
||||
: new RetryPolicy(request, retryPolicy, this, this.getClientName()));
|
||||
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
return retryTemplate.execute(callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
@@ -115,9 +116,10 @@ public class OkHttpRibbonConfiguration {
|
||||
ILoadBalancer loadBalancer,
|
||||
RetryHandler retryHandler,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
OkHttpClient delegate) {
|
||||
OkHttpClient delegate,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(delegate, config,
|
||||
serverIntrospector, loadBalancedRetryPolicyFactory);
|
||||
serverIntrospector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
|
||||
@@ -22,6 +22,7 @@ import okhttp3.Response;
|
||||
import java.net.URI;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
@@ -33,6 +34,8 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -48,17 +51,31 @@ import com.netflix.loadbalancer.Server;
|
||||
public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClient implements ServiceInstanceChooser {
|
||||
|
||||
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
|
||||
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
}
|
||||
|
||||
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy,
|
||||
RetryCallback<OkHttpRibbonResponse, Exception> callback)
|
||||
throws Exception {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
boolean retryable = request.getContext() == null ? true :
|
||||
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.support;
|
||||
|
||||
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
|
||||
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
@@ -140,4 +141,11 @@ public abstract class AbstractLoadBalancingClient<S extends ContextAwareRequest,
|
||||
}
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeLoadBalancerCommandBuilder(S request, IClientConfig config, LoadBalancerCommand.Builder<T> builder) {
|
||||
if (request.getLoadBalancerKey() != null) {
|
||||
builder.withServerLocator(request.getLoadBalancerKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public abstract class ContextAwareRequest extends ClientRequest implements HttpR
|
||||
}
|
||||
this.uri = context.uri();
|
||||
this.isRetriable = context.getRetryable();
|
||||
this.loadBalancerKey = context.getLoadBalancerKey();
|
||||
}
|
||||
|
||||
public RibbonCommandContext getContext() {
|
||||
@@ -73,7 +74,8 @@ public abstract class ContextAwareRequest extends ClientRequest implements HttpR
|
||||
RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(),
|
||||
this.context.getMethod(), uri.toString(), this.context.getRetryable(),
|
||||
this.context.getHeaders(), this.context.getParams(), this.context.getRequestEntity(),
|
||||
this.context.getRequestCustomizers(), this.context.getContentLength());
|
||||
this.context.getRequestCustomizers(), this.context.getContentLength(),
|
||||
this.context.getLoadBalancerKey());
|
||||
return commandContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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
|
||||
* *
|
||||
* * http://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.netflix.ribbon.support;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when the status code is deemed to be retryable.
|
||||
* @author Ryan Baxter
|
||||
* @deprecated Use {@link org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException} instead
|
||||
*/
|
||||
//TODO Remove in Edgeware
|
||||
@Deprecated
|
||||
public class RetryableStatusCodeException extends IOException {
|
||||
|
||||
private static final String MESSAGE = "Service %s returned a status code of %d";
|
||||
|
||||
public RetryableStatusCodeException(String serviceId, int statusCode) {
|
||||
super(String.format(MESSAGE, serviceId, statusCode));
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Yongsung Yoon
|
||||
*/
|
||||
public class RibbonCommandContext {
|
||||
private final String serviceId;
|
||||
@@ -41,6 +42,7 @@ public class RibbonCommandContext {
|
||||
private final List<RibbonRequestCustomizer> requestCustomizers;
|
||||
private InputStream requestEntity;
|
||||
private Long contentLength;
|
||||
private Object loadBalancerKey;
|
||||
|
||||
/**
|
||||
* Kept for backwards compatibility with Spring Cloud Sleuth 1.x versions
|
||||
@@ -50,7 +52,7 @@ public class RibbonCommandContext {
|
||||
String uri, Boolean retryable, MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity) {
|
||||
this(serviceId, method, uri, retryable, headers, params, requestEntity,
|
||||
new ArrayList<RibbonRequestCustomizer>(), null);
|
||||
new ArrayList<RibbonRequestCustomizer>(), null, null);
|
||||
}
|
||||
|
||||
public RibbonCommandContext(String serviceId, String method, String uri,
|
||||
@@ -58,13 +60,22 @@ public class RibbonCommandContext {
|
||||
MultiValueMap<String, String> params, InputStream requestEntity,
|
||||
List<RibbonRequestCustomizer> requestCustomizers) {
|
||||
this(serviceId, method, uri, retryable, headers, params, requestEntity,
|
||||
requestCustomizers, null);
|
||||
requestCustomizers, null, null);
|
||||
}
|
||||
|
||||
public RibbonCommandContext(String serviceId, String method, String uri,
|
||||
Boolean retryable, MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity,
|
||||
List<RibbonRequestCustomizer> requestCustomizers, Long contentLength) {
|
||||
this(serviceId, method, uri, retryable, headers, params, requestEntity,
|
||||
requestCustomizers, contentLength, null);
|
||||
}
|
||||
|
||||
public RibbonCommandContext(String serviceId, String method, String uri,
|
||||
Boolean retryable, MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity,
|
||||
List<RibbonRequestCustomizer> requestCustomizers, Long contentLength,
|
||||
Object loadBalancerKey) {
|
||||
Assert.notNull(serviceId, "serviceId may not be null");
|
||||
Assert.notNull(method, "method may not be null");
|
||||
Assert.notNull(uri, "uri may not be null");
|
||||
@@ -80,6 +91,7 @@ public class RibbonCommandContext {
|
||||
this.requestEntity = requestEntity;
|
||||
this.requestCustomizers = requestCustomizers;
|
||||
this.contentLength = contentLength;
|
||||
this.loadBalancerKey = loadBalancerKey;
|
||||
}
|
||||
|
||||
public URI uri() {
|
||||
@@ -153,6 +165,14 @@ public class RibbonCommandContext {
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
|
||||
public Object getLoadBalancerKey() {
|
||||
return loadBalancerKey;
|
||||
}
|
||||
|
||||
public void setLoadBalancerKey(Object loadBalancerKey) {
|
||||
this.loadBalancerKey = loadBalancerKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
@@ -167,13 +187,14 @@ public class RibbonCommandContext {
|
||||
.equals(params, that.params) && Objects
|
||||
.equals(requestEntity, that.requestEntity) && Objects
|
||||
.equals(requestCustomizers, that.requestCustomizers) && Objects
|
||||
.equals(contentLength, that.contentLength);
|
||||
.equals(contentLength, that.contentLength) && Objects
|
||||
.equals(loadBalancerKey, that.loadBalancerKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(serviceId, method, uri, retryable, headers, params,
|
||||
requestEntity, requestCustomizers, contentLength);
|
||||
requestEntity, requestCustomizers, contentLength, loadBalancerKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,6 +209,7 @@ public class RibbonCommandContext {
|
||||
sb.append(", requestEntity=").append(requestEntity);
|
||||
sb.append(", requestCustomizers=").append(requestCustomizers);
|
||||
sb.append(", contentLength=").append(contentLength);
|
||||
sb.append(", loadBalancerKey=").append(loadBalancerKey);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
@@ -42,6 +43,10 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.backoff.BackOffContext;
|
||||
import org.springframework.retry.backoff.BackOffInterruptedException;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
@@ -79,6 +84,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
@Mock
|
||||
private IClientConfig config;
|
||||
private ServerIntrospector inspector = new DefaultServerIntrospector();
|
||||
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
|
||||
private Integer defaultConnectTimeout = 10000;
|
||||
private Integer defaultReadTimeout = 10000;
|
||||
@@ -117,7 +124,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
|
||||
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
|
||||
doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
|
||||
loadBalancedBackOffPolicyFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertEquals(200, ribbonResponse.toResponse().status());
|
||||
verify(client, times(1)).execute(any(Request.class), any(Request.Options.class));
|
||||
@@ -136,7 +144,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
public LoadBalancedRetryPolicy create(String s, ServiceInstanceChooser serviceInstanceChooser) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, loadBalancedBackOffPolicyFactory);
|
||||
try {
|
||||
feignLb.execute(request, null);
|
||||
} catch(Exception e) {
|
||||
@@ -167,10 +175,13 @@ public class RetryableFeignLoadBalancerTests {
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
|
||||
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
|
||||
doThrow(new IOException("boom")).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
|
||||
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
|
||||
backOffPolicyFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertEquals(200, ribbonResponse.toResponse().status());
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
assertEquals(1, backOffPolicyFactory.getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -195,10 +206,13 @@ public class RetryableFeignLoadBalancerTests {
|
||||
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
|
||||
Response fourOFourResponse = Response.builder().status(404).headers(new HashMap<String, Collection<String>>()).build();
|
||||
doReturn(fourOFourResponse).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
|
||||
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
|
||||
backOffPolicyFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertEquals(200, ribbonResponse.toResponse().status());
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
assertEquals(1, backOffPolicyFactory.getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -214,7 +228,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
|
||||
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
|
||||
doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
|
||||
loadBalancedBackOffPolicyFactory);
|
||||
RequestSpecificRetryHandler retryHandler = feignLb.getRequestSpecificRetryHandler(request, config);
|
||||
assertEquals(1, retryHandler.getMaxRetriesOnNextServer());
|
||||
assertEquals(1, retryHandler.getMaxRetriesOnSameServer());
|
||||
@@ -265,11 +280,35 @@ public class RetryableFeignLoadBalancerTests {
|
||||
public List<Server> getAllServers() {
|
||||
return null;
|
||||
}
|
||||
}, config, inspector, loadBalancedRetryPolicyFactory);
|
||||
}, config, inspector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory);
|
||||
ServiceInstance serviceInstance = feignLb.choose("foo");
|
||||
assertEquals("foo", serviceInstance.getHost());
|
||||
assertEquals(80, serviceInstance.getPort());
|
||||
|
||||
}
|
||||
|
||||
class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public BackOffContext start(RetryContext retryContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
|
||||
count++;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackOffPolicy createBackOffPolicy(String service) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
|
||||
@@ -43,6 +44,10 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.backoff.BackOffContext;
|
||||
import org.springframework.retry.backoff.BackOffInterruptedException;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.netflix.client.DefaultLoadBalancerRetryHandler;
|
||||
@@ -55,6 +60,7 @@ import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -74,6 +80,7 @@ import static org.mockito.Mockito.when;
|
||||
public class RibbonLoadBalancingHttpClientTests {
|
||||
|
||||
private ILoadBalancer loadBalancer;
|
||||
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -198,9 +205,10 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
}
|
||||
|
||||
private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer,
|
||||
boolean retryable, boolean retryOnAllOps,
|
||||
String serviceName, String host, int port,
|
||||
HttpClient delegate, ILoadBalancer lb, String statusCodes) throws Exception {
|
||||
boolean retryable, boolean retryOnAllOps,
|
||||
String serviceName, String host, int port,
|
||||
CloseableHttpClient delegate, ILoadBalancer lb, String statusCodes,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) throws Exception {
|
||||
ServerIntrospector introspector = mock(ServerIntrospector.class);
|
||||
RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(retriesSameServer, retriesNextServer, retryable);
|
||||
doReturn(new Server(host, port)).when(lb).chooseServer(eq(serviceName));
|
||||
@@ -215,7 +223,8 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
doReturn(context).when(clientFactory).getLoadBalancerContext(eq(serviceName));
|
||||
doReturn(clientConfig).when(clientFactory).getClientConfig(eq(serviceName));
|
||||
LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
|
||||
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(clientConfig, introspector, factory);
|
||||
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(delegate, clientConfig,
|
||||
introspector, factory, loadBalancedBackOffPolicyFactory);
|
||||
client.setLoadBalancer(lb);
|
||||
ReflectionTestUtils.setField(client, "delegate", delegate);
|
||||
return client;
|
||||
@@ -240,7 +249,7 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
doThrow(new IOException("boom")).doReturn(response).when(delegate).execute(any(HttpUriRequest.class));
|
||||
ILoadBalancer lb = mock(ILoadBalancer.class);
|
||||
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
|
||||
serviceName, host, port, delegate, lb, "");
|
||||
serviceName, host, port, delegate, lb, "", loadBalancedBackOffPolicyFactory);
|
||||
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
|
||||
doReturn(uri).when(request).getURI();
|
||||
doReturn(method).when(request).getMethod();
|
||||
@@ -272,8 +281,9 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response).
|
||||
when(delegate).execute(any(HttpUriRequest.class));
|
||||
ILoadBalancer lb = mock(ILoadBalancer.class);
|
||||
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
|
||||
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
|
||||
serviceName, host, port, delegate, lb, "");
|
||||
serviceName, host, port, delegate, lb, "", myBackOffPolicyFactory);
|
||||
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
|
||||
doReturn(uri).when(request).getURI();
|
||||
doReturn(method).when(request).getMethod();
|
||||
@@ -284,6 +294,7 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
|
||||
verify(delegate, times(3)).execute(any(HttpUriRequest.class));
|
||||
verify(lb, times(1)).chooseServer(eq(serviceName));
|
||||
assertEquals(2, myBackOffPolicyFactory.getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -305,8 +316,9 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response).
|
||||
when(delegate).execute(any(HttpUriRequest.class));
|
||||
ILoadBalancer lb = mock(ILoadBalancer.class);
|
||||
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
|
||||
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
|
||||
serviceName, host, port, delegate, lb, "");
|
||||
serviceName, host, port, delegate, lb, "", myBackOffPolicyFactory);
|
||||
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
|
||||
doReturn(method).when(request).getMethod();
|
||||
doReturn(uri).when(request).getURI();
|
||||
@@ -317,6 +329,7 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
verify(response, times(0)).close();
|
||||
verify(delegate, times(3)).execute(any(HttpUriRequest.class));
|
||||
verify(lb, times(1)).chooseServer(eq(serviceName));
|
||||
assertEquals(2, myBackOffPolicyFactory.getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -336,7 +349,7 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
when(delegate).execute(any(HttpUriRequest.class));
|
||||
ILoadBalancer lb = mock(ILoadBalancer.class);
|
||||
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
|
||||
serviceName, host, port, delegate, lb, "");
|
||||
serviceName, host, port, delegate, lb, "", loadBalancedBackOffPolicyFactory);
|
||||
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
|
||||
doReturn(method).when(request).getMethod();
|
||||
doReturn(uri).when(request).getURI();
|
||||
@@ -376,8 +389,9 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine();
|
||||
doReturn(fourOFourResponse).doReturn(response).when(delegate).execute(any(HttpUriRequest.class));
|
||||
ILoadBalancer lb = mock(ILoadBalancer.class);
|
||||
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
|
||||
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
|
||||
serviceName, host, port, delegate, lb, "404");
|
||||
serviceName, host, port, delegate, lb, "404", myBackOffPolicyFactory);
|
||||
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
|
||||
doReturn(uri).when(request).getURI();
|
||||
doReturn(method).when(request).getMethod();
|
||||
@@ -389,6 +403,7 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
verify(fourOFourResponse, times(1)).close();
|
||||
verify(delegate, times(2)).execute(any(HttpUriRequest.class));
|
||||
verify(lb, times(0)).chooseServer(eq(serviceName));
|
||||
assertEquals(1, myBackOffPolicyFactory.getCount());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -480,4 +495,28 @@ public class RibbonLoadBalancingHttpClientTests {
|
||||
return requestConfigCaptor.getValue();
|
||||
}
|
||||
|
||||
class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public BackOffContext start(RetryContext retryContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
|
||||
count++;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackOffPolicy createBackOffPolicy(String service) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ public class ContextAwareRequestTests {
|
||||
doReturn(new URI("http://foo")).when(context).uri();
|
||||
doReturn("foo").when(context).getServiceId();
|
||||
doReturn(new LinkedMultiValueMap<>()).when(context).getParams();
|
||||
doReturn("testLoadBalancerKey").when(context).getLoadBalancerKey();
|
||||
request = new TestContextAwareRequest(context);
|
||||
}
|
||||
|
||||
@@ -97,6 +98,18 @@ public class ContextAwareRequestTests {
|
||||
assertEquals(headers, request.getHeaders());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLoadBalancerKey() throws Exception {
|
||||
assertEquals("testLoadBalancerKey", request.getLoadBalancerKey());
|
||||
|
||||
RibbonCommandContext defaultContext = mock(RibbonCommandContext.class);
|
||||
doReturn(new LinkedMultiValueMap()).when(defaultContext).getHeaders();
|
||||
doReturn(null).when(defaultContext).getLoadBalancerKey();
|
||||
ContextAwareRequest defaultRequest = new TestContextAwareRequest(defaultContext);
|
||||
|
||||
assertNull(defaultRequest.getLoadBalancerKey());
|
||||
}
|
||||
|
||||
static class TestContextAwareRequest extends ContextAwareRequest {
|
||||
|
||||
public TestContextAwareRequest(RibbonCommandContext context) {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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
|
||||
* *
|
||||
* * http://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.netflix.ribbon.support;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RetryableStatusCodeExceptionTests {
|
||||
|
||||
@Test
|
||||
public void testMessage() {
|
||||
RetryableStatusCodeException ex = new RetryableStatusCodeException("foo", 404);
|
||||
assertEquals("Service foo returned a status code of 404", ex.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.ribbon.support;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -28,6 +29,7 @@ import com.google.common.collect.Lists;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import okhttp3.Request;
|
||||
@@ -92,4 +94,18 @@ public class RibbonCommandContextTest {
|
||||
new ByteArrayInputStream(TEST_CONTENT),
|
||||
Lists.newArrayList(requestCustomizer));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullSafetyWithNullableParameters() throws Exception {
|
||||
LinkedMultiValueMap headers = new LinkedMultiValueMap();
|
||||
LinkedMultiValueMap params = new LinkedMultiValueMap();
|
||||
|
||||
RibbonCommandContext testContext = new RibbonCommandContext("serviceId",
|
||||
HttpMethod.POST.toString(), "/my/route", true, headers, params,
|
||||
new ByteArrayInputStream(TEST_CONTENT), Collections.<RibbonRequestCustomizer>emptyList(),
|
||||
null, null);
|
||||
|
||||
assertNotEquals(0, testContext.hashCode());
|
||||
assertNotNull(testContext.toString());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
<eureka.version>1.8.4</eureka.version>
|
||||
<feign.version>9.5.1</feign.version>
|
||||
<hystrix.version>1.5.12</hystrix.version>
|
||||
<ribbon.version>2.2.2</ribbon.version>
|
||||
<ribbon.version>2.2.4</ribbon.version>
|
||||
<servo.version>0.10.1</servo.version>
|
||||
<zuul.version>1.3.0</zuul.version>
|
||||
<rxjava.version>1.2.0</rxjava.version>
|
||||
@@ -301,6 +301,46 @@
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-gson</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jackson-jaxb</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jackson</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java8</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jaxb</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jaxrs</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-ribbon</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-sax</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* 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.netflix.eureka;
|
||||
@@ -23,7 +22,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -51,6 +50,9 @@ import org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfi
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScope;
|
||||
import org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration;
|
||||
import org.springframework.cloud.netflix.eureka.metadata.DefaultManagementMetadataProvider;
|
||||
import org.springframework.cloud.netflix.eureka.metadata.ManagementMetadata;
|
||||
import org.springframework.cloud.netflix.eureka.metadata.ManagementMetadataProvider;
|
||||
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration;
|
||||
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
|
||||
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry;
|
||||
@@ -119,58 +121,76 @@ public class EurekaClientAutoConfiguration {
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ManagementMetadataProvider serviceManagementMetadataProvider() {
|
||||
return new DefaultManagementMetadataProvider();
|
||||
}
|
||||
|
||||
private String getProperty(String property) {
|
||||
return this.env.containsProperty(property) ? this.env.getProperty(property) : "";
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
|
||||
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) {
|
||||
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
|
||||
ManagementMetadataProvider managementMetadataProvider) {
|
||||
String hostname = getProperty("eureka.instance.hostname");
|
||||
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
|
||||
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
|
||||
int nonSecurePort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
|
||||
|
||||
int managementPort = Integer.valueOf(env.getProperty("management.port", String.valueOf(nonSecurePort)));
|
||||
String managementContextPath = env.getProperty("management.context-path", env.getProperty("server.servlet.context-path", "/"));
|
||||
String serverContextPath = env.getProperty("server.context-path", "/");
|
||||
int serverPort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
|
||||
|
||||
Integer managementPort = env.getProperty("management.server.port", Integer.class);// nullable. should be wrapped into optional
|
||||
String managementContextPath = env.getProperty("management.server.context-path");// nullable. should be wrapped into optional
|
||||
Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
|
||||
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
|
||||
instance.setNonSecurePort(nonSecurePort);
|
||||
|
||||
instance.setNonSecurePort(serverPort);
|
||||
instance.setInstanceId(getDefaultInstanceId(env));
|
||||
instance.setPreferIpAddress(preferIpAddress);
|
||||
|
||||
if(isSecurePortEnabled) {
|
||||
int securePort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
|
||||
instance.setSecurePort(securePort);
|
||||
instance.setSecurePort(serverPort);
|
||||
}
|
||||
|
||||
if (managementPort != nonSecurePort && managementPort != 0) {
|
||||
if (StringUtils.hasText(hostname)) {
|
||||
instance.setHostname(hostname);
|
||||
}
|
||||
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
|
||||
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
|
||||
if (!managementContextPath.endsWith("/")) {
|
||||
managementContextPath = managementContextPath + "/";
|
||||
}
|
||||
if (StringUtils.hasText(statusPageUrlPath)) {
|
||||
instance.setStatusPageUrlPath(statusPageUrlPath);
|
||||
}
|
||||
if (StringUtils.hasText(healthCheckUrlPath)) {
|
||||
instance.setHealthCheckUrlPath(healthCheckUrlPath);
|
||||
}
|
||||
if (StringUtils.hasText(hostname)) {
|
||||
instance.setHostname(hostname);
|
||||
}
|
||||
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
|
||||
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
|
||||
|
||||
String scheme = instance.getSecurePortEnabled() ? "https" : "http";
|
||||
try {
|
||||
URL base = new URL(scheme, instance.getHostname(), managementPort, managementContextPath);
|
||||
instance.setStatusPageUrl(new URL(base, StringUtils.trimLeadingCharacter(instance.getStatusPageUrlPath(), '/')).toString());
|
||||
instance.setHealthCheckUrl(new URL(base, StringUtils.trimLeadingCharacter(instance.getHealthCheckUrlPath(), '/')).toString());
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("Unable to set status page or health check url.", e);
|
||||
if (StringUtils.hasText(statusPageUrlPath)) {
|
||||
instance.setStatusPageUrlPath(statusPageUrlPath);
|
||||
}
|
||||
if (StringUtils.hasText(healthCheckUrlPath)) {
|
||||
instance.setHealthCheckUrlPath(healthCheckUrlPath);
|
||||
}
|
||||
|
||||
ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
|
||||
serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
if(metadata != null) {
|
||||
instance.setStatusPageUrl(metadata.getStatusPageUrl());
|
||||
instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
|
||||
Map<String, String> metadataMap = instance.getMetadataMap();
|
||||
if (metadataMap.get("management.port") == null) {
|
||||
metadataMap.put("management.port", String.valueOf(metadata.getManagementPort()));
|
||||
}
|
||||
}
|
||||
|
||||
setupJmxPort(instance, jmxPort);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void setupJmxPort(EurekaInstanceConfigBean instance, Integer jmxPort) {
|
||||
Map<String, String> metadataMap = instance.getMetadataMap();
|
||||
if (metadataMap.get("jmx.port") == null && jmxPort != null) {
|
||||
metadataMap.put("jmx.port", String.valueOf(jmxPort));
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
|
||||
return new EurekaDiscoveryClient(config, client);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.springframework.cloud.netflix.eureka.metadata;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public class DefaultManagementMetadataProvider implements ManagementMetadataProvider {
|
||||
|
||||
private static final int RANDOM_PORT = 0;
|
||||
private static final Log log = LogFactory.getLog(DefaultManagementMetadataProvider.class);
|
||||
|
||||
@Override
|
||||
public ManagementMetadata get(EurekaInstanceConfigBean instance, int serverPort,
|
||||
String serverContextPath, String managementContextPath,
|
||||
Integer managementPort) {
|
||||
if (isRandom(managementPort)) {
|
||||
return null;
|
||||
}
|
||||
if (managementPort == null && isRandom(serverPort)) {
|
||||
return null;
|
||||
}
|
||||
String healthCheckUrl = getHealthCheckUrl(instance, serverPort, serverContextPath,
|
||||
managementContextPath, managementPort);
|
||||
String statusPageUrl = getStatusPageUrl(instance, serverPort, serverContextPath,
|
||||
managementContextPath, managementPort);
|
||||
|
||||
return new ManagementMetadata(healthCheckUrl, statusPageUrl, managementPort == null ? serverPort : managementPort);
|
||||
}
|
||||
|
||||
private boolean isRandom(Integer port) {
|
||||
return port != null && port == RANDOM_PORT;
|
||||
}
|
||||
|
||||
private String getHealthCheckUrl(EurekaInstanceConfigBean instance, int serverPort, String serverContextPath,
|
||||
String managementContextPath, Integer managementPort) {
|
||||
String healthCheckUrlPath = instance.getHealthCheckUrlPath();
|
||||
String healthCheckUrl = getUrl(instance, serverPort, serverContextPath, managementContextPath,
|
||||
managementPort, healthCheckUrlPath);
|
||||
log.debug("Constructed eureka meta-data healthcheckUrl: " + healthCheckUrl);
|
||||
return healthCheckUrl;
|
||||
}
|
||||
|
||||
public String getStatusPageUrl(EurekaInstanceConfigBean instance, int serverPort, String serverContextPath,
|
||||
String managementContextPath, Integer managementPort) {
|
||||
String statusPageUrlPath = instance.getStatusPageUrlPath();
|
||||
String statusPageUrl = getUrl(instance, serverPort, serverContextPath, managementContextPath,
|
||||
managementPort, statusPageUrlPath);
|
||||
log.debug("Constructed eureka meta-data statusPageUrl: " + statusPageUrl);
|
||||
return statusPageUrl;
|
||||
}
|
||||
|
||||
private String getUrl(EurekaInstanceConfigBean instance, int serverPort,
|
||||
String serverContextPath, String managementContextPath,
|
||||
Integer managementPort, String urlPath) {
|
||||
managementContextPath = refineManagementContextPath(serverContextPath, managementContextPath, managementPort);
|
||||
if (managementPort == null) {
|
||||
managementPort = serverPort;
|
||||
}
|
||||
String scheme = instance.getSecurePortEnabled() ? "https" : "http";
|
||||
return constructValidUrl(scheme, instance.getHostname(), managementPort, managementContextPath, urlPath);
|
||||
}
|
||||
|
||||
private String refineManagementContextPath(String serverContextPath, String managementContextPath,
|
||||
Integer managementPort) {
|
||||
if(managementContextPath != null) {
|
||||
return managementContextPath;
|
||||
}
|
||||
if(managementPort != null) {
|
||||
return "/";
|
||||
}
|
||||
return serverContextPath;
|
||||
}
|
||||
|
||||
private String constructValidUrl(String scheme, String hostname, int port,
|
||||
String contextPath, String statusPath) {
|
||||
try {
|
||||
if (!contextPath.endsWith("/")) {
|
||||
contextPath = contextPath + "/";
|
||||
}
|
||||
URL base = new URL(scheme, hostname, port, contextPath);
|
||||
String refinedStatusPath = StringUtils.trimLeadingCharacter(statusPath, '/');
|
||||
return new URL(base, refinedStatusPath).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
String message = getErrorMessage(scheme, hostname, port, contextPath, statusPath);
|
||||
throw new IllegalStateException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getErrorMessage(String scheme, String hostname, int port, String contextPath, String statusPath) {
|
||||
return String.format("Failed to construct url for scheme: %s, hostName: %s port: %s contextPath: %s statusPath: %s",
|
||||
scheme, hostname, port, contextPath, statusPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.springframework.cloud.netflix.eureka.metadata;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ManagementMetadata {
|
||||
|
||||
private final String healthCheckUrl;
|
||||
private final String statusPageUrl;
|
||||
private final Integer managementPort;
|
||||
|
||||
public ManagementMetadata(String healthCheckUrl, String statusPageUrl, Integer managementPort) {
|
||||
this.healthCheckUrl = healthCheckUrl;
|
||||
this.statusPageUrl = statusPageUrl;
|
||||
this.managementPort = managementPort;
|
||||
}
|
||||
|
||||
public String getHealthCheckUrl() {
|
||||
return healthCheckUrl;
|
||||
}
|
||||
|
||||
public String getStatusPageUrl() {
|
||||
return statusPageUrl;
|
||||
}
|
||||
|
||||
public Integer getManagementPort() {
|
||||
return managementPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ManagementMetadata that = (ManagementMetadata) o;
|
||||
return Objects.equals(healthCheckUrl, that.healthCheckUrl) &&
|
||||
Objects.equals(statusPageUrl, that.statusPageUrl) &&
|
||||
Objects.equals(managementPort, that.managementPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(healthCheckUrl, statusPageUrl, managementPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("ManagementMetadata{");
|
||||
sb.append("healthCheckUrl='").append(healthCheckUrl).append('\'');
|
||||
sb.append(", statusPageUrl='").append(statusPageUrl).append('\'');
|
||||
sb.append(", managementPort=").append(managementPort);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.cloud.netflix.eureka.metadata;
|
||||
|
||||
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
|
||||
|
||||
public interface ManagementMetadataProvider {
|
||||
|
||||
ManagementMetadata get(EurekaInstanceConfigBean instance, int serverPort,
|
||||
String serverContextPath, String managementContextPath, Integer managementPort);
|
||||
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
* 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.netflix.eureka;
|
||||
@@ -81,6 +80,54 @@ public class EurekaClientAutoConfigurationTests {
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetManagementPortInMetadataMapIfEqualToServerPort() throws Exception {
|
||||
addEnvironment(this.context, "server.port=8989");
|
||||
setupContext(RefreshAutoConfiguration.class);
|
||||
|
||||
EurekaInstanceConfigBean instance = this.context
|
||||
.getBean(EurekaInstanceConfigBean.class);
|
||||
|
||||
assertEquals("8989", instance.getMetadataMap().get("management.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotSetManagementAndJmxPortsInMetadataMap() throws Exception {
|
||||
addEnvironment(this.context, "server.port=8989", "management.port=0");
|
||||
setupContext(RefreshAutoConfiguration.class);
|
||||
|
||||
EurekaInstanceConfigBean instance = this.context
|
||||
.getBean(EurekaInstanceConfigBean.class);
|
||||
|
||||
assertEquals(null, instance.getMetadataMap().get("management.port"));
|
||||
assertEquals(null, instance.getMetadataMap().get("jmx.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetManagementAndJmxPortsInMetadataMap() throws Exception {
|
||||
addEnvironment(this.context, "management.port=9999",
|
||||
"com.sun.management.jmxremote.port=6789");
|
||||
setupContext(RefreshAutoConfiguration.class);
|
||||
|
||||
EurekaInstanceConfigBean instance = this.context
|
||||
.getBean(EurekaInstanceConfigBean.class);
|
||||
assertEquals("9999", instance.getMetadataMap().get("management.port"));
|
||||
assertEquals("6789", instance.getMetadataMap().get("jmx.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotResetManagementAndJmxPortsInMetadataMap() throws Exception {
|
||||
addEnvironment(this.context, "management.port=9999",
|
||||
"eureka.instance.metadata-map.jmx.port=9898",
|
||||
"eureka.instance.metadata-map.management.port=7878");
|
||||
setupContext(RefreshAutoConfiguration.class);
|
||||
|
||||
EurekaInstanceConfigBean instance = this.context
|
||||
.getBean(EurekaInstanceConfigBean.class);
|
||||
assertEquals("7878", instance.getMetadataMap().get("management.port"));
|
||||
assertEquals("9898", instance.getMetadataMap().get("jmx.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonSecurePortPeriods() {
|
||||
testNonSecurePort("server.port");
|
||||
@@ -153,6 +200,34 @@ public class EurekaClientAutoConfigurationTests {
|
||||
instance.getHealthCheckUrl().contains("/myHealthCheck"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusPageUrl_and_healthCheckUrl_do_not_contain_server_context_path() throws Exception {
|
||||
addEnvironment(this.context, "server.port=8989",
|
||||
"management.port=9999", "server.contextPath=/service");
|
||||
|
||||
setupContext(RefreshAutoConfiguration.class);
|
||||
EurekaInstanceConfigBean instance = this.context
|
||||
.getBean(EurekaInstanceConfigBean.class);
|
||||
assertTrue("Wrong status page: " + instance.getStatusPageUrl(),
|
||||
instance.getStatusPageUrl().endsWith(":9999/info"));
|
||||
assertTrue("Wrong health check: " + instance.getHealthCheckUrl(),
|
||||
instance.getHealthCheckUrl().endsWith(":9999/health"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusPageUrl_and_healthCheckUrl_contain_management_context_path() throws Exception {
|
||||
addEnvironment(this.context,
|
||||
"server.port=8989", "management.contextPath=/management");
|
||||
|
||||
setupContext(RefreshAutoConfiguration.class);
|
||||
EurekaInstanceConfigBean instance = this.context
|
||||
.getBean(EurekaInstanceConfigBean.class);
|
||||
assertTrue("Wrong status page: " + instance.getStatusPageUrl(),
|
||||
instance.getStatusPageUrl().endsWith(":8989/management/info"));
|
||||
assertTrue("Wrong health check: " + instance.getHealthCheckUrl(),
|
||||
instance.getHealthCheckUrl().endsWith(":8989/management/health"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusPageUrlPathAndManagementPortAndContextPath() {
|
||||
TestPropertyValues.of( "server.port=8989",
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.springframework.cloud.netflix.eureka.metadata;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DefaultManagementMetadataProviderTest {
|
||||
|
||||
private static final EurekaInstanceConfigBean INSTANCE = mock(EurekaInstanceConfigBean.class);
|
||||
private final ManagementMetadataProvider provider = new DefaultManagementMetadataProvider();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
when(INSTANCE.getHostname()).thenReturn("host");
|
||||
when(INSTANCE.getHealthCheckUrlPath()).thenReturn("health");
|
||||
when(INSTANCE.getStatusPageUrlPath()).thenReturn("info");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortIsRandomAndManagementPortIsNull() throws Exception {
|
||||
int serverPort = 0;
|
||||
String serverContextPath = "/";
|
||||
String managementContextPath = null;
|
||||
Integer managementPort = null;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementPortIsRandom() throws Exception {
|
||||
int serverPort = 0;
|
||||
String serverContextPath = "/";
|
||||
String managementContextPath = null;
|
||||
Integer managementPort = 0;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPort() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/";
|
||||
String managementContextPath = null;
|
||||
Integer managementPort = null;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:7777/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:7777/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(7777);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortManagementPort() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/";
|
||||
String managementContextPath = null;
|
||||
Integer managementPort = 8888;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(8888);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortManagementPortServerContextPath() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/Server";
|
||||
String managementContextPath = null;
|
||||
Integer managementPort = 8888;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(8888);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortManagementPortServerContextPathManagementContextPath() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/Server";
|
||||
String managementContextPath = "/Management";
|
||||
Integer managementPort = 8888;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/Management/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/Management/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(8888);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortServerContextPathManagementContextPath() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/Server";
|
||||
String managementContextPath = "/Management";
|
||||
Integer managementPort = null;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:7777/Management/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:7777/Management/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(7777);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortServerContextPath() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/Server";
|
||||
String managementContextPath = null;
|
||||
Integer managementPort = null;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:7777/Server/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:7777/Server/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(7777);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPortManagementPortManagementContextPath() throws Exception {
|
||||
int serverPort = 7777;
|
||||
String serverContextPath = "/";
|
||||
String managementContextPath = "/Management";
|
||||
Integer managementPort = 8888;
|
||||
ManagementMetadata actual = provider.get(INSTANCE, serverPort, serverContextPath, managementContextPath, managementPort);
|
||||
|
||||
assertThat(actual.getHealthCheckUrl()).isEqualTo("http://host:8888/Management/health");
|
||||
assertThat(actual.getStatusPageUrl()).isEqualTo("http://host:8888/Management/info");
|
||||
assertThat(actual.getManagementPort()).isEqualTo(8888);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
0
spring-cloud-netflix-hystrix-amqp/pom.xml
Normal file
0
spring-cloud-netflix-hystrix-amqp/pom.xml
Normal file
0
spring-cloud-netflix-spectator/pom.xml
Normal file
0
spring-cloud-netflix-spectator/pom.xml
Normal file
@@ -22,7 +22,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Responsible for adding in a marker bean to trigger activation of
|
||||
* {@link ZuulServerAutoConfiguration}
|
||||
* {@link ZuulProxyAutoConfiguration}
|
||||
*
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,7 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.RIBBON_ROUTING_FILTER_ORDER;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY;
|
||||
|
||||
/**
|
||||
* Route {@link ZuulFilter} that uses Ribbon, Hystrix and pluggable http clients to send requests.
|
||||
@@ -135,6 +136,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
|
||||
|
||||
String serviceId = (String) context.get(SERVICE_ID_KEY);
|
||||
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
|
||||
Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);
|
||||
|
||||
String uri = this.helper.buildZuulRequestURI(request);
|
||||
|
||||
@@ -144,7 +146,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
|
||||
long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();
|
||||
|
||||
return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
|
||||
requestEntity, this.requestCustomizers, contentLength);
|
||||
requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
|
||||
}
|
||||
|
||||
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
|
||||
|
||||
@@ -70,6 +70,11 @@ public class FilterConstants {
|
||||
*/
|
||||
public static final String SERVICE_ID_KEY = "serviceId";
|
||||
|
||||
/**
|
||||
* Zuul {@link com.netflix.zuul.context.RequestContext} key for use in {@link org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter}
|
||||
*/
|
||||
public static final String LOAD_BALANCER_KEY = "loadBalancerKey";
|
||||
|
||||
// ORDER constants -----------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2013-2017 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
|
||||
*
|
||||
* http://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.netflix.zuul.filters.route;
|
||||
|
||||
import com.netflix.loadbalancer.AvailabilityFilteringRule;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
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.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.netflix.ribbon.StaticServerList;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
|
||||
|
||||
/**
|
||||
* @author Yongsung Yoon
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = CanaryTestZuulProxyApplication.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
value = { "zuul.routes.simple.path: /simple/**" })
|
||||
@DirtiesContext
|
||||
public class RibbonRoutingFilterLoadBalancerKeyIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate testRestTemplate;
|
||||
|
||||
@Before
|
||||
public void setTestRequestContext() {
|
||||
RequestContext context = new RequestContext();
|
||||
RequestContext.testSetCurrentContext(context);
|
||||
}
|
||||
|
||||
@After
|
||||
public void clear() {
|
||||
RequestContext.getCurrentContext().clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWithUserDefinedCanaryHeader() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-Canary-Test", "true");
|
||||
|
||||
ResponseEntity<String> result = testRestTemplate.exchange("/simple/hello", HttpMethod.GET,
|
||||
new HttpEntity<>(headers), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("canary", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWithoutUserDefinedCanaryHeader() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
ResponseEntity<String> result = testRestTemplate.exchange("/simple/hello", HttpMethod.GET,
|
||||
new HttpEntity<>(headers), String.class);
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClient(name = "simple", configuration = CanaryTestRibbonClientConfiguration.class)
|
||||
class CanaryTestZuulProxyApplication {
|
||||
|
||||
@RequestMapping(value = "/hello", method = RequestMethod.GET)
|
||||
public String hello() {
|
||||
return "canary";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter testCanarySupportPreFilter() {
|
||||
return new ZuulFilter() {
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
if (checkIfCanaryRequest(context)) {
|
||||
context.set(LOAD_BALANCER_KEY, "canary"); // set loadBalancerKey for IRule
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean checkIfCanaryRequest(RequestContext context) {
|
||||
HttpServletRequest request = context.getRequest();
|
||||
String canaryHeader = request.getHeader("X-Canary-Test"); // user defined header
|
||||
|
||||
if ((canaryHeader != null) && (canaryHeader.equalsIgnoreCase("true"))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return PRE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
class CanaryTestRibbonClientConfiguration {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private static Server testCanaryInstance;
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("normal-routing-notexist-localhost", this.port));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IRule canaryTestRule() {
|
||||
if (testCanaryInstance == null) {
|
||||
testCanaryInstance = new Server("localhost", port); // use test server as a canary instance
|
||||
}
|
||||
return new TestCanaryRule();
|
||||
}
|
||||
|
||||
public static class TestCanaryRule extends AvailabilityFilteringRule {
|
||||
@Override
|
||||
public Server choose(Object key) {
|
||||
if ((key != null) && (key.equals("canary"))) {
|
||||
return testCanaryInstance; // choose test canary server instead of normal servers.
|
||||
}
|
||||
return super.choose(key); // normal routing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,22 +19,74 @@ package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Yongsung Yoon
|
||||
*/
|
||||
public class RibbonRoutingFilterTests {
|
||||
|
||||
private RequestContext requestContext;
|
||||
private RibbonRoutingFilter filter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
setUpRequestContext();
|
||||
setupRibbonRoutingFilter();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
requestContext.unset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useServlet31Works() {
|
||||
RibbonCommandFactory factory = mock(RibbonCommandFactory.class);
|
||||
RibbonRoutingFilter filter = new RibbonRoutingFilter(new ProxyRequestHelper(), factory,
|
||||
Collections.<RibbonRequestCustomizer>emptyList());
|
||||
assertThat(filter.isUseServlet31()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadBalancerKeyToRibbonCommandContext() throws Exception {
|
||||
final String testKey = "testLoadBalancerKey";
|
||||
requestContext.set(LOAD_BALANCER_KEY, testKey);
|
||||
RibbonCommandContext commandContext = filter.buildCommandContext(requestContext);
|
||||
|
||||
assertThat(commandContext.getLoadBalancerKey()).isEqualTo(testKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullLoadBalancerKeyToRibbonCommandContext() throws Exception {
|
||||
requestContext.set(LOAD_BALANCER_KEY, null);
|
||||
RibbonCommandContext commandContext = filter.buildCommandContext(requestContext);
|
||||
|
||||
assertThat(commandContext.getLoadBalancerKey()).isNull();
|
||||
}
|
||||
|
||||
private void setUpRequestContext() {
|
||||
requestContext = RequestContext.getCurrentContext();
|
||||
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
|
||||
mockRequest.setMethod("GET");
|
||||
mockRequest.setRequestURI("/foo/bar");
|
||||
requestContext.setRequest(mockRequest);
|
||||
requestContext.setRequestQueryParams(Collections.EMPTY_MAP);
|
||||
requestContext.set(SERVICE_ID_KEY, "testServiceId");
|
||||
}
|
||||
|
||||
private void setupRibbonRoutingFilter() {
|
||||
RibbonCommandFactory factory = mock(RibbonCommandFactory.class);
|
||||
filter = new RibbonRoutingFilter(new ProxyRequestHelper(), factory, Collections.<RibbonRequestCustomizer>emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user