package org.apache.cxf.clustering;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.isA;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cxf.Bus;
import org.apache.cxf.binding.BindingFactoryManager;
import org.apache.cxf.binding.soap.Soap11;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.bus.CXFBusImpl;
import org.apache.cxf.bus.managers.BindingFactoryManagerImpl;
import org.apache.cxf.bus.managers.ConduitInitiatorManagerImpl;
import org.apache.cxf.clustering.FailoverTargetSelector.InvocationContext;
import org.apache.cxf.clustering.FailoverTargetSelector.InvocationKey;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.EndpointException;
import org.apache.cxf.endpoint.EndpointImpl;
import org.apache.cxf.endpoint.Retryable;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.jaxrs.JAXRSBindingFactory;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.service.model.BindingInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.ConduitInitiator;
import org.apache.cxf.transport.ConduitInitiatorManager;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPTransportFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
public class CircuitSwitcherTargetSelectorTest {
private static final String ENDPOINT_TRANSPORT_ID = "http://cxf.apache.org/transports/http";
CircuitSwitcherTargetSelector circuitBreakerTargetSelector = new CircuitSwitcherTargetSelector(
null, 0, 0, null);
private Retryable client;
private Endpoint ep;
@Before
public void before() throws EndpointException {
EndpointInfo ei = new EndpointInfo();
ei.setTransportId(ENDPOINT_TRANSPORT_ID);
ep = new EndpointImpl(null, null, ei);
circuitBreakerTargetSelector.setEndpoint(ep);
circuitBreakerTargetSelector.setResetTimeout(200l);
}
@Test
public void shouldRequireFailoverWhereIOExceptionHasBeenThrown() {
circuitBreakerTargetSelector = new CircuitSwitcherTargetSelector(null, 0, 0, null);
Exchange exchange = new ExchangeImpl();
Message message = new SoapMessage(Soap11.getInstance());
exchange.setOutMessage(message);
message.put(Exception.class, new IOException());
boolean requiresFailover = circuitBreakerTargetSelector.requiresFailover(exchange);
assertTrue(requiresFailover);
}
@Test
public void shouldSendMessageToFirstAddress() throws EndpointException {
List<String> addresses = new ArrayList<String>();
addresses.add("http://address1");
addresses.add("http://address2");
addresses.add("http://address3");
circuitBreakerTargetSelector.setAddressList(addresses);
Message message = sendRequestToFirstAvailableAddress("/endpoint");
assertSendingMessageTo(message, "http://address1/endpoint");
message = sendRequestToFirstAvailableAddress("/endpoint2");
assertSendingMessageTo(message, "http://address1/endpoint2");
}
@Test
public void shouldSendMessageToFirstAddressAfter1Failure() throws Exception {
List<String> addresses = new ArrayList<String>();
addresses.add("http://address1");
addresses.add("http://address2");
addresses.add("http://address3");
circuitBreakerTargetSelector.setFailureThreshold(3);
circuitBreakerTargetSelector.setAddressList(addresses);
Message message = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message, "http://address1/resourceABC");
}
@Test
public void shouldKeepSendingToFirstAddressAfter2ndFailure() throws Exception {
circuitBreakerTargetSelector.setFailureThreshold(3);
circuitBreakerTargetSelector.setAddressList(asList("http://address1", "http://address2"));
Message message = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message, "http://address1/resourceABC");
verifyRertyToNode("http://address1");
Message message2 = sendRequestToFirstAvailableAddressAndForceFailure("/resourceBCD");
assertSendingMessageTo(message2, "http://address1/resourceBCD");
verifyRertyToNode("http://address1");
}
@Test
public void shouldFailoverTo2ndAddressAfterExceedingFailureThreshold() throws Exception {
circuitBreakerTargetSelector.setFailureThreshold(3);
circuitBreakerTargetSelector.setResetTimeout(300);
circuitBreakerTargetSelector.setAddressList(asList("http://address1", "http://address2",
"http://address3"));
Message message = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message, "http://address1/resourceABC");
Message message2 = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message2, "http://address1/resourceABC");
Message message3 = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message3, "http://address1/resourceABC");
verifyRertyToNode("http://address2");
// 3 failures on address 1, redirecting to address2
Message message4 = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message4, "http://address2/resourceABC");
Message message5 = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message5, "http://address2/resourceABC");
Message message6 = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
// 3 failures on address 2, redirecting to address3
assertSendingMessageTo(message6, "http://address2/resourceABC");
verifyRertyToNode("http://address3");
}
@Test
public void shouldDefaultTo2ndAddressAfterFailover() throws Exception {
List<String> addresses = new ArrayList<String>();
addresses.add("http://addressA");
addresses.add("http://addressB");
addresses.add("http://addressC");
circuitBreakerTargetSelector.setFailureThreshold(0);
circuitBreakerTargetSelector.setAddressList(addresses);
Message message = sendRequestToFirstAvailableAddressAndForceFailure("/resourceABC");
assertSendingMessageTo(message, "http://addressA/resourceABC");
verifyRertyToNode("http://addressB");
Message message2 = sendRequestToFirstAvailableAddress("/endpoint");
assertSendingMessageTo(message2, "http://addressB/endpoint");
Message message3 = sendRequestToFirstAvailableAddress("/endpoint2");
assertSendingMessageTo(message3, "http://addressB/endpoint2");
}
@Test
public void shouldFailbackAfterResetTimeoutElapsed() throws InterruptedException {
List<String> addresses = new ArrayList<String>();
addresses.add("http://addressA");
addresses.add("http://addressB");
addresses.add("http://addressC");
circuitBreakerTargetSelector.setResetTimeout(200l);
circuitBreakerTargetSelector.setFailureThreshold(0);
circuitBreakerTargetSelector.setAddressList(addresses);
sendRequestToFirstAvailableAddressAndForceFailure("/endpointCBA");
Message message = sendRequestToFirstAvailableAddress("/endpointABC");
assertSendingMessageTo(message, "http://addressB/endpointABC");
Message message2 = sendRequestToFirstAvailableAddress("/endpointDEF");
assertSendingMessageTo(message2, "http://addressB/endpointDEF");
Thread.sleep(250l);
// failback
Message message3 = sendRequestToFirstAvailableAddress("/endpointAAA");
assertSendingMessageTo(message3, "http://addressA/endpointAAA");
}
@Test
public void shouldSetReceiveTimeoutOnConduit() {
List<String> addresses = new ArrayList<String>();
addresses.add("http://addressA");
circuitBreakerTargetSelector.setAddressList(addresses);
long receiveTimeout = 500l;
circuitBreakerTargetSelector.setReceiveTimeout(receiveTimeout);
HTTPConduit conduit = (HTTPConduit) circuitBreakerTargetSelector.selectConduit(messageTo(
"http://abc", ""));
assertThat(conduit.getClient().getReceiveTimeout(), is(receiveTimeout));
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldThrowExceptionIfNoMoreNodesToFailover() {
circuitBreakerTargetSelector.setFailureThreshold(0);
circuitBreakerTargetSelector.setResetTimeout(200);
circuitBreakerTargetSelector.setAddressList(asList("http://addressA", "http://addressB"));
Message message = sendRequestToFirstAvailableAddressAndForceFailure("/endpointAAA");
assertSendingMessageTo(message, "http://addressA/endpointAAA");
Message message2 = sendRequestToFirstAvailableAddressAndForceFailure("/endpointAAA");
assertSendingMessageTo(message2, "http://addressB/endpointAAA");
thrown.expect(Fault.class);
thrown.expectCause(isA(IOException.class));
sendRequestToFirstAvailableAddressAndForceFailure("/endpointAAA");
}
private Message sendRequestToFirstAvailableAddressAndForceFailure(String requestPath) {
Message message = messageTo("http://originalAddress", requestPath);
message.put(Exception.class, new IOException());
circuitBreakerTargetSelector.selectConduit(message);
circuitBreakerTargetSelector.complete(message.getExchange());
return message;
}
private Message sendRequestToFirstAvailableAddress(String requestPath) {
Message message = messageTo("http://originalAddress", requestPath);
circuitBreakerTargetSelector.selectConduit(message);
circuitBreakerTargetSelector.complete(message.getExchange());
return message;
}
private Message messageTo(String requestBaseURL, String requestPath) {
Message message = new SoapMessage(Soap11.getInstance());
String requestURL = requestBaseURL + requestPath;
message.put(Message.ENDPOINT_ADDRESS, requestURL);
message.put(Message.BASE_PATH, requestBaseURL);
message.put(Message.REQUEST_URI, requestURL);
HashMap<String, Object> ctx = new HashMap<String, Object>();
ctx.put(Client.REQUEST_CONTEXT, new HashMap<String, Object>());
message.put(Message.INVOCATION_CONTEXT, ctx);
CXFBusImpl bus = new CXFBusImpl();
BindingFactoryManagerImpl bindingFactoryManager = new BindingFactoryManagerImpl();
bindingFactoryManager.registerBindingFactory("abc", new JAXRSBindingFactory());
bus.setExtension(bindingFactoryManager, BindingFactoryManager.class);
Map<String, ConduitInitiator> conduitInitiators = new HashMap<String, ConduitInitiator>();
ConduitInitiator ci = new HTTPTransportFactory(bus);
conduitInitiators.put(ENDPOINT_TRANSPORT_ID, ci);
ConduitInitiatorManagerImpl cim = new ConduitInitiatorManagerImpl(conduitInitiators);
bus.setExtension(cim, ConduitInitiatorManager.class);
Exchange exchange = exchange(message);
exchange.put(Bus.class, bus);
EndpointInfo ei = new EndpointInfo();
ei.setAddress("http://abc123");
BindingInfo b = new BindingInfo(null, "abc");
ei.setBinding(b);
Endpoint endpointMock = mock(Endpoint.class);
when(endpointMock.getEndpointInfo()).thenReturn(ei);
exchange.put(Endpoint.class, endpointMock);
message.setExchange(exchange);
message.setContent(List.class, new ArrayList<String>());
circuitBreakerTargetSelector.prepare(message);
InvocationKey key = new InvocationKey(exchange);
InvocationContext invocation = circuitBreakerTargetSelector.getInvocation(key);
invocation.getContext().put(Message.ENDPOINT_ADDRESS, requestPath);
invocation.getContext().put("org.apache.cxf.request.uri", requestPath);
return message;
}
private Exchange exchange(Message message) {
Exchange exchange = new ExchangeImpl();
exchange.setOutMessage(message);
client = mock(Retryable.class);
exchange.put(Retryable.class, client);
return exchange;
}
private void assertSendingMessageTo(Message message, String address) {
assertThat((String) message.get(Message.ENDPOINT_ADDRESS), is(address));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void verifyRertyToNode(String address) throws Exception {
ArgumentCaptor<Map> requestContextCaptor = ArgumentCaptor.forClass(Map.class);
verify(client).invoke(any(BindingOperationInfo.class), any(Object[].class),
requestContextCaptor.capture(), any(Exchange.class));
Map requestContect = (Map) requestContextCaptor.getValue().get(Client.REQUEST_CONTEXT);
assertThat((String) requestContect.get(Message.ENDPOINT_ADDRESS), is(address));
}
}