Commit a4f30ebf by Ryan Baxter

Make the use of Spring Retry optional when using Feign. Fixes #1708

parent de886d6c
...@@ -1492,20 +1492,6 @@ The default HTTP client used by zuul is now backed by the Apache HTTP Client ins ...@@ -1492,20 +1492,6 @@ The default HTTP client used by zuul is now backed by the Apache HTTP Client ins
deprecated Ribbon `RestClient`. To use `RestClient` or to use the `okhttp3.OkHttpClient` set deprecated Ribbon `RestClient`. To use `RestClient` or to use the `okhttp3.OkHttpClient` set
`ribbon.restclient.enabled=true` or `ribbon.okhttp.enabled=true` respectively. `ribbon.restclient.enabled=true` or `ribbon.okhttp.enabled=true` respectively.
==== Retrying Failed Requests
When using the Apache Http Client or the OK HTTP Client you can enable them to automatically
retry failed requests by adding https://github.com/spring-projects/spring-retry[Spring Retry]
to your application's classpath. When Zuul uses Ribbon, Zuul will honor some of the Ribbon
configuration values related to retrying failed requests. The properties you can use are
`client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextServer`, and
`client.ribbon.OkToRetryOnAllOperations`. See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation]
for a description of what there properties do.
You can turn off Zuul's retry functionality by setting `zuul.retryable` to `false`. You
can also disable retry functionality on route by route basis by setting
`zuul.routes.routename.retryable` to `false`.
=== Cookies and Sensitive Headers === Cookies and Sensitive Headers
It's OK to share headers between services in the same system, but you It's OK to share headers between services in the same system, but you
...@@ -2412,3 +2398,28 @@ TIP: After executing several requests against your service, you can gather some ...@@ -2412,3 +2398,28 @@ TIP: After executing several requests against your service, you can gather some
The Atlas wiki contains a link:https://github.com/Netflix/atlas/wiki/Single-Line[compilation of sample queries] for various scenarios. The Atlas wiki contains a link:https://github.com/Netflix/atlas/wiki/Single-Line[compilation of sample queries] for various scenarios.
Make sure to check out the link:https://github.com/Netflix/atlas/wiki/Alerting-Philosophy[alerting philosophy] and docs on using link:https://github.com/Netflix/atlas/wiki/DES[double exponential smoothing] to generate dynamic alert thresholds. Make sure to check out the link:https://github.com/Netflix/atlas/wiki/Alerting-Philosophy[alerting philosophy] and docs on using link:https://github.com/Netflix/atlas/wiki/DES[double exponential smoothing] to generate dynamic alert thresholds.
[[retrying-failed-requests]]
=== Retrying Failed Requests
Spring Cloud Netflix offers a variety of ways to make HTTP requests. You can use a load balanced
`RestTemplate`, Ribbon, or Feign. No matter how you choose to your HTTP requests, there is always
a chance the request may fail. When a request fails you may want to have the request retried
automatically. To accomplish this when using Sping Cloud Netflix you need to include
https://github.com/spring-projects/spring-retry[Spring Retry] on your application's classpath.
When Spring Retry is present load balanced `RestTemplates`, Feign, and Zuul will automatically
retry any failed requests (assuming you configuration allows it to).
==== Configuration
Anytime Ribbon is used with Spring Retry you can control the retry functionality by configuring
certain Ribbon properties. The properties you can use are
`client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextServer`, and
`client.ribbon.OkToRetryOnAllOperations`. See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation]
for a description of what there properties do.
==== Zuul
You can turn off Zuul's retry functionality by setting `zuul.retryable` to `false`. You
can also disable retry functionality on route by route basis by setting
`zuul.routes.routename.retryable` to `false`.
...@@ -37,6 +37,7 @@ public class CachingSpringLoadBalancerFactory { ...@@ -37,6 +37,7 @@ public class CachingSpringLoadBalancerFactory {
private final SpringClientFactory factory; private final SpringClientFactory factory;
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private boolean enableRetry = false;
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>(); private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
...@@ -51,6 +52,13 @@ public class CachingSpringLoadBalancerFactory { ...@@ -51,6 +52,13 @@ public class CachingSpringLoadBalancerFactory {
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
} }
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, boolean enableRetry) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.enableRetry = enableRetry;
}
public FeignLoadBalancer create(String clientName) { public FeignLoadBalancer create(String clientName) {
if (this.cache.containsKey(clientName)) { if (this.cache.containsKey(clientName)) {
return this.cache.get(clientName); return this.cache.get(clientName);
...@@ -58,8 +66,8 @@ public class CachingSpringLoadBalancerFactory { ...@@ -58,8 +66,8 @@ public class CachingSpringLoadBalancerFactory {
IClientConfig config = this.factory.getClientConfig(clientName); IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer client = new FeignLoadBalancer(lb, config, serverIntrospector, FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory); loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client); this.cache.put(clientName, client);
return client; return client;
} }
......
...@@ -29,26 +29,16 @@ import java.util.HashMap; ...@@ -29,26 +29,16 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.AbstractLoadBalancerAwareClient;
import com.netflix.client.ClientException; import com.netflix.client.ClientException;
import com.netflix.client.ClientRequest; import com.netflix.client.ClientRequest;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
import com.netflix.client.IResponse; import com.netflix.client.IResponse;
import com.netflix.client.RequestSpecificRetryHandler; import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.ILoadBalancer;
...@@ -57,20 +47,17 @@ import com.netflix.loadbalancer.Server; ...@@ -57,20 +47,17 @@ import com.netflix.loadbalancer.Server;
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded; import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded;
public class FeignLoadBalancer extends public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> implements AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
ServiceInstanceChooser {
private final int connectTimeout; protected int connectTimeout;
private final int readTimeout; protected int readTimeout;
private final IClientConfig clientConfig; protected IClientConfig clientConfig;
private final ServerIntrospector serverIntrospector; protected ServerIntrospector serverIntrospector;
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { ServerIntrospector serverIntrospector) {
super(lb, clientConfig); super(lb, clientConfig);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; this.setRetryHandler(RetryHandler.DEFAULT);
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
this.clientConfig = clientConfig; this.clientConfig = clientConfig;
this.connectTimeout = clientConfig.get(CommonClientConfigKey.ConnectTimeout); this.connectTimeout = clientConfig.get(CommonClientConfigKey.ConnectTimeout);
this.readTimeout = clientConfig.get(CommonClientConfigKey.ReadTimeout); this.readTimeout = clientConfig.get(CommonClientConfigKey.ReadTimeout);
...@@ -78,9 +65,9 @@ public class FeignLoadBalancer extends ...@@ -78,9 +65,9 @@ public class FeignLoadBalancer extends
} }
@Override @Override
public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException { throws IOException {
final Request.Options options; Request.Options options;
if (configOverride != null) { if (configOverride != null) {
options = new Request.Options( options = new Request.Options(
configOverride.get(CommonClientConfigKey.ConnectTimeout, configOverride.get(CommonClientConfigKey.ConnectTimeout,
...@@ -91,35 +78,26 @@ public class FeignLoadBalancer extends ...@@ -91,35 +78,26 @@ public class FeignLoadBalancer extends
else { else {
options = new Request.Options(this.connectTimeout, this.readTimeout); options = new Request.Options(this.connectTimeout, this.readTimeout);
} }
LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); Response response = request.client().execute(request.toRequest(), options);
RetryTemplate retryTemplate = new RetryTemplate(); return new RibbonResponse(request.getUri(), response);
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
@Override
public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException {
Request feignRequest = null;
//on retries the policy will choose the server and set it in the context
//extract the server and update the request being made
if(retryContext instanceof LoadBalancedRetryContext) {
ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance();
if(service != null) {
feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
}
}
if(feignRequest == null) {
feignRequest = request.toRequest();
}
Response response = request.client().execute(feignRequest, options);
return new RibbonResponse(request.getUri(), response);
}
});
} }
@Override @Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler( public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
RibbonRequest request, IClientConfig requestConfig) { RibbonRequest request, IClientConfig requestConfig) {
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig); if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations,
false)) {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
if (!request.toRequest().method().equals("GET")) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
requestConfig);
}
else {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
} }
@Override @Override
...@@ -128,12 +106,6 @@ public class FeignLoadBalancer extends ...@@ -128,12 +106,6 @@ public class FeignLoadBalancer extends
return super.reconstructURIWithServer(server, uri); return super.reconstructURIWithServer(server, uri);
} }
@Override
public ServiceInstance choose(String serviceId) {
return new RibbonLoadBalancerClient.RibbonServer(serviceId,
this.getLoadBalancer().chooseServer(serviceId));
}
static class RibbonRequest extends ClientRequest implements Cloneable { static class RibbonRequest extends ClientRequest implements Cloneable {
private final Request request; private final Request request;
...@@ -188,6 +160,7 @@ public class FeignLoadBalancer extends ...@@ -188,6 +160,7 @@ public class FeignLoadBalancer extends
}; };
} }
@Override @Override
public Object clone() { public Object clone() {
return new RibbonRequest(this.client, this.request, getUri()); return new RibbonRequest(this.client, this.request, getUri());
......
...@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
...@@ -50,9 +51,18 @@ public class FeignRibbonClientAutoConfiguration { ...@@ -50,9 +51,18 @@ public class FeignRibbonClientAutoConfiguration {
@Bean @Bean
@Primary @Primary
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory( public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
@Bean
@Primary
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryPolicyFactory retryPolicyFactory) { SpringClientFactory factory, LoadBalancedRetryPolicyFactory retryPolicyFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory); return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, true);
} }
@Bean @Bean
......
/*
*
* * 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.feign.ribbon;
import feign.Request;
import feign.Response;
import java.io.IOException;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
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.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
/**
* A {@link FeignLoadBalancer} that leverages Spring Retry to retry failed requests.
* @author Ryan Baxter
*/
public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser {
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
super(lb, clientConfig, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
}
@Override
public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride)
throws IOException {
final Request.Options options;
if (configOverride != null) {
options = new Request.Options(
configOverride.get(CommonClientConfigKey.ConnectTimeout,
this.connectTimeout),
(configOverride.get(CommonClientConfigKey.ReadTimeout,
this.readTimeout)));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
@Override
public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException {
Request feignRequest = null;
//on retries the policy will choose the server and set it in the context
//extract the server and update the request being made
if(retryContext instanceof LoadBalancedRetryContext) {
ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance();
if(service != null) {
feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
}
}
if(feignRequest == null) {
feignRequest = request.toRequest();
}
Response response = request.client().execute(feignRequest, options);
return new RibbonResponse(request.getUri(), response);
}
});
}
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
FeignLoadBalancer.RibbonRequest request, IClientConfig requestConfig) {
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig);
}
@Override
public ServiceInstance choose(String serviceId) {
return new RibbonLoadBalancerClient.RibbonServer(serviceId,
this.getLoadBalancer().chooseServer(serviceId));
}
}
...@@ -17,7 +17,6 @@ import org.mockito.MockitoAnnotations; ...@@ -17,7 +17,6 @@ import org.mockito.MockitoAnnotations;
import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.RibbonRequest; import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.RibbonRequest;
import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.RibbonResponse; import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.RibbonResponse;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector; import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import java.net.URI; import java.net.URI;
...@@ -49,8 +48,6 @@ public class FeignLoadBalancerTests { ...@@ -49,8 +48,6 @@ public class FeignLoadBalancerTests {
private ILoadBalancer lb; private ILoadBalancer lb;
@Mock @Mock
private IClientConfig config; private IClientConfig config;
@Mock
private RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private FeignLoadBalancer feignLoadBalancer; private FeignLoadBalancer feignLoadBalancer;
...@@ -76,7 +73,7 @@ public class FeignLoadBalancerTests { ...@@ -76,7 +73,7 @@ public class FeignLoadBalancerTests {
public void testUriInsecure() { public void testUriInsecure() {
when(this.config.get(IsSecure)).thenReturn(false); when(this.config.get(IsSecure)).thenReturn(false);
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config, this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
this.inspector, loadBalancedRetryPolicyFactory); this.inspector);
Request request = new RequestTemplate().method("GET").append("http://foo/") Request request = new RequestTemplate().method("GET").append("http://foo/")
.request(); .request();
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request,
...@@ -97,7 +94,7 @@ public class FeignLoadBalancerTests { ...@@ -97,7 +94,7 @@ public class FeignLoadBalancerTests {
public void testSecureUriFromClientConfig() { public void testSecureUriFromClientConfig() {
when(this.config.get(IsSecure)).thenReturn(true); when(this.config.get(IsSecure)).thenReturn(true);
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config, this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
this.inspector, loadBalancedRetryPolicyFactory); this.inspector);
Server server = new Server("foo", 7777); Server server = new Server("foo", 7777);
URI uri = this.feignLoadBalancer.reconstructURIWithServer(server, URI uri = this.feignLoadBalancer.reconstructURIWithServer(server,
new URI("http://foo/")); new URI("http://foo/"));
...@@ -119,7 +116,7 @@ public class FeignLoadBalancerTests { ...@@ -119,7 +116,7 @@ public class FeignLoadBalancerTests {
public Map<String, String> getMetadata(Server server) { public Map<String, String> getMetadata(Server server) {
return null; return null;
} }
}, loadBalancedRetryPolicyFactory); });
Server server = new Server("foo", 7777); Server server = new Server("foo", 7777);
URI uri = this.feignLoadBalancer.reconstructURIWithServer(server, URI uri = this.feignLoadBalancer.reconstructURIWithServer(server,
new URI("http://foo/")); new URI("http://foo/"));
...@@ -130,7 +127,7 @@ public class FeignLoadBalancerTests { ...@@ -130,7 +127,7 @@ public class FeignLoadBalancerTests {
@SneakyThrows @SneakyThrows
public void testSecureUriFromClientConfigOverride() { public void testSecureUriFromClientConfigOverride() {
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config, this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
this.inspector, loadBalancedRetryPolicyFactory); this.inspector);
Server server = Mockito.mock(Server.class); Server server = Mockito.mock(Server.class);
when(server.getPort()).thenReturn(443); when(server.getPort()).thenReturn(443);
when(server.getHost()).thenReturn("foo"); when(server.getHost()).thenReturn("foo");
......
/*
*
* * 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.feign.ribbon;
import feign.Client;
import feign.Request;
import feign.Response;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
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 com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import static com.netflix.client.config.CommonClientConfigKey.ConnectTimeout;
import static com.netflix.client.config.CommonClientConfigKey.MaxAutoRetries;
import static com.netflix.client.config.CommonClientConfigKey.MaxAutoRetriesNextServer;
import static com.netflix.client.config.CommonClientConfigKey.OkToRetryOnAllOperations;
import static com.netflix.client.config.CommonClientConfigKey.ReadTimeout;
import static com.netflix.client.config.DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES;
import static com.netflix.client.config.DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
*/
public class RetryableFeignLoadBalancerTest {
@Mock
private ILoadBalancer lb;
@Mock
private IClientConfig config;
private ServerIntrospector inspector = new DefaultServerIntrospector();
private Integer defaultConnectTimeout = 10000;
private Integer defaultReadTimeout = 10000;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(this.config.get(MaxAutoRetries, DEFAULT_MAX_AUTO_RETRIES)).thenReturn(1);
when(this.config.get(MaxAutoRetriesNextServer,
DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER)).thenReturn(1);
when(this.config.get(OkToRetryOnAllOperations, eq(anyBoolean())))
.thenReturn(true);
when(this.config.get(ConnectTimeout)).thenReturn(this.defaultConnectTimeout);
when(this.config.get(ReadTimeout)).thenReturn(this.defaultReadTimeout);
}
@Test
public void executeNoFailure() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
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);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(1)).execute(any(Request.class), any(Request.Options.class));
}
@Test
public void executeNeverRetry() throws Exception {
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
doThrow(new IOException("boom")).when(client).execute(any(Request.class), any(Request.Options.class));
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, new LoadBalancedRetryPolicyFactory() {
@Override
public LoadBalancedRetryPolicy create(String s, ServiceInstanceChooser serviceInstanceChooser) {
return null;
}
});
try {
feignLb.execute(request, null);
} catch(Exception e) {
assertThat(e, instanceOf(IOException.class));
} finally {
verify(client, times(1)).execute(any(Request.class), any(Request.Options.class));
}
}
@Test
public void executeRetry() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
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);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
}
@Test
public void getRequestSpecificRetryHandler() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
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);
RequestSpecificRetryHandler retryHandler = feignLb.getRequestSpecificRetryHandler(request, config);
assertEquals(1, retryHandler.getMaxRetriesOnNextServer());
assertEquals(1, retryHandler.getMaxRetriesOnSameServer());
}
@Test
public void choose() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
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));
final Server server = new Server("foo", 80);
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(new ILoadBalancer() {
@Override
public void addServers(List<Server> list) {
}
@Override
public Server chooseServer(Object o) {
return server;
}
@Override
public void markServerDown(Server server) {
}
@Override
public List<Server> getServerList(boolean b) {
return null;
}
@Override
public List<Server> getReachableServers() {
return null;
}
@Override
public List<Server> getAllServers() {
return null;
}
}, config, inspector, loadBalancedRetryPolicyFactory);
ServiceInstance serviceInstance = feignLb.choose("foo");
assertEquals("foo", serviceInstance.getHost());
assertEquals(80, serviceInstance.getPort());
}
}
\ No newline at end of file
...@@ -28,12 +28,18 @@ import org.springframework.cloud.ClassPathExclusions; ...@@ -28,12 +28,18 @@ import org.springframework.cloud.ClassPathExclusions;
import org.springframework.cloud.FilteredClassPathRunner; import org.springframework.cloud.FilteredClassPathRunner;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer;
import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.cloud.netflix.feign.ribbon.RetryableFeignLoadBalancer;
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is;
/** /**
* @author Ryan Baxter * @author Ryan Baxter
...@@ -47,7 +53,8 @@ public class SpringRetryDisabledTests { ...@@ -47,7 +53,8 @@ public class SpringRetryDisabledTests {
@Before @Before
public void setUp() { public void setUp() {
context = new SpringApplicationBuilder().web(false) context = new SpringApplicationBuilder().web(false)
.sources(RibbonAutoConfiguration.class,LoadBalancerAutoConfiguration.class, RibbonClientConfiguration.class).run(); .sources(RibbonAutoConfiguration.class,LoadBalancerAutoConfiguration.class, RibbonClientConfiguration.class,
FeignRibbonClientAutoConfiguration.class).run();
} }
@After @After
...@@ -65,5 +72,10 @@ public class SpringRetryDisabledTests { ...@@ -65,5 +72,10 @@ public class SpringRetryDisabledTests {
Map<String, RibbonLoadBalancingHttpClient> clients = context.getBeansOfType(RibbonLoadBalancingHttpClient.class); Map<String, RibbonLoadBalancingHttpClient> clients = context.getBeansOfType(RibbonLoadBalancingHttpClient.class);
assertThat(clients.values(), hasSize(1)); assertThat(clients.values(), hasSize(1));
assertThat(clients.values().toArray()[0], instanceOf(RibbonLoadBalancingHttpClient.class)); assertThat(clients.values().toArray()[0], instanceOf(RibbonLoadBalancingHttpClient.class));
Map<String, CachingSpringLoadBalancerFactory> lbFactorys = context.getBeansOfType(CachingSpringLoadBalancerFactory.class);
assertThat(lbFactorys.values(), hasSize(1));
FeignLoadBalancer lb =lbFactorys.values().iterator().next().create("foo");
assertThat(lb, instanceOf(FeignLoadBalancer.class));
assertThat(lb, is(not(instanceOf(RetryableFeignLoadBalancer.class))));
} }
} }
...@@ -19,11 +19,16 @@ ...@@ -19,11 +19,16 @@
package org.springframework.cloud.netflix.ribbon; package org.springframework.cloud.netflix.ribbon;
import java.util.Map; import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer;
import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.cloud.netflix.feign.ribbon.RetryableFeignLoadBalancer;
import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient;
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
...@@ -39,7 +44,8 @@ import static org.hamcrest.collection.IsCollectionWithSize.hasSize; ...@@ -39,7 +44,8 @@ import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RibbonAutoConfiguration.class, RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class}) @ContextConfiguration(classes = {RibbonAutoConfiguration.class, RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class,
FeignRibbonClientAutoConfiguration.class})
public class SpringRetryEnabledTests implements ApplicationContextAware { public class SpringRetryEnabledTests implements ApplicationContextAware {
private ApplicationContext context; private ApplicationContext context;
...@@ -52,6 +58,10 @@ public class SpringRetryEnabledTests implements ApplicationContextAware { ...@@ -52,6 +58,10 @@ public class SpringRetryEnabledTests implements ApplicationContextAware {
Map<String, RibbonLoadBalancingHttpClient> clients = context.getBeansOfType(RibbonLoadBalancingHttpClient.class); Map<String, RibbonLoadBalancingHttpClient> clients = context.getBeansOfType(RibbonLoadBalancingHttpClient.class);
assertThat(clients.values(), hasSize(1)); assertThat(clients.values(), hasSize(1));
assertThat(clients.values().toArray()[0], instanceOf(RetryableRibbonLoadBalancingHttpClient.class)); assertThat(clients.values().toArray()[0], instanceOf(RetryableRibbonLoadBalancingHttpClient.class));
Map<String, CachingSpringLoadBalancerFactory> lbFactorys = context.getBeansOfType(CachingSpringLoadBalancerFactory.class);
assertThat(lbFactorys.values(), Matchers.hasSize(1));
FeignLoadBalancer lb =lbFactorys.values().iterator().next().create("foo");
assertThat(lb, instanceOf(RetryableFeignLoadBalancer.class));
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment