package org.jgroups.tests;
import org.jgroups.Event;
import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.blocks.ReplicatedHashMap;
import org.jgroups.blocks.atomic.Counter;
import org.jgroups.blocks.atomic.CounterService;
import org.jgroups.fork.ForkChannel;
import org.jgroups.fork.ForkProtocolStack;
import org.jgroups.protocols.COUNTER;
import org.jgroups.protocols.FORK;
import org.jgroups.protocols.FRAG2;
import org.jgroups.protocols.pbcast.STATE;
import org.jgroups.stack.Protocol;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.MyReceiver;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.List;
/**
* Tests {@link org.jgroups.fork.ForkChannel}
* @author Bela Ban
* @since 3.4
*/
@Test(groups=Global.FUNCTIONAL,singleThreaded=true)
public class ForkChannelTest {
protected JChannel a, b;
protected ForkChannel fc1, fc2, fc3, fc4;
protected static final String CLUSTER="ForkChannelTest";
protected Protocol[] protocols() {return Util.getTestStack(new STATE(), new FORK());}
@BeforeMethod protected void setup() throws Exception {
a=new JChannel(protocols()).name("A");
}
@AfterMethod protected void destroy() {
Util.close(fc4, fc3, fc2, fc1, a, b);
}
@Test
public void testCreateForkIfAbsent() throws Exception {
JChannel c = new JChannel(Util.getTestStack(new STATE())).name("C");
ForkChannel fc = new ForkChannel(c,
"hijack-stack",
"lead-hijacker",
true,
ProtocolStack.Position.ABOVE,
FRAG2.class);
assert fc.isOpen() && !fc.isConnected() && !fc.isClosed() : "state=" + fc.getState();
Util.close(fc, c);
}
public void testLifecycle() throws Exception {
fc1=new ForkChannel(a, "stack", "fc1");
assert fc1.isOpen() && !fc1.isConnected() && !fc1.isClosed() : "state=" + fc1.getState();
a.connect(CLUSTER);
assert fc1.isOpen() && !fc1.isConnected() && !fc1.isClosed() : "state=" + fc1.getState();
fc1.connect("bla");
assert fc1.isOpen() && fc1.isConnected() && !fc1.isClosed() : "state=" + fc1.getState();
assert a.getAddress().equals(fc1.getAddress());
assert a.getClusterName().equals(fc1.getClusterName());
assert a.getView().equals(fc1.getView());
fc1.disconnect();
assert fc1.isOpen() && !fc1.isConnected() && !fc1.isClosed() : "state=" + fc1.getState();
fc1.connect("foobar");
assert fc1.isOpen() && fc1.isConnected() && !fc1.isClosed() : "state=" + fc1.getState();
Util.close(fc1);
assert !fc1.isOpen() && !fc1.isConnected() && fc1.isClosed() : "state=" + fc1.getState();
try {
fc1.connect("whocares");
assert false : "a closed fork channel cannot be reconnected";
}
catch(Exception ex) {
assert ex instanceof IllegalStateException;
}
assert !fc1.isOpen() && !fc1.isConnected() && fc1.isClosed() : "state=" + fc1.getState();
Util.close(a);
assert !fc1.isOpen() && !fc1.isConnected() && fc1.isClosed() : "state=" + fc1.getState();
try {
fc1.send(null, "hello");
assert false: "sending on a fork-channel with a disconnected main-channel should throw an exception";
}
catch(Throwable t) {
System.out.println("got an exception (as expected) sending on a fork-channel where the main-channel is disconnected: " + t);
}
}
public void testIncorrectConnectSequence() throws Exception {
fc1=new ForkChannel(a, "stack", "fc1");
try {
fc1.connect(CLUSTER);
assert false : "Connecting a fork channel before the main channel should have thrown an exception";
}
catch(Exception ex) {
assert ex instanceof IllegalStateException : "expected IllegalStateException but got " + ex;
}
}
public void testRefcount() throws Exception {
FORK fork=a.getProtocolStack().findProtocol(FORK.class);
Protocol prot=fork.get("stack");
assert prot == null;
fc1=new ForkChannel(a, "stack", "fc1");
prot=fork.get("stack");
assert prot != null;
ForkProtocolStack fork_stack=(ForkProtocolStack)getProtStack(prot);
int inits=fork_stack.getInits();
assert inits == 1 : "inits is " + inits + "(expected 1)";
fc2=new ForkChannel(a, "stack", "fc2"); // uses the same fork stack "stack"
inits=fork_stack.getInits();
assert inits == 2 : "inits is " + inits + "(expected 2)";
a.connect(CLUSTER);
fc1.connect(CLUSTER);
int connects=fork_stack.getConnects();
assert connects == 1 : "connects is " + connects + "(expected 1)";
fc1.connect(CLUSTER); // duplicate connect()
connects=fork_stack.getConnects();
assert connects == 1 : "connects is " + connects + "(expected 1)";
fc2.connect(CLUSTER);
connects=fork_stack.getConnects();
assert connects == 2 : "connects is " + connects + "(expected 2)";
fc2.disconnect();
fc2.disconnect(); // duplicate disconnect() !
connects=fork_stack.getConnects();
assert connects == 1 : "connects is " + connects + "(expected 1)";
Util.close(fc2);
inits=fork_stack.getInits();
assert inits == 1 : "inits is " + inits + "(expected 1)";
Util.close(fc2); // duplicate close()
inits=fork_stack.getInits();
assert inits == 1 : "inits is " + inits + "(expected 1)";
Util.close(fc1);
connects=fork_stack.getConnects();
assert connects == 0 : "connects is " + connects + "(expected 0)";
inits=fork_stack.getInits();
assert inits == 0 : "inits is " + inits + "(expected 0)";
prot=fork.get("stack");
assert prot == null;
}
public void testRefcount2() throws Exception {
Prot p1=new Prot("P1"), p2=new Prot("P2");
fc1=new ForkChannel(a, "stack", "fc1", p1, p2);
fc2=new ForkChannel(a, "stack", "fc2"); // uses p1 and p2 from fc1
fc3=new ForkChannel(a, "stack", "fc3"); // uses p1 and p2 from fc1
assert p1.inits == 1 && p2.inits == 1;
FORK fork=a.getProtocolStack().findProtocol(FORK.class);
Protocol prot=fork.get("stack");
ForkProtocolStack fork_stack=(ForkProtocolStack)getProtStack(prot);
int inits=fork_stack.getInits();
assert inits == 3;
a.connect(CLUSTER);
fc1.connect(CLUSTER);
int connects=fork_stack.getConnects();
assert connects == 1;
assert p1.starts == 1 && p2.starts == 1;
fc2.connect(CLUSTER);
fc3.connect(CLUSTER);
connects=fork_stack.getConnects();
assert connects == 3;
assert p1.starts == 1 && p2.starts == 1;
fc3.disconnect();
fc2.disconnect();
assert p1.starts == 1 && p2.starts == 1;
assert p1.stops == 0 && p2.stops == 0;
fc1.disconnect();
assert p1.starts == 1 && p2.starts == 1;
assert p1.stops == 1 && p2.stops == 1;
Util.close(fc3,fc2);
assert p1.destroys == 0 && p2.destroys == 0;
Util.close(fc1);
assert p1.destroys == 1 && p2.destroys == 1;
}
public void testIncorrectLifecycle() throws Exception {
fc1=new ForkChannel(a, "stack", "fc1");
a.connect(CLUSTER);
fc1.connect(CLUSTER);
Util.close(fc1);
try {
fc1.connect(CLUSTER);
assert false : "reconnecting a closed fork channel must throw an exception";
}
catch(Exception ex) {
assert ex instanceof IllegalStateException;
System.out.println("got exception as expected: " + ex);
}
}
/** Tests the case where we don't add any fork-stack specific protocols */
public void testNullForkStack() throws Exception {
fc1=new ForkChannel(a, "stack", "fc1");
fc2=new ForkChannel(a, "stack", "fc2");
MyReceiver<Integer> r1=new MyReceiver<>(), r2=new MyReceiver<>();
fc1.setReceiver(r1); fc2.setReceiver(r2);
a.connect(CLUSTER);
fc1.connect("foo");
fc2.connect("bar");
for(int i=1; i <= 5; i++) {
fc1.send(null, i);
fc2.send(null, i+5);
}
List<Integer> l1=r1.list(), l2=r2.list();
for(int i=0; i < 20; i++) {
if(l1.size() == 5 && l2.size() == 5)
break;
Util.sleep(500);
}
System.out.println("r1: " + r1.list() + ", r2: " + r2.list());
assert r1.size() == 5 && r2.size() == 5;
for(int i=1; i <= 5; i++)
assert r1.list().contains(i) && r2.list().contains(i+5);
}
/**
* Tests CounterService on 2 different fork-channels, using the *same* fork-stack. This means the 2 counter
* services will 'see' each other and the counters must have the same value
* @throws Exception
*/
public void testCounterService() throws Exception {
a.connect(CLUSTER);
fc1=new ForkChannel(a, "stack", "fc1", false,ProtocolStack.Position.ABOVE, FORK.class, new COUNTER());
fc2=new ForkChannel(a, "stack", "fc2", false,ProtocolStack.Position.ABOVE, FORK.class, new COUNTER());
fc1.connect("foo");
fc2.connect("bar");
CounterService cs1=new CounterService(fc1), cs2=new CounterService(fc2);
Counter c1=cs1.getOrCreateCounter("counter", 1), c2=cs2.getOrCreateCounter("counter", 1);
System.out.println("counter1=" + c1 + ", counter2=" + c2);
assert c1.get() == 1 && c2.get() == 1;
c1.addAndGet(5);
System.out.println("counter1=" + c1 + ", counter2=" + c2);
assert c1.get() == 6 && c2.get() == 6;
c2.compareAndSet(6, 10);
System.out.println("counter1=" + c1 + ", counter2=" + c2);
assert c1.get() == 10 && c2.get() == 10;
}
public void testStateTransfer() throws Exception {
ReplicatedHashMap<String,Integer> rhm_a=new ReplicatedHashMap<>(a),
rhm_b, rhm_fc1, rhm_fc2, rhm_fc3, rhm_fc4;
a.connect("state-transfer");
fc1=createForkChannel(a, "stack1", "fc1");
rhm_fc1=new ReplicatedHashMap<>(fc1);
fc2=createForkChannel(a, "stack2", "fc2");
rhm_fc2=new ReplicatedHashMap<>(fc2);
addData(rhm_a, rhm_fc1, rhm_fc2);
b=new JChannel(protocols()).name("B");
rhm_b=new ReplicatedHashMap<>(b);
b.connect("state-transfer");
fc3=createForkChannel(b, "stack1", "fc1");
rhm_fc3=new ReplicatedHashMap<>(fc3);
fc4=createForkChannel(b, "stack2", "fc2");
rhm_fc4=new ReplicatedHashMap<>(fc4);
Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b);
b.getState(null, 10000);
for(int i=0; i < 10; i++) {
if(rhm_b.size() == rhm_a.size() && rhm_fc1.size() == rhm_fc3.size() && rhm_fc2.size() == rhm_fc4.size())
break;
Util.sleep(1000);
}
System.out.printf("rhm_a: %s, rhm_b: %s\nrhm_fc1: %s, rhm_fc3: %s\nrhm_fc2: %s, rhm_fc4: %s\n",
rhm_a, rhm_b, rhm_fc1, rhm_fc3, rhm_fc2, rhm_fc4);
assert rhm_a.equals(rhm_b);
assert rhm_fc1.equals(rhm_fc3);
assert rhm_fc2.equals(rhm_fc4);
}
protected ForkChannel createForkChannel(JChannel main, String stack_name, String ch_name) throws Exception {
ForkChannel fork_ch=new ForkChannel(main, stack_name, ch_name);
fork_ch.connect(ch_name);
return fork_ch;
}
protected void addData(ReplicatedHashMap<String,Integer> a, ReplicatedHashMap<String,Integer> b, ReplicatedHashMap<String,Integer> c) {
if(a != null) {
a.put("id", 322649);
a.put("version", 45);
}
if(b != null) {
b.put("major", 3);
b.put("minor", 6);
b.put("patch", 5);
}
if(c != null) {
c.put("hobbies", 3);
c.put("kids", 2);
}
}
protected static ProtocolStack getProtStack(Protocol prot) {
while(prot != null && !(prot instanceof ProtocolStack)) {
prot=prot.getUpProtocol();
}
return prot instanceof ProtocolStack? (ProtocolStack)prot : null;
}
/* protected Entry[] create(boolean main_ch, boolean fc1, boolean fc2) {
Entry[] entry=new Entry[3];
return entry;
}
protected static class Entry {
protected final JChannel ch;
protected final ReplicatedHashMap<String,Integer> map;
public Entry(JChannel ch, ReplicatedHashMap<String,Integer> map) {
this.ch=ch;
this.map=map;
}
}*/
protected static class Prot extends Protocol {
protected final String myname;
protected int inits, starts, stops, destroys;
public Prot(String name) {
this.myname=name;
}
public void init() throws Exception {
super.init();
System.out.println(myname + ".init()");
inits++;
}
public void start() throws Exception {
super.start();
System.out.println(myname + ".start()");
starts++;
}
public void stop() {
super.stop();
System.out.println(myname + ".stop()");
stops++;
}
public void destroy() {
super.destroy();
System.out.println(myname + ".destroy()");
destroys++;
}
public Object down(Event evt) {
System.out.println(myname + ": down(): " + evt);
return down_prot.down(evt);
}
public Object down(Message msg) {
System.out.println(myname + ": down(): " + msg);
return down_prot.down(msg);
}
public String toString() {
return myname;
}
}
}