// $Id: WANPIPE.java,v 1.7 2005/05/30 14:31:24 belaban Exp $
package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.blocks.LogicalLink;
import org.jgroups.stack.Protocol;
import org.jgroups.util.List;
import org.jgroups.util.Util;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.net.InetAddress;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
/**
Logical point-to-point link. Uses multiple physical links to provide a reliable transport. For example,
if there are 2 physical links over different networks, and one of them fails, the WAN pipe will still be
able to send traffic over the other link. Currently traffic is sent over the physical links round-robin,
but this will be made configurable in the future. Example: 70% over first link, 30% over second, or
packets are split and sent across both links (increasing the available bandwidth).
*/
public class WANPIPE extends Protocol implements LogicalLink.Receiver {
LogicalLink pipe=null;
String name=null; // logical name of WAN pipe
final List links=new List(); // contains the parsed link descriptions
Address local_addr=null;
String group_addr=null;
final Properties properties=null;
final Vector members=new Vector();
public WANPIPE() {
pipe=new LogicalLink(this);
}
public String toString() {
return "Protocol WANPIPE(local address: " + local_addr + ')';
}
public String getName() {return "WANPIPE";}
/**
Sent to destination(s) using the WAN pipe. Send local messages directly back up the stack
*/
public void down(Event evt) {
Message msg, rsp, copy;
Address dest_addr;
if(evt.getType() != Event.MSG) {
handleDownEvent(evt);
return;
}
msg=(Message)evt.getArg();
dest_addr=msg.getDest();
if(dest_addr == null) { // send both local and remote
for(int i=0; i < members.size(); i++) {
dest_addr=(Address)members.elementAt(i);
if(dest_addr.equals(local_addr)) { // local or ...
returnLocal(msg);
}
else { // remote
copy=msg.copy();
copy.setDest(dest_addr);
copy.putHeader(getName(), new WanPipeHeader(group_addr));
sendUnicastMessage(copy);
}
}
}
else {
if(dest_addr.equals(local_addr)) { // destination can either be local ...
returnLocal(msg);
}
else { // or remote
msg.putHeader(getName(), new WanPipeHeader(group_addr));
sendUnicastMessage(msg);
}
}
}
/** Make a response and send back up the same stack it came down */
void returnLocal(Message msg) {
Message rsp=msg.copy();
rsp.setDest(local_addr);
rsp.setSrc(local_addr);
passUp(new Event(Event.MSG, rsp));
}
public void start() throws Exception {
LinkInfo l;
for(Enumeration e=links.elements(); e.hasMoreElements();) {
l=(LinkInfo)e.nextElement();
pipe.addLink(l.local_addr, l.local_port, l.remote_addr, l.remote_port);
}
pipe.start();
local_addr=new WanPipeAddress(name); // logical address for the WAN pipe
passUp(new Event(Event.SET_LOCAL_ADDRESS, local_addr));
}
public void stop() {
pipe.stop();
pipe.removeAllLinks();
}
// LogicalLink.Receiver interface
public void receive(byte[] buf) {
WanPipeHeader hdr=null;
Message msg=null;
try {
msg=(Message)Util.objectFromByteBuffer(buf);
}
catch(Exception e) {
log.error("WANPIPE.receive(): " + e);
return;
}
if(log.isInfoEnabled()) log.info("received msg " + msg);
hdr=(WanPipeHeader)msg.removeHeader(getName());
/* Discard all messages destined for a channel with a different name */
String ch_name=null;
if(hdr.group_addr != null)
ch_name=hdr.group_addr;
if(group_addr == null) {
log.error("WANPIPE.receive(): group address in header was null, discarded");
return;
}
if(ch_name != null && !group_addr.equals(ch_name))
return;
passUp(new Event(Event.MSG, msg));
}
public void linkDown(InetAddress local, int local_port, InetAddress remote, int remote_port) {
Object p=getPeer();
passUp(new Event(Event.SUSPECT, p));
}
public void linkUp(InetAddress local, int local_port, InetAddress remote, int remote_port) {
}
public void missedHeartbeat(InetAddress local, int local_port, InetAddress remote, int remote_port, int num_hbs) {
}
public void receivedHeartbeatAgain(InetAddress local, int local_port, InetAddress remote, int remote_port) {
}
/** Setup the Protocol instance acording to the configuration string */
public boolean setProperties(Properties props) {super.setProperties(props);
String str;
str=props.getProperty("name");
if(str != null) {
name=str;
props.remove("name");
}
str=props.getProperty("links");
if(str != null) {
// parse links and put them in list (as LinkInfos)
if(parseLinks(str) == false)
return false;
props.remove("links");
}
if(name == null || name.length() == 0) {
log.error("WANPIPE.setProperties(): 'name' must be set");
return false;
}
if(links.size() == 0) {
log.error("WANPIPE.setProperties(): no links specified (at least 1 link must be present)");
return false;
}
if(props.size() > 0) {
log.error("WANPIPE.setProperties(): the following properties are not recognized: " + props);
return false;
}
return true;
}
/** Parse link spec and put each link into 'links' (as LinkInfo) <br>
Example: <pre> [daddy@6666,daddy@7777,daddy@7777,sindhu@6666] </pre>*/
boolean parseLinks(String s) {
LinkInfo info;
StringTokenizer tok;
String src, dst;
int index=0; // holds position of '@'
s=s.replace('[', ' ');
s=s.replace(']', ' ');
s=s.trim();
tok=new StringTokenizer(s, ",");
while(tok.hasMoreElements()) {
src=tok.nextToken().trim();
dst=tok.nextToken().trim();
info=new LinkInfo();
index=src.indexOf('@');
if(index == -1) {
log.error("WANPIPE.parseLinks(): local address " + src + " must have a @ separator");
return false;
}
info.local_addr=src.substring(0, index);
info.local_port=Integer.parseInt(src.substring(index + 1, src.length()));
index=dst.indexOf('@');
if(index == -1) {
log.error("WANPIPE.parseLinks(): remote address " + dst + " must have a @ separator");
return false;
}
info.remote_addr=dst.substring(0, index);
info.remote_port=Integer.parseInt(dst.substring(index + 1, dst.length()));
links.add(info);
}
return true;
}
Object getPeer() {
Object ret=null;
if(members == null || members.size() == 0 || local_addr == null)
return null;
for(int i=0; i < members.size(); i++)
if(!members.elementAt(i).equals(local_addr))
return members.elementAt(i);
return ret;
}
/**
If the sender is null, set our own address. We cannot just go ahead and set the address
anyway, as we might be sending a message on behalf of someone else ! E.g. in case of
retransmission, when the original sender has crashed, or in a FLUSH protocol when we
have to return all unstable messages with the FLUSH_OK response.
*/
private void setSourceAddress(Message msg) {
if(msg.getSrc() == null)
msg.setSrc(local_addr);
}
/** Send a message to the address specified in msg.dest */
private void sendUnicastMessage(Message msg) {
byte[] buf=null;
setSourceAddress(msg);
try {
buf=Util.objectToByteBuffer(msg);
}
catch(Exception e) {
log.error("WANPIPE.sendUnicastMessage(): " + e);
return;
}
try {
pipe.send(buf);
}
catch(LogicalLink.AllLinksDown links_down) {
log.error("WANPIPE.sendUnicastMessage(): WAN pipe has no currently operational " +
"link to send message. Discarding it.");
}
catch(LogicalLink.NoLinksAvailable no_links) {
log.error("WANPIPE.sendUnicastMessage(): WAN pipe has no physical links configured;" +
" cannot send message");
}
catch(Exception e) {
log.error("WANPIPE.sendUnicastMessage(): " + e);
}
}
private void handleUpEvent(Event evt) {
switch(evt.getType()) {
case Event.SUSPECT:
break;
}
}
private void handleDownEvent(Event evt) {
switch(evt.getType()) {
case Event.TMP_VIEW:
case Event.VIEW_CHANGE:
synchronized(members) {
members.removeAllElements();
Vector tmpvec=((View)evt.getArg()).getMembers();
for(int i=0; i < tmpvec.size(); i++)
members.addElement(tmpvec.elementAt(i));
}
break;
case Event.SUSPECT:
break;
case Event.GET_LOCAL_ADDRESS: // return local address -> Event(SET_LOCAL_ADDRESS, local)
passUp(new Event(Event.SET_LOCAL_ADDRESS, local_addr));
break;
case Event.CONNECT:
group_addr=(String)evt.getArg();
passUp(new Event(Event.CONNECT_OK));
break;
case Event.DISCONNECT:
passUp(new Event(Event.DISCONNECT_OK));
break;
}
}
private static class LinkInfo {
String local_addr=null, remote_addr=null;
int local_port=0, remote_port=0;
public String toString() {
StringBuffer ret=new StringBuffer();
ret.append("local_addr=" + (local_addr != null? local_addr : "null"));
ret.append(":" + local_port);
ret.append(", remote_addr=" + (remote_addr != null ? remote_addr : "null"));
ret.append(":" + remote_port);
return ret.toString();
}
}
public class WanPipeHeader extends Header {
public String group_addr=null;
public WanPipeHeader() {} // used for externalization
public WanPipeHeader(String n) {group_addr=n;}
public long size() {
return Header.HDR_OVERHEAD;
}
public String toString() {
return "[WanPipe: group_addr=" + group_addr + ']';
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(group_addr);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
group_addr=(String)in.readObject();
}
}
}