/*
*
* * 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.zuul.filters.route.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
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.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
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.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.zuul.context.RequestContext;
import static org.junit.Assert.assertEquals;
/**
* @author Ryan Baxter
*/
public abstract class RibbonRetryIntegrationTestBase {
private final Log LOG = LogFactory.getLog(RibbonRetryIntegrationTestBase.class);
@Value("${local.server.port}")
protected int port;
@Before
public void setup() {
RequestContext.getCurrentContext().clear();
String uri = "/resetError";
new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
}
@Test
public void retryable() {
String uri = "/retryable/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void retryableFourOFour() {
String uri = "/retryable/404everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void postRetryOK() {
String uri = "/retryable/posteveryothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.POST,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void getRetryable() {
String uri = "/getretryable/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void postNotRetryable() {
String uri = "/getretryable/posteveryothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.POST,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
@Test
public void disableRetry() {
String uri = "/disableretry/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
LOG.info("Response Body: " + result.getBody());
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
@Test
public void globalRetryDisabled() {
String uri = "/globalretrydisabled/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
LOG.info("Response Body: " + result.getBody());
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
@RibbonClients({
@RibbonClient(name = "retryable", configuration = RibbonClientConfiguration.class),
@RibbonClient(name = "disableretry", configuration = RibbonClientConfiguration.class),
@RibbonClient(name = "globalretrydisabled", configuration = RibbonClientConfiguration.class),
@RibbonClient(name = "getretryable", configuration = RibbonClientConfiguration.class)})
public static class RetryableTestConfig {
private boolean error = true;
@RequestMapping("/resetError")
public void resetError() {
error = true;
}
@RequestMapping("/everyothererror")
public ResponseEntity<String> timeout() {
boolean shouldError = error;
error = !error;
try {
if(shouldError) {
Thread.sleep(80000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ResponseEntity<String>("no error", HttpStatus.OK);
}
@RequestMapping(path = "/posteveryothererror", method = RequestMethod.POST)
public ResponseEntity<String> postTimeout() {
return timeout();
}
@RequestMapping("/404everyothererror")
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> fourOFourError() {
boolean shouldError = error;
error = !error;
if(shouldError) {
return new ResponseEntity<String>("not found", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<String>("no error", HttpStatus.OK);
}
}
@Configuration
public static class RibbonClientConfiguration {
@Value("${local.server.port}")
private int port;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
}
@Configuration
public static class FourOFourRetryableRibbonConfiguration extends RibbonClientConfiguration {
@Bean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory(SpringClientFactory factory) {
return new MyRibbonRetryPolicyFactory(factory);
}
public static class MyRibbonRetryPolicyFactory extends RibbonLoadBalancedRetryPolicyFactory {
private SpringClientFactory factory;
public MyRibbonRetryPolicyFactory(SpringClientFactory clientFactory) {
super(clientFactory);
this.factory = clientFactory;
}
@Override
public LoadBalancedRetryPolicy create(String serviceId, ServiceInstanceChooser loadBalanceChooser) {
RibbonLoadBalancerContext lbContext = this.factory
.getLoadBalancerContext(serviceId);
return new MyLoadBalancedRetryPolicy(serviceId, lbContext, loadBalanceChooser);
}
class MyLoadBalancedRetryPolicy extends RibbonLoadBalancedRetryPolicy {
public MyLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser) {
super(serviceId, context, loadBalanceChooser);
}
@Override
public boolean retryableStatusCode( int statusCode) {
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return true;
}
return super.retryableStatusCode(statusCode);
}
}
}
}
}