package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.protocols.tom.TOA;
import org.jgroups.stack.Protocol;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
/**
* Some tests for TOA protocol.
*
* @author Pedro Ruivo
* @since 3.5
*/
@Test(groups = Global.FUNCTIONAL, singleThreaded = true)
public class TOATest {
private final List<TOANode> registeredToaNodes = new ArrayList<TOANode>(4);
@AfterMethod(alwaysRun = true)
public void closeAndDisconnect() {
for (TOANode toaNode : registeredToaNodes) {
toaNode.stop();
}
registeredToaNodes.clear();
}
public void testSingleDestinationSingleMember() throws Exception {
doSingleDestinationTest(false);
}
public void testTotalOrderSingleDestinationSingleMember() throws Exception {
doSingleDestinationTest(true);
}
public void testSingleDestinationWithMultipleMembers() throws Exception {
doSingleDestinationWithMultipleMembersTest(false, 4);
}
public void testTotalOrderSingleDestinationWithMultipleMembers() throws Exception {
doSingleDestinationWithMultipleMembersTest(true, 4);
}
public void testTotalOrder2() throws Exception {
doTotalOrderTest(2);
}
public void testTotalOrder3() throws Exception {
doTotalOrderTest(3);
}
public void testTotalOrder4() throws Exception {
doTotalOrderTest(4);
}
public void testTotalOrder5() throws Exception {
doTotalOrderTest(5);
}
public void testTotalOrderWithSingleDestination2() throws Exception {
doTotalOrderTestWithSingleDestinationMessages(2);
}
public void testTotalOrderWithSingleDestination3() throws Exception {
doTotalOrderTestWithSingleDestinationMessages(3);
}
public void testTotalOrderWithSingleDestination4() throws Exception {
doTotalOrderTestWithSingleDestinationMessages(4);
}
public void testTotalOrderWithSingleDestination5() throws Exception {
doTotalOrderTestWithSingleDestinationMessages(5);
}
private void doTotalOrderTest(int numberOfMembers) throws Exception {
final int maxMessages = 1024;
final int totalMessages = maxMessages * numberOfMembers;
for (int i = 0; i < numberOfMembers; ++i) {
TOANode node = createNewChannel("to_test_" + i);
node.start("to_test_cluster_" + numberOfMembers);
node.setEstimatedMessages(totalMessages);
}
for (TOANode member : registeredToaNodes) {
member.waitAllMembers(registeredToaNodes, 30, TimeUnit.SECONDS);
}
final ExecutorService executor = Executors.newFixedThreadPool(numberOfMembers);
final CyclicBarrier barrier = new CyclicBarrier(numberOfMembers);
final CountDownLatch shutdown = new CountDownLatch(numberOfMembers);
for (TOANode node : registeredToaNodes) {
executor.execute(new TOANodeRunnable(node) {
@Override
public void run() {
final String address = toaNode.channel.getAddressAsString();
try {
barrier.await();
for (int i = 0; i < maxMessages; ++i) {
toaNode.sendTotalOrderAnycastMessage(new AnycastAddress(), "to-message-" + address + "-" + i);
}
shutdown.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
shutdown.await();
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
assertTrue(executor.shutdownNow().isEmpty());
for (TOANode node : registeredToaNodes) {
node.waitMessages(totalMessages, 30, TimeUnit.SECONDS);
}
for (int i = 0; i < totalMessages; ++i) {
Iterator<TOANode> iterator = registeredToaNodes.iterator();
String message = iterator.next().pop();
while (iterator.hasNext()) {
TOANode node = iterator.next();
assertEquals("wrong message order #" + i + " for " + node.channel.getAddress(),
message, node.pop());
}
}
}
private void doTotalOrderTestWithSingleDestinationMessages(final int numberOfMembers) throws Exception {
final int maxMessages = 1024;
//one member will only send single destination messages
final int totalMessages = maxMessages * (numberOfMembers -1);
for (int i = 0; i < numberOfMembers; ++i) {
TOANode node = createNewChannel("to_test_single_" + i);
node.start("to_test_single_cluster_" + numberOfMembers);
node.setEstimatedMessages(totalMessages);
}
for (TOANode member : registeredToaNodes) {
member.waitAllMembers(registeredToaNodes, 30, TimeUnit.SECONDS);
}
final ExecutorService executor = Executors.newFixedThreadPool(numberOfMembers);
final CyclicBarrier barrier = new CyclicBarrier(numberOfMembers);
final CountDownLatch shutdown = new CountDownLatch(numberOfMembers);
final Map<Address, AtomicInteger> perAddressCounter = new ConcurrentHashMap<Address, AtomicInteger>();
boolean first = true;
for (TOANode node : registeredToaNodes) {
if (first) {
first = false;
executor.execute(new TOANodeRunnable(node) {
@Override
public void run() {
final String address = toaNode.channel.getAddressAsString();
final List<Address> addressList = toaNode.currentView.getMembers();
try {
barrier.await();
for (int i = 0; i < maxMessages; ++i) {
Address destination = addressList.get(i % numberOfMembers);
AtomicInteger counter = perAddressCounter.get(destination);
if (counter != null) {
counter.incrementAndGet();
} else {
perAddressCounter.put(destination, new AtomicInteger(1));
}
toaNode.sendTotalOrderAnycastMessage(new AnycastAddress(destination),
"single-message-" + address + "-" + i);
}
shutdown.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
});
} else {
executor.execute(new TOANodeRunnable(node) {
@Override
public void run() {
final String address = toaNode.channel.getAddressAsString();
try {
barrier.await();
for (int i = 0; i < maxMessages; ++i) {
toaNode.sendTotalOrderAnycastMessage(new AnycastAddress(), "to-message-" + address + "-" + i);
}
shutdown.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
shutdown.await();
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
assertTrue(executor.shutdownNow().isEmpty());
for (TOANode node : registeredToaNodes) {
AtomicInteger singleMessages = perAddressCounter.get(node.localAddress());
node.waitMessages(totalMessages + (singleMessages == null ? 0 : singleMessages.get()), 30, TimeUnit.SECONDS);
}
Map<Address, AtomicInteger> perAddressCounter2 = new ConcurrentHashMap<Address, AtomicInteger>();
for (int i = 0; i < totalMessages; ++i) {
Iterator<TOANode> iterator = registeredToaNodes.iterator();
TOANode firstNode = iterator.next();
AtomicInteger counter = perAddressCounter2.get(firstNode.localAddress());
if (counter == null) {
counter = new AtomicInteger(0);
perAddressCounter2.put(firstNode.localAddress(), counter);
}
String message = nextMessage(firstNode, counter, "to-message-");
while (iterator.hasNext()) {
TOANode node = iterator.next();
counter = perAddressCounter2.get(node.localAddress());
if (counter == null) {
counter = new AtomicInteger(0);
perAddressCounter2.put(node.localAddress(), counter);
}
String message2 = nextMessage(node, counter, "to-message-");
assertEquals("wrong message order #" + i + " for " + node.channel.getAddress(),
message, message2);
}
}
for (TOANode node : registeredToaNodes) {
if (node.size() > 0) {
AtomicInteger counter = perAddressCounter2.get(node.localAddress());
if (counter == null) {
counter = new AtomicInteger(0);
perAddressCounter2.put(node.localAddress(), counter);
}
while (node.size() > 0) {
assertTrue(node.pop().startsWith("single-message-"));
counter.incrementAndGet();
}
}
}
assertEquals(perAddressCounter.size(), perAddressCounter2.size());
for (Map.Entry<Address, AtomicInteger> entry : perAddressCounter.entrySet()) {
final Address key = entry.getKey();
final AtomicInteger value = entry.getValue();
assertTrue("Error in single messages in node " + key, perAddressCounter2.containsKey(key));
assertEquals("Error in number of single messages in node " + key, value.get(), perAddressCounter2.get(key).get());
}
}
private void doSingleDestinationTest(boolean totalOrder) throws Exception {
final int maxMessages = 1024;
TOANode singleMember = createNewChannel("toa_single_member_" + totalOrder);
singleMember.start("toa_single_member_cluster_" + totalOrder);
singleMember.waitForMembersInView(30, TimeUnit.SECONDS, singleMember.localAddress());
singleMember.setEstimatedMessages(maxMessages);
for (int i = 0; i < maxMessages; ++i) {
if (totalOrder) {
singleMember.sendTotalOrderAnycastMessage(new AnycastAddress(), Integer.toString(i));
} else {
singleMember.sendAnycastMessage(new AnycastAddress(), Integer.toString(i));
}
}
//wait until all is delivered
singleMember.waitMessages(maxMessages, 30, TimeUnit.SECONDS);
for (int i = 0; i < maxMessages; ++i) {
AssertJUnit.assertEquals("Wrong message.", Integer.toString(i), singleMember.pop());
}
}
private void doSingleDestinationWithMultipleMembersTest(boolean totalOrder, int members) throws Exception {
final int maxMessages = 1024;
for (int i = 0; i < members; ++i) {
TOANode member = createNewChannel("toa_multiple_member_" + totalOrder + "_" + i);
member.start("toa_multiple_member_cluster_" + totalOrder);
member.setEstimatedMessages(maxMessages);
}
for (TOANode member : registeredToaNodes) {
member.waitAllMembers(registeredToaNodes, 30, TimeUnit.SECONDS);
}
AssertJUnit.assertTrue("Expected more than one node. value=" + registeredToaNodes.size(),
registeredToaNodes.size() > 1);
final TOANode sender = registeredToaNodes.get(0);
for (TOANode destination : registeredToaNodes) {
for (int i = 0; i < maxMessages; ++i) {
if (totalOrder) {
sender.sendTotalOrderAnycastMessage(new AnycastAddress(destination.localAddress()), Integer.toString(i));
} else {
sender.sendAnycastMessage(new AnycastAddress(destination.localAddress()), Integer.toString(i));
}
}
}
for (TOANode destination : registeredToaNodes) {
//wait until all is delivered
destination.waitMessages(maxMessages, 30, TimeUnit.SECONDS);
for (int i = 0; i < maxMessages; ++i) {
AssertJUnit.assertEquals("Wrong message in member " + destination.channel.getAddress(),
Integer.toString(i), destination.pop());
}
}
}
private TOANode createNewChannel(String name) throws Exception {
JChannel channel = new JChannel(new Protocol[]{
new SHARED_LOOPBACK(),
new SHARED_LOOPBACK_PING(),
new MERGE3(),
new NAKACK2(),
new UNICAST3(),
new TOA(),
new GMS()
}).name(name);
TOANode toaNode = new TOANode(channel);
registeredToaNodes.add(toaNode);
return toaNode;
}
private static class TOANodeRunnable implements Runnable {
protected final TOANode toaNode;
public TOANodeRunnable(TOANode toaNode) {
this.toaNode = toaNode;
}
@Override
public void run() {
}
}
private static class TOANode extends ReceiverAdapter {
private final Object viewLock = new Object();
private final JChannel channel;
private volatile View currentView;
private final ArrayList<String> messages;
private TOANode(JChannel channel) {
if (channel == null) {
throw new NullPointerException();
}
this.channel = channel;
this.channel.setReceiver(this);
this.messages = new ArrayList<String>();
}
public void start(String clusterName) throws Exception {
channel.connect(clusterName);
}
public void stop() {
channel.disconnect();
channel.close();
}
public void setEstimatedMessages(int estimatedMessages) {
messages.ensureCapacity(estimatedMessages);
}
public Address localAddress() {
return channel.getAddress();
}
public String pop() {
synchronized (messages) {
return messages.remove(0);
}
}
public int size() {
synchronized (messages) {
return messages.size();
}
}
public void waitForMembersInView(int timeout, TimeUnit timeUnit, Address... members) throws InterruptedException {
int loops = 0;
final int maxLoops = (int) ((timeUnit.toSeconds(timeout) / 2) + 1);
final Collection<Address> memberCollection = Arrays.asList(members);
synchronized (viewLock) {
while (!currentView.getMembers().containsAll(memberCollection) && loops++ < maxLoops) {
viewLock.wait(TimeUnit.SECONDS.toMillis(2));
}
}
assertTrue("Members is missing: view=" + currentView + ", expected members=" + memberCollection,
currentView.getMembers().containsAll(memberCollection));
}
public void waitAllMembers(Collection<TOANode> members, int timeout, TimeUnit timeUnit) throws InterruptedException {
List<Address> addresses = new ArrayList<Address>(members.size());
for (TOANode member : members) {
addresses.add(member.localAddress());
}
waitForMembersInView(timeout, timeUnit, addresses.toArray(new Address[addresses.size()]));
}
public void waitMessages(int size, long timeout, TimeUnit timeUnit) throws InterruptedException {
int loops = 0;
final int maxLoops = (int) (timeUnit.toSeconds(timeout) / 2) + 1;
synchronized (messages) {
while (messages.size() < size && loops++ < maxLoops) {
messages.wait(TimeUnit.SECONDS.toMillis(2));
}
}
AssertJUnit.assertEquals("Wrong number of messages received.", size, messages.size());
}
public void sendAnycastMessage(AnycastAddress anycastAddress, String data) throws Exception {
Message msg = new Message();
msg.setSrc(channel.getAddress());
msg.setDest(anycastAddress);
msg.setFlag(Message.Flag.NO_TOTAL_ORDER);
msg.setObject(data);
channel.send(msg);
}
public void sendTotalOrderAnycastMessage(AnycastAddress anycastAddress, String data) throws Exception {
Message msg = new Message();
msg.setSrc(channel.getAddress());
msg.setDest(anycastAddress);
msg.setObject(data);
channel.send(msg);
}
@Override
public void receive(Message msg) {
synchronized (messages) {
messages.add(String.valueOf(msg.getObject()));
}
}
@Override
public void viewAccepted(View view) {
synchronized (viewLock) {
this.currentView = view;
}
}
}
private static String nextMessage(TOANode node, AtomicInteger missCounter, String prefix) {
String message = node.pop();
while (!message.startsWith(prefix)) {
missCounter.incrementAndGet();
message = node.pop();
}
return message;
}
}