package org.jgroups.tests;
import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.View;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.protocols.FRAG2;
import org.jgroups.protocols.FRAG3;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.JoinRsp;
import org.jgroups.protocols.pbcast.ParticipantGmsImpl;
import org.jgroups.protocols.pbcast.STABLE;
import org.jgroups.stack.Protocol;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.Digest;
import org.jgroups.util.MessageBatch;
import org.jgroups.util.Tuple;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.List;
/**
* Tests DeltaViews (https://issues.jboss.org/browse/JGRP-2159)
* @author Bela Ban
* @since 4.0.1
*/
@Test(groups=Global.FUNCTIONAL,singleThreaded=true)
public class DeltaViewTest {
protected JChannel j, k, l;
protected static final String CLUSTER=DeltaViewTest.class.getSimpleName();
protected static final short GMS_ID=ClassConfigurator.getProtocolId(GMS.class);
@BeforeMethod protected void setup() throws Exception {
j=create("J");
k=create("K");
l=create("L");
}
@AfterMethod protected void destroy() {Util.close(l, k, j);}
public void testDeltaViews() throws Exception {
DelayViewsAndJoinRsps del=new DelayViewsAndJoinRsps(j);
j.connect(CLUSTER);
j.getProtocolStack().insertProtocol(del, ProtocolStack.Position.BELOW, GMS.class);
k.connect(CLUSTER);
Util.waitUntilAllChannelsHaveSameView(5000, 500, j,k);
System.out.printf("\nJ: %s\nK: %s\n\n", j.getView(), k.getView());
l.connect(CLUSTER);
Util.waitUntilAllChannelsHaveSameView(10000, 1000, j, k, l);
System.out.printf("\nJ: %s\nK: %s\nL: %s\n", j.getView(), k.getView(), l.getView());
}
protected JChannel create(String name) throws Exception {
JChannel ch=new JChannel(Util.getTestStack()).name(name);
ch.getProtocolStack().removeProtocol(STABLE.class, FRAG2.class, FRAG3.class);
GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
gms.setViewAckCollectionTimeout(1000);
gms.setValue("join_timeout", 1500);
//.setValue("install_view_locally_first", false); // setting this to true should fix the issue!
return ch;
}
// up first view: queue
// down JOIN-RSP: queue
// up second view: queue
// down JOIN-RSP:
// ==> send first and second JOIN-RSP
// ==> deliver first and second view (second should fail)
// ==> remove this protocol
protected class DelayViewsAndJoinRsps extends Protocol {
protected final List<Message> views=new ArrayList<>(2); // views to be delivered
protected final List<Message> join_rsps=new ArrayList<>(2); // JOIN-RSPS to be sent to K
protected final JChannel ch;
protected boolean removed;
public DelayViewsAndJoinRsps(JChannel ch) {
this.ch=ch;
}
public Object down(Message msg) {
if(isJoinRsp(msg)) {
checkDone(msg, join_rsps);
return null;
}
return down_prot.down(msg);
}
public Object up(Message msg) {
if(isView(msg)) {
checkDone(msg, views);
return null;
}
return up_prot.up(msg);
}
public void up(MessageBatch batch) {
for(Message msg: batch) {
if(isView(msg)) {
batch.remove(msg);
checkDone(msg, views);
}
}
if(!batch.isEmpty())
up_prot.up(batch);
}
protected synchronized void checkDone(Message msg, List<Message> list) {
list.add(msg);
if((join_rsps.size() >= 2 || views.size() >= 2) && !removed) {
flushMessages();
ch.getProtocolStack().removeProtocol(this);
removed=true;
}
}
protected void flushMessages() {
System.out.printf("** flushing %d JOIN-RSPs and %d views:\n", join_rsps.size(), views.size());
int count=1;
for(Message msg: join_rsps) {
try {
JoinRsp join_rsp=Util.streamableFromBuffer(JoinRsp.class, msg.getRawBuffer(), msg.getOffset(), msg.getLength());
System.out.printf("join-rsp #%d to %s: %s\n", count++, msg.dest(), join_rsp.getView());
}
catch(Throwable t) {
log.error("failed unmarshalling JOIN-RSP", t);
}
}
// deliver the views
count=1;
for(Message msg: views) {
try {
Tuple<View,Digest> tuple=GMS._readViewAndDigest(msg.getRawBuffer(), msg.getOffset(), msg.getLength());
System.out.printf("view #%d: %s\n", count++, tuple.getVal1());
}
catch(Throwable t) {
log.error("failed unmarshalling view", t);
}
}
System.out.println("\n");
// send the JOIN-RSPs: the first JOIN-RSP neeeds to be handled by a client, the second by a participant: if we
// sent them right next to each other, then the second would not be installed if GMS.impl is still a client.
// We therefore need to wait until impl is a participant. This is a kludge, but better then using sleep()
Message join_rsp_msg=join_rsps.remove(0);
down_prot.down(join_rsp_msg);
join_rsp_msg=join_rsps.remove(0);
JoinRsp join_rsp=null;
try {
join_rsp=Util.streamableFromBuffer(JoinRsp.class, join_rsp_msg.getRawBuffer(), join_rsp_msg.getOffset(), join_rsp_msg.getLength());
}
catch(Exception e) {
throw new RuntimeException(e);
}
installJoinRspInParticipant(k, join_rsp);
join_rsps.clear();
// deliver the views
for(Message msg: views)
up_prot.up(msg);
views.clear();
}
// Waits until GMS.impl is ParticipantGmsImpl, then calls impl.handleJoinRsp()
protected void installJoinRspInParticipant(JChannel ch, JoinRsp rsp) {
GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
for(int i=0; i < 10; i++) {
if(ParticipantGmsImpl.class.equals(gms.getImpl().getClass())) {
gms.getImpl().handleJoinResponse(rsp);
break;
}
Util.sleep(500);
}
if(!ParticipantGmsImpl.class.equals(gms.getImpl().getClass()))
throw new IllegalStateException(String.format("GMS.impl is not participant: %s", gms.getImpl().getClass().getSimpleName()));
}
}
protected static boolean isView(Message msg) {
GMS.GmsHeader hdr=msg.getHeader(GMS_ID);
return hdr != null && hdr.getType() == GMS.GmsHeader.VIEW;
};
protected static boolean isJoinRsp(Message msg) {
GMS.GmsHeader hdr=msg.getHeader(GMS_ID);
return hdr != null && hdr.getType() == GMS.GmsHeader.JOIN_RSP;
};
}