/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cxf.systest.clustering;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.extensions.soap.SOAPAddress;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.cxf.clustering.FailoverTargetSelector;
import org.apache.cxf.clustering.RandomStrategy;
import org.apache.cxf.clustering.SequentialStrategy;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.endpoint.ConduitSelector;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.greeter_control.ClusteredGreeterService;
import org.apache.cxf.greeter_control.Control;
import org.apache.cxf.greeter_control.ControlService;
import org.apache.cxf.greeter_control.Greeter;
import org.apache.cxf.greeter_control.PingMeFault;
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.cxf.transport.http.HTTPException;
import org.apache.cxf.ws.addressing.MAPAggregator;
import org.apache.cxf.ws.addressing.soap.MAPCodec;
import org.apache.cxf.wsdl.WSDLManager;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Tests failover within a static cluster.
*/
public class FailoverTest extends AbstractBusClientServerTestBase {
public static final String PORT_0 = allocatePort(Server.class, 0);
public static final String PORT_A = allocatePort(Server.class, 1);
public static final String PORT_B = allocatePort(Server.class, 2);
public static final String PORT_C = allocatePort(Server.class, 3);
public static final String PORT_D = allocatePort(Server.class, 4);
public static final String PORT_E = allocatePort(Server.class, 5);
protected static final String REPLICA_A =
"http://localhost:" + PORT_A + "/SoapContext/ReplicatedPortA";
protected static final String REPLICA_B =
"http://localhost:" + PORT_B + "/SoapContext/ReplicatedPortB";
protected static final String REPLICA_C =
"http://localhost:" + PORT_C + "/SoapContext/ReplicatedPortC";
protected static final String REPLICA_D =
"http://localhost:" + PORT_D + "/SoapContext/ReplicatedPortD";
protected static final String REPLICA_E =
"http://localhost:" + PORT_E + "/SoapContext/ReplicatedPortE";
private static final Logger LOG =
LogUtils.getLogger(FailoverTest.class);
private static final String FAILOVER_CONFIG =
"org/apache/cxf/systest/clustering/failover.xml";
private static String wsdlLocation = ClusteredGreeterService.WSDL_LOCATION.toString();
protected Bus bus;
protected Greeter greeter;
protected List<String> targets;
protected Control control;
private MAPAggregator mapAggregator;
private MAPCodec mapCodec;
@BeforeClass
public static void startServers() throws Exception {
assertTrue("server did not launch correctly",
launchServer(Server.class));
}
protected String getConfig() {
return FAILOVER_CONFIG;
}
@Before
public void setUp() {
targets = new ArrayList<>();
SpringBusFactory bf = new SpringBusFactory();
bus = bf.createBus(getConfig());
BusFactory.setDefaultBus(bus);
updateWsdlExtensors("9051", PORT_A);
updateWsdlExtensors("9052", PORT_B);
updateWsdlExtensors("9053", PORT_C);
updateWsdlExtensors("9055", PORT_E);
}
@After
public void tearDown() {
if (null != control) {
for (String address : targets) {
assertTrue("Failed to stop greeter",
control.stopGreeter(address));
}
}
targets = null;
if (bus != null) {
bus.shutdown(true);
bus = null;
}
}
@Test
public void testNoFailoverAcrossBindings() throws Exception {
startTarget(REPLICA_D);
setupGreeter();
try {
greeter.greetMe("fred");
fail("expected exception");
} catch (Exception e) {
verifyCurrentEndpoint(REPLICA_A);
}
}
@Test
public void testRevertExceptionOnUnsucessfulFailover() throws Exception {
startTarget(REPLICA_B);
startTarget(REPLICA_C);
setupGreeter();
stopTarget(REPLICA_C);
stopTarget(REPLICA_B);
try {
greeter.greetMe("fred");
fail("expected exception");
} catch (Exception e) {
Throwable cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
// failover attempt bails after retried invocations on
// started & stopped replicas B & C fail with HTTP 404
// indicated by a thrown IOException("Not found"),
// in which case we should revert back to the original
// java.net.ConnectionException on the unavailable
// replica A
boolean isOrig = cause instanceof ConnectException;
if (!isOrig) {
//depending on the order of the tests,
//the port COULD have been created, but no service deployed
isOrig = cause instanceof HTTPException
&& cause.getMessage().contains("SoapContext/ReplicatedPortA")
&& cause.getMessage().contains("404:");
}
if (!isOrig) {
cause.printStackTrace();
}
assertTrue("should revert to original exception when no failover: "
+ cause,
isOrig);
// similarly the current endpoint referenced by the client
// should also revert back to the original replica A
//
verifyCurrentEndpoint(REPLICA_A);
}
}
@Test
public void testInitialFailoverOnPrimaryReplicaUnavailable() throws Exception {
startTarget(REPLICA_C);
setupGreeter();
String response = null;
response = greeter.greetMe("fred");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
response = greeter.greetMe("joe");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
}
@Test
public void testNoFailoverOnApplicationFault() throws Exception {
startTarget(REPLICA_C);
setupGreeter();
greeter.pingMe();
verifyCurrentEndpoint(REPLICA_C);
startTarget(REPLICA_B);
try {
greeter.pingMe();
} catch (PingMeFault pmf) {
verifyCurrentEndpoint(REPLICA_C);
}
}
@Test
public void testFailoverOnCurrentReplicaDeath() throws Exception {
startTarget(REPLICA_C);
setupGreeter();
String response = null;
response = greeter.greetMe("fred");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
startTarget(REPLICA_B);
stopTarget(REPLICA_C);
response = greeter.greetMe("joe");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_B));
verifyCurrentEndpoint(REPLICA_B);
}
@Test
public void testNoFailbackWhileCurrentReplicaLive() throws Exception {
startTarget(REPLICA_C);
setupGreeter();
String response = null;
response = greeter.greetMe("fred");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
startTarget(REPLICA_A);
response = greeter.greetMe("joe");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
startTarget(REPLICA_B);
response = greeter.greetMe("bob");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
stopTarget(REPLICA_B);
response = greeter.greetMe("john");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
stopTarget(REPLICA_A);
response = greeter.greetMe("mike");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
verifyCurrentEndpoint(REPLICA_C);
}
@Test
public void testEndpointSpecificInterceptorsDoNotPersistAcrossFailover()
throws Exception {
startTarget(REPLICA_A);
setupGreeter();
String response = null;
enableWSAForCurrentEndpoint();
response = greeter.greetMe("fred");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_A));
assertTrue("response expected to include WS-A messageID",
response.indexOf("message: urn:uuid") != -1);
verifyCurrentEndpoint(REPLICA_A);
assertTrue("expected WSA enabled for current endpoint",
isWSAEnabledForCurrentEndpoint());
stopTarget(REPLICA_A);
startTarget(REPLICA_C);
response = greeter.greetMe("mike");
assertNotNull("expected non-null response", response);
assertTrue("response from unexpected target: " + response,
response.endsWith(REPLICA_C));
assertTrue("response not expected to include WS-A messageID",
response.indexOf("message: urn:uuid") == -1);
verifyCurrentEndpoint(REPLICA_C);
assertFalse("unexpected WSA enabled for current endpoint",
isWSAEnabledForCurrentEndpoint());
}
@Test
public void testDefaultSequentialStrategy() throws Exception {
strategyTest(REPLICA_B, REPLICA_C, REPLICA_A, false);
}
@Test
public void testExplicitSequentialStrategy() throws Exception {
strategyTest(REPLICA_A, REPLICA_C, REPLICA_B, false);
}
@Test
public void testRandomStrategy() throws Exception {
strategyTest(REPLICA_A, REPLICA_B, REPLICA_C, true);
}
protected Greeter getGreeter(String type) throws Exception {
if (REPLICA_A.equals(type)) {
Greeter g = new ClusteredGreeterService().getReplicatedPortA();
updateAddressPort(g, PORT_A);
updateWsdlExtensors("9051", PORT_A);
return g;
} else if (REPLICA_B.equals(type)) {
Greeter g = new ClusteredGreeterService().getReplicatedPortB();
updateAddressPort(g, PORT_B);
updateWsdlExtensors("9052", PORT_B);
return g;
} else if (REPLICA_C.equals(type)) {
Greeter g = new ClusteredGreeterService().getReplicatedPortC();
updateAddressPort(g, PORT_C);
updateWsdlExtensors("9053", PORT_C);
return g;
}
Greeter g = new ClusteredGreeterService().getReplicatedPortE();
updateAddressPort(g, PORT_E);
updateWsdlExtensors("9055", PORT_E);
return g;
}
protected void strategyTest(String activeReplica1,
String activeReplica2,
String inactiveReplica,
boolean expectRandom) throws Exception {
startTarget(activeReplica1);
startTarget(activeReplica2);
boolean randomized = false;
String prevEndpoint = null;
for (int i = 0; i < 20; i++) {
Greeter g = getGreeter(inactiveReplica);
verifyStrategy(g, expectRandom
? RandomStrategy.class
: SequentialStrategy.class);
String response = g.greetMe("fred");
assertNotNull("expected non-null response", response);
String currEndpoint = getCurrentEndpoint(g);
if (!(prevEndpoint == null || currEndpoint.equals(prevEndpoint))) {
randomized = true;
}
prevEndpoint = currEndpoint;
}
stopTarget(activeReplica1);
stopTarget(activeReplica2);
assertEquals("unexpected random/sequential distribution of failovers",
expectRandom,
randomized);
}
protected void startTarget(String address) throws Exception {
ControlService cs = new ControlService();
control = cs.getControlPort();
updateAddressPort(control, PORT_0);
LOG.info("starting replicated target: " + address);
assertTrue("Failed to start greeter", control.startGreeter(address));
targets.add(address);
}
protected void stopTarget(String address) {
if (control != null
&& targets.contains(address)) {
LOG.info("starting replicated target: " + address);
assertTrue("Failed to start greeter", control.stopGreeter(address));
targets.remove(address);
}
}
protected void verifyCurrentEndpoint(String replica) {
assertEquals("unexpected current endpoint",
replica,
getCurrentEndpoint(greeter));
}
protected String getCurrentEndpoint(Object proxy) {
return ClientProxy.getClient(proxy).getEndpoint().getEndpointInfo().getAddress();
}
protected void setupGreeter() throws Exception {
ClusteredGreeterService cs = new ClusteredGreeterService();
// REVISIT: why doesn't the generic (i.e. non-Port-specific)
// Service.getPort() load the <jaxws:client> configuration?
greeter = cs.getReplicatedPortA();
updateAddressPort(greeter, PORT_A);
assertTrue("unexpected conduit selector: "
+ ClientProxy.getClient(greeter).getConduitSelector().getClass().getName(),
ClientProxy.getClient(greeter).getConduitSelector()
instanceof FailoverTargetSelector);
updateWsdlExtensors("9051", PORT_A);
updateWsdlExtensors("9052", PORT_B);
updateWsdlExtensors("9053", PORT_C);
updateWsdlExtensors("9055", PORT_E);
}
protected void verifyStrategy(Object proxy, Class<?> clz) {
ConduitSelector conduitSelector =
ClientProxy.getClient(proxy).getConduitSelector();
if (conduitSelector instanceof FailoverTargetSelector) {
Object strategy =
((FailoverTargetSelector)conduitSelector).getStrategy();
assertTrue("unexpected strategy", clz.isInstance(strategy));
} else {
fail("unexpected conduit selector: " + conduitSelector);
}
}
protected void enableWSAForCurrentEndpoint() {
Endpoint provider = ClientProxy.getClient(greeter).getEndpoint();
mapAggregator = new MAPAggregator();
mapCodec = new MAPCodec();
provider.getInInterceptors().add(mapAggregator);
provider.getInInterceptors().add(mapCodec);
provider.getOutInterceptors().add(mapAggregator);
provider.getOutInterceptors().add(mapCodec);
provider.getInFaultInterceptors().add(mapAggregator);
provider.getInFaultInterceptors().add(mapCodec);
provider.getOutFaultInterceptors().add(mapAggregator);
provider.getOutFaultInterceptors().add(mapCodec);
}
protected boolean isWSAEnabledForCurrentEndpoint() {
Endpoint provider = ClientProxy.getClient(greeter).getEndpoint();
boolean enabledIn =
provider.getInInterceptors().contains(mapAggregator)
&& provider.getInInterceptors().contains(mapCodec)
&& provider.getInFaultInterceptors().contains(mapAggregator)
&& provider.getInFaultInterceptors().contains(mapCodec);
boolean enabledOut =
provider.getOutInterceptors().contains(mapAggregator)
&& provider.getOutInterceptors().contains(mapCodec)
&& provider.getOutFaultInterceptors().contains(mapAggregator)
&& provider.getOutFaultInterceptors().contains(mapCodec);
return enabledIn && enabledOut;
}
/**
* Exchange the port number in all service addresses on the bus.
* @param port1 current port
* @param port2 new port
*/
private void updateWsdlExtensors(String port1, String port2) {
try {
Definition def = bus.getExtension(WSDLManager.class)
.getDefinition(wsdlLocation);
Map<?, ?> map = def.getAllServices();
for (Object o : map.values()) {
Service service = (Service)o;
Map<?, ?> ports = service.getPorts();
for (Object p : ports.values()) {
Port port = (Port)p;
List<?> l = port.getExtensibilityElements();
for (Object e : l) {
if (e instanceof SOAPAddress) {
String add = ((SOAPAddress)e).getLocationURI();
int idx = add.indexOf(":" + port1);
if (idx != -1) {
add = add.substring(0, idx) + ":" + port2
+ add.substring(idx + port1.length() + 1);
((SOAPAddress)e).setLocationURI(add);
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}