package org.jgroups.tests;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.jgroups.*;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Tests the FLUSH protocol, requires flush-udp.xml in ./conf to be present and
* configured to use FLUSH
*
* @author Bela Ban
* @version $Id: FlushTest.java,v 1.58.2.8 2009/03/16 21:18:38 vlada Exp $
*/
public class FlushTest extends ChannelTestBase {
private JChannel c1, c2,c3;
public FlushTest(){
super();
}
public FlushTest(String name){
super(name);
}
public void setUp() throws Exception {
super.setUp();
CHANNEL_CONFIG = System.getProperty("channel.conf.flush", "flush-udp.xml");
}
public void tearDown() throws Exception {
if(c3 != null){
c3.close();
assertFalse(c3.isOpen());
assertFalse(c3.isConnected());
c3 = null;
}
if(c2 != null){
c2.close();
assertFalse(c2.isOpen());
assertFalse(c2.isConnected());
c2 = null;
}
if(c1 != null){
c1.close();
assertFalse(c1.isOpen());
assertFalse(c1.isConnected());
c1 = null;
}
Util.sleep(500);
super.tearDown();
}
public boolean useBlocking() {
return true;
}
public void testSingleChannel() throws Exception {
Semaphore s = new Semaphore(1);
FlushTestReceiver receivers[] = new FlushTestReceiver[] { new FlushTestReceiver("c1",
s,
0,
FlushTestReceiver.CONNECT_ONLY) };
receivers[0].start();
s.release(1);
// Make sure everyone is in sync
blockUntilViewsReceived(receivers, 60000);
// Sleep to ensure the threads get all the semaphore tickets
Util.sleep(1000);
// Reacquire the semaphore tickets; when we have them all
// we know the threads are done
s.tryAcquire(1, 60, TimeUnit.SECONDS);
receivers[0].cleanup();
Util.sleep(1000);
checkEventStateTransferSequence(receivers[0]);
}
/**
* Tests issue #1 in http://jira.jboss.com/jira/browse/JGRP-335
*/
public void testJoinFollowedByUnicast() throws ChannelException {
c1 = createChannel();
c1.setReceiver(new SimpleReplier(c1, true));
c1.connect("test");
Address target = c1.getLocalAddress();
Message unicast_msg = new Message(target);
c2 = createChannel();
c2.setReceiver(new SimpleReplier(c2, false));
c2.connect("test");
// now send unicast, this might block as described in the case
c2.send(unicast_msg);
// if we don't get here this means we'd time out
}
public void testFlushWithCrashedFlushCoordinator() throws ChannelException {
c1 = createChannel();
c1.connect("test");
c2 = createChannel();
c2.connect("test");
c3 = createChannel();
c3.connect("test");
//start flush
Util.startFlush(c2);
//and then kill the flush coordinator
((JChannel)c2).shutdown();
Util.sleep(8000);
//cluster should not hang and two remaining members should have a correct view
assertTrue("correct view size, expecting 2", c1.getView().size()==2);
assertTrue("correct view size, expecting 2", c3.getView().size()==2);
}
public void testFlushWithCrashedNonCoordinator() throws ChannelException {
c1 = createChannel();
c1.connect("test");
c2 = createChannel();
c2.connect("test");
c3 = createChannel();
c3.connect("test");
//start flush
Util.startFlush(c2);
//and then kill the member other than flush coordinator
((JChannel)c3).shutdown();
c2.stopFlush();
Util.sleep(8000);
//cluster should not hang and two remaining members should have a correct view
assertTrue("correct view size, expecting 2", c1.getView().size()==2);
assertTrue("correct view size, expecting 2", c2.getView().size()==2);
}
public void testFlushWithCrashedNonCoordinators() throws ChannelException {
c1 = createChannel();
c1.connect("test");
c2 = createChannel();
c2.connect("test");
c3 = createChannel();
c3.connect("test");
//start flush
Util.startFlush(c2);
//and then kill members other than flush coordinator
((JChannel)c3).shutdown();
((JChannel)c1).shutdown();
c2.stopFlush();
Util.sleep(8000);
//cluster should not hang and one remaining member should have a correct view
assertTrue("correct view size, expecting 1", c2.getView().size()==1);
}
/**
* Tests issue #2 in http://jira.jboss.com/jira/browse/JGRP-335
*/
public void testStateTransferFollowedByUnicast() throws ChannelException {
c1 = createChannel();
c1.setReceiver(new SimpleReplier(c1, true));
c1.connect("test");
Address target = c1.getLocalAddress();
Message unicast_msg = new Message(target);
c2 = createChannel();
c2.setReceiver(new SimpleReplier(c2, false));
c2.connect("test");
log.info("\n** Getting the state **");
c2.getState(null, 10000);
// now send unicast, this might block as described in the case
c2.send(unicast_msg);
}
/**
* Tests http://jira.jboss.com/jira/browse/JGRP-661
* @throws Exception
*/
public void testPartialFlush() throws Exception {
c1 = createChannel();
c1.setReceiver(new SimpleReplier(c1, true));
c1.connect("test");
c2 = createChannel();
c2.setReceiver(new SimpleReplier(c2, false));
c2.connect("test");
List<Address> members = new ArrayList<Address>();
members.add(c2.getLocalAddress());
boolean flushedOk = Util.startFlush(c2,members);
assertTrue("Partial flush worked", flushedOk);
c2.stopFlush(members);
}
/**
* Tests emition of block/unblock/get|set state events in both mux and bare
* channel mode. In mux mode this test creates getFactoryCount() real
* channels and creates only one mux application on top of each channel. In
* bare channel mode 4 real channels are created.
*
*/
public void testBlockingNoStateTransfer() {
String[] names = createApplicationNames(4);
_testChannels(names, FlushTestReceiver.CONNECT_ONLY, 4);
}
/**
* Tests emition of block/unblock/set|get state events for both mux and bare
* channel depending on mux.on parameter. In mux mode there will be only one
* mux channel for each "real" channel created and the number of real
* channels created is getMuxFactoryCount().
*
*/
public void testBlockingWithStateTransfer() {
String[] names = createApplicationNames(4);
_testChannels(names, FlushTestReceiver.CONNECT_AND_SEPARATE_GET_STATE, 4);
}
/**
* Tests emition of block/unblock/set|get state events for both mux and bare
* channel depending on mux.on parameter. In mux mode there will be only one
* mux channel for each "real" channel created and the number of real
* channels created is getMuxFactoryCount().
*
*/
public void testBlockingWithConnectAndStateTransfer() {
String[] names = createApplicationNames(4);
_testChannels(names, FlushTestReceiver.CONNECT_AND_GET_STATE, 4);
}
private void _testChannels(String names[],
int connectType,
Assertable a) {
int count = names.length;
ArrayList<FlushTestReceiver> channels = new ArrayList<FlushTestReceiver>(count);
try{
// Create a semaphore and take all its permits
Semaphore semaphore = new Semaphore(count);
semaphore.acquire(count);
// Create channels and their threads that will block on the
// semaphore
for(int i = 0;i < count;i++){
FlushTestReceiver channel = new FlushTestReceiver(names[i], semaphore, 0, connectType);
channels.add(channel);
// Release one ticket at a time to allow the thread to start
// working
channel.start();
semaphore.release(1);
Util.sleep(1000);
}
blockUntilViewsReceived(channels, 10000);
// if state transfer is used release all at once
// clear all channels of view events
if(connectType == FlushTestReceiver.CONNECT_AND_SEPARATE_GET_STATE){
for(FlushTestReceiver app:channels){
app.clear();
}
semaphore.release(count);
}
// Sleep to ensure the threads get all the semaphore tickets
Util.sleep(1000);
// Reacquire the semaphore tickets; when we have them all
// we know the threads are done
semaphore.tryAcquire(count, 60, TimeUnit.SECONDS);
// do general asserts about channels
a.verify(channels);
// kill random member
FlushTestReceiver randomRecv = channels.remove(RANDOM.nextInt(count));
log.info("Closing random member " + randomRecv.getName()
+ " at "
+ randomRecv.getLocalAddress());
ChannelCloseAssertable closeAssert = new ChannelCloseAssertable(randomRecv);
randomRecv.cleanup();
// let the view propagate and verify related asserts
Util.sleep(5000);
closeAssert.verify(channels);
}catch(Exception ex){
log.warn("Exception encountered during test", ex);
fail("Exception encountered during test execution: " + ex);
}finally{
//close all channels and ....
for(FlushTestReceiver app:channels){
app.cleanup();
Util.sleep(2000);
}
// verify block/unblock/view/get|set state sequences for all members
for(FlushTestReceiver receiver:channels) {
checkEventStateTransferSequence(receiver);
}
}
}
private void _testChannels(String names[], int connectMethod, int viewSize) {
_testChannels(names, connectMethod, new ChannelAssertable(viewSize));
}
private class ChannelCloseAssertable implements Assertable {
ChannelApplication app;
View viewBeforeClose;
Address appAddress;
public ChannelCloseAssertable(ChannelApplication app){
this.app = app;
this.viewBeforeClose = app.getChannel().getView();
appAddress = app.getChannel().getLocalAddress();
}
public void verify(Object verifiable) {
Channel ch = app.getChannel();
assertFalse("Channel open", ch.isOpen());
assertFalse("Chnanel connected", ch.isConnected());
}
}
private class ChannelAssertable implements Assertable {
int expectedViewSize = 0;
public ChannelAssertable(int expectedViewSize){
this.expectedViewSize = expectedViewSize;
}
public void verify(Object verifiable) {
Collection channels = (Collection) verifiable;
for(Iterator iter = channels.iterator();iter.hasNext();){
FlushTestReceiver receiver = (FlushTestReceiver) iter.next();
Channel ch = receiver.getChannel();
assertEquals("Correct view", ch.getView().getMembers().size(), expectedViewSize);
assertTrue("Channel open", ch.isOpen());
assertTrue("Chnanel connected", ch.isConnected());
assertNotNull("Valid address ", ch.getLocalAddress());
assertTrue("Address included in view ", ch.getView()
.getMembers()
.contains(ch.getLocalAddress()));
assertNotNull("Valid cluster name ", ch.getClusterName());
}
}
}
protected JChannel createChannel() throws ChannelException {
JChannel ret = new JChannel(CHANNEL_CONFIG);
ret.setOpt(Channel.BLOCK, Boolean.TRUE);
Protocol flush = ret.getProtocolStack().findProtocol("FLUSH");
if(flush != null){
Properties p = new Properties();
p.setProperty("timeout", "0");
flush.setProperties(p);
// send timeout up and down the stack, so other protocols can use
// the same value too
Map<String, Object> map = new HashMap<String, Object>();
map.put("flush_timeout", new Long(0));
flush.getUpProtocol().up(new Event(Event.CONFIG, map));
flush.getDownProtocol().down(new Event(Event.CONFIG, map));
}
return ret;
}
private interface Assertable {
public void verify(Object verifiable);
}
private class FlushTestReceiver extends PushChannelApplicationWithSemaphore {
List<Object> events;
private int connectMethod;
public static final int CONNECT_ONLY = 1;
public static final int CONNECT_AND_SEPARATE_GET_STATE = 2;
public static final int CONNECT_AND_GET_STATE = 3;
int msgCount = 0;
protected FlushTestReceiver(String name,
Semaphore semaphore,
int msgCount,
int connectMethod) throws Exception{
super(name, semaphore);
this.connectMethod = connectMethod;
this.msgCount = msgCount;
events = Collections.synchronizedList(new LinkedList<Object>());
if(connectMethod == CONNECT_ONLY || connectMethod == CONNECT_AND_SEPARATE_GET_STATE)
channel.connect("test");
if(connectMethod == CONNECT_AND_GET_STATE){
channel.connect("test",null,null, 25000);
}
}
public void clear() {
events.clear();
}
public List<Object> getEvents() {
return new LinkedList<Object>(events);
}
public void block() {
events.add(new BlockEvent());
}
public void unblock() {
events.add(new UnblockEvent());
}
public void viewAccepted(View new_view) {
events.add(new_view);
}
public byte[] getState() {
events.add(new GetStateEvent(null, null));
return new byte[] { 'b', 'e', 'l', 'a' };
}
public void setState(byte[] state) {
events.add(new SetStateEvent(null, null));
}
public void getState(OutputStream ostream) {
events.add(new GetStateEvent(null, null));
byte[] payload = new byte[] { 'b', 'e', 'l', 'a' };
try{
ostream.write(payload);
}catch(IOException e){
e.printStackTrace();
}finally{
Util.close(ostream);
}
}
public void setState(InputStream istream) {
events.add(new SetStateEvent(null, null));
byte[] payload = new byte[4];
try{
istream.read(payload);
}catch(IOException e){
e.printStackTrace();
}finally{
Util.close(istream);
}
}
protected void useChannel() throws Exception {
if(connectMethod == CONNECT_AND_SEPARATE_GET_STATE){
channel.getState(null, 25000);
}
if(msgCount > 0){
for(int i = 0;i < msgCount;i++){
channel.send(new Message());
Util.sleep(100);
}
}
}
}
private class SimpleReplier extends ExtendedReceiverAdapter {
Channel channel;
boolean handle_requests = false;
public SimpleReplier(Channel channel,boolean handle_requests){
this.channel = channel;
this.handle_requests = handle_requests;
}
public void receive(Message msg) {
Message reply = new Message(msg.getSrc());
try{
log.info("-- MySimpleReplier[" + channel.getLocalAddress()
+ "]: received message from "
+ msg.getSrc());
if(handle_requests){
log.info(", sending reply");
channel.send(reply);
}else
System.out.println("\n");
}catch(Exception e){
e.printStackTrace();
}
}
public void viewAccepted(View new_view) {
log.info("-- MySimpleReplier[" + channel.getLocalAddress()
+ "]: viewAccepted("
+ new_view
+ ")");
}
public void block() {
log.info("-- MySimpleReplier[" + channel.getLocalAddress() + "]: block()");
}
public void unblock() {
log.info("-- MySimpleReplier[" + channel.getLocalAddress() + "]: unblock()");
}
}
public static Test suite() {
return new TestSuite(FlushTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(FlushTest.suite());
}
}