org.jgroups.stack.GossipRouter Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
package org.jgroups.stack;
import org.jgroups.Address;
import org.jgroups.Message;
import org.jgroups.PhysicalAddress;
import org.jgroups.Version;
import org.jgroups.annotations.Component;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.blocks.cs.*;
import org.jgroups.conf.AttributeType;
import org.jgroups.jmx.JmxConfigurator;
import org.jgroups.jmx.ReflectUtils;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.PingData;
import org.jgroups.util.*;
import javax.net.ssl.*;
import java.io.DataInput;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* Router for TCP based group comunication (using layer TCP instead of UDP). Instead of the TCP
* layer sending packets point-to-point to each other member, it sends the packet to the router
* which - depending on the target address - multicasts or unicasts it to the group / or single member.
*
* This class is especially interesting for applets which cannot directly make connections (neither
* UDP nor TCP) to a host different from the one they were loaded from. Therefore, an applet would
* create a normal channel plus protocol stack, but the bottom layer would have to be the TCP layer
* which sends all packets point-to-point (over a TCP connection) to the router, which in turn
* forwards them to their end location(s) (also over TCP). A centralized router would therefore have
* to be running on the host the applet was loaded from.
*
* An alternative for running JGroups in an applet (IP multicast is not allows in applets as of
* 1.2), is to use point-to-point UDP communication via the gossip server. However, then the appplet
* has to be signed which involves additional administrative effort on the part of the user.
*
* Note that a GossipRouter is also a good way of running JGroups in Amazon's EC2 environment which (as of summer 09)
* doesn't support IP multicasting.
* @author Bela Ban
* @author Vladimir Blagojevic
* @author Ovidiu Feodorov
* @since 2.1.1
*/
public class GossipRouter extends ReceiverAdapter implements ConnectionListener, DiagnosticsHandler.ProbeHandler {
@ManagedAttribute(description="The bind address which should be used by the GossipRouter.")
protected InetAddress bind_addr;
@ManagedAttribute(description="server port on which the GossipRouter accepts client connections", writable=true)
protected int port=12001;
@ManagedAttribute(description="Time (in msecs) until idle client connections are closed. 0 disables expiration.",
writable=true,type=AttributeType.TIME)
protected long expiry_time;
@ManagedAttribute(description="Interval (in msecs) to check for expired connections and close them. 0 disables reaping.",
writable=true,type=AttributeType.TIME)
protected long reaper_interval;
@ManagedAttribute(description="Time (in ms) for setting SO_LINGER on sockets returned from accept(). 0 means do not set SO_LINGER"
,type=AttributeType.TIME)
protected int linger_timeout=-1;
protected ThreadFactory thread_factory=new DefaultThreadFactory("gossip", false, true);
protected SocketFactory socket_factory=new DefaultSocketFactory();
@ManagedAttribute(description="Initial size of the TCP/NIO receive buffer (in bytes)")
protected int recv_buf_size;
@ManagedAttribute(description="Expose GossipRouter via JMX")
protected boolean jmx;
@ManagedAttribute(description="Use non-blocking IO (true) or blocking IO (false). Cannot be changed at runtime")
protected boolean use_nio;
@ManagedAttribute(description="Handles client disconnects: sends SUSPECT message to all other members of that group")
protected boolean emit_suspect_events=true;
@ManagedAttribute(description="Dumps messages (dest/src/length/headers) to stdout")
protected DumpMessages dump_msgs;
@ManagedAttribute(description="The max number of bytes a message can have. If greater, an exception will be " +
"thrown. 0 disables this", type=AttributeType.BYTES)
protected int max_length;
protected BaseServer server;
protected final AtomicBoolean running=new AtomicBoolean(false);
protected final Log log=LogFactory.getLog(this.getClass());
@Component(name="diag",description="DiagnosticsHandler listening for probe requests")
protected DiagnosticsHandler diag;
@Component(name="tls",description="Contains the attributes for TLS (SSL sockets) when enabled=true")
protected TLS tls=new TLS();
@ManagedAttribute(description="Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759)) in TCP (use_nio=false)")
protected boolean non_blocking_sends;
@ManagedAttribute(description="When sending and non_blocking, how many messages to queue max")
protected int max_send_queue=128;
// mapping between groups and - pairs
protected final Map> address_mappings=new ConcurrentHashMap<>();
// to cache output streams for serialization (https://issues.redhat.com/browse/JGRP-2576)
protected final Map output_streams=new ConcurrentHashMap<>();
protected static final BiConsumer MSG_CONSUMER=(version,msg)
-> System.out.printf("dst=%s src=%s (%d bytes): hdrs= %s\n", msg.dest(), msg.src(), msg.getLength(), msg.printHeaders());
public GossipRouter(String bind_addr, int local_port) throws Exception {
this.port=local_port;
this.bind_addr=bind_addr != null? InetAddress.getByName(bind_addr) : null;
init();
}
public GossipRouter(InetAddress bind_addr, int local_port) throws Exception {
this.port=local_port;
this.bind_addr=bind_addr;
init();
}
public Address localAddress() {return server.localAddress();}
public String bindAddress() {return bind_addr != null? bind_addr.toString() : null;}
public GossipRouter bindAddress(InetAddress addr) {this.bind_addr=addr; return this;}
public int port() {return port;}
public GossipRouter port(int port) {this.port=port; return this;}
public long expiryTime() {return expiry_time;}
public GossipRouter expiryTime(long t) {this.expiry_time=t; return this;}
public long reaperInterval() {return reaper_interval;}
public GossipRouter reaperInterval(long t) {this.reaper_interval=t; return this;}
public int lingerTimeout() {return linger_timeout;}
public GossipRouter lingerTimeout(int t) {this.linger_timeout=t; return this;}
public int recvBufferSize() {return recv_buf_size;}
public GossipRouter recvBufferSize(int s) {recv_buf_size=s; return this;}
public ThreadFactory threadPoolFactory() {return thread_factory;}
public GossipRouter threadPoolFactory(ThreadFactory f) {this.thread_factory=f; return this;}
public SocketFactory socketFactory() {return socket_factory;}
public GossipRouter socketFactory(SocketFactory sf) {this.socket_factory=sf; return this;}
public boolean jmx() {return jmx;}
public GossipRouter jmx(boolean flag) {jmx=flag; return this;}
public boolean useNio() {return use_nio;}
public GossipRouter useNio(boolean flag) {use_nio=flag; return this;}
public boolean emitSuspectEvents() {return emit_suspect_events;}
public GossipRouter emitSuspectEvents(boolean flag) {emit_suspect_events=flag; return this;}
public DumpMessages dumpMessages() {return dump_msgs;}
public GossipRouter dumpMessages(DumpMessages flag) {dump_msgs=flag; return this;}
public GossipRouter dumpMessages(boolean d) {dump_msgs=d? DumpMessages.ALL : DumpMessages.NONE; return this;}
public int maxLength() {return max_length;}
public GossipRouter maxLength(int len) {max_length=len; if(server != null) server.setMaxLength(len);
return this;}
public DiagnosticsHandler diagHandler() {return diag;}
public TLS tls() {return tls;}
public GossipRouter tls(TLS t) {this.tls=t; return this;}
public boolean nonBlockingSends() {return non_blocking_sends;}
public GossipRouter nonBlockingSends(boolean b) {this.non_blocking_sends=b; return this;}
public int maxSendQueue() {return max_send_queue;}
public GossipRouter maxSendQueue(int s) {this.max_send_queue=s; return this;}
@ManagedAttribute(description="operational status", name="running")
public boolean running() {return running.get();}
@ManagedAttribute(description="The number of different clusters registered")
public int numRegisteredClusters() {
return address_mappings.size();
}
@ManagedAttribute(description="The number of registered client (all clusters)")
public int numRegisteredClients() {
return (int)address_mappings.values().stream().mapToLong(s -> s.keySet().size()).sum();
}
public GossipRouter init() throws Exception {
diag=new DiagnosticsHandler(log, socket_factory, thread_factory)
.registerProbeHandler(this)
.printHeaders(b -> String.format("GossipRouter [addr=%s, cluster=GossipRouter, version=%s]\n",
localAddress(), Version.description));
return this;
}
/**
* Lifecycle operation. Called after create(). When this method is called, the managed attributes
* have already been set.
* Brings the Router into a fully functional state.
*/
@ManagedOperation(description="Lifecycle operation. Called after create(). When this method is called, "
+ "the managed attributes have already been set. Brings the Router into a fully functional state.")
public GossipRouter start() throws Exception {
if(!running.compareAndSet(false, true))
return this;
if(jmx)
JmxConfigurator.register(this, Util.getMBeanServer(), "jgroups:name=GossipRouter");
if(diag.isEnabled()) {
StackType ip_version=bind_addr instanceof Inet6Address? StackType.IPv6 : StackType.IPv4;
Configurator.setDefaultAddressValues(diag, ip_version);
diag.start();
}
// Creating the DiagnosticsHandler _before_ the TLS socket factory makes the former use regular sockets;
// if this was not the case, Probe would have to be extended to use SSLSockets, too
if(tls.enabled()) {
SocketFactory factory=tls.createSocketFactory();
socketFactory(factory);
}
server=use_nio? new NioServer(thread_factory, socket_factory, bind_addr, port, port, null, 0,
recv_buf_size, "jgroups.nio.gossiprouter")
: new TcpServer(thread_factory, socket_factory, bind_addr, port, port, null, 0, recv_buf_size,
"jgroups.tcp.gossiprouter").nonBlockingSends(non_blocking_sends).maxSendQueue(max_send_queue);
server.receiver(this).setMaxLength(max_length)
.addConnectionListener(this)
.connExpireTimeout(expiry_time).reaperInterval(reaper_interval).linger(linger_timeout);
server.start();
Runtime.getRuntime().addShutdownHook(new Thread(GossipRouter.this::stop));
return this;
}
/**
* Always called before destroy(). Close connections and frees resources.
*/
public void stop() {
if(!running.compareAndSet(true, false))
return;
try {
JmxConfigurator.unregister(this, Util.getMBeanServer(), "jgroups:name=GossipRouter");
}
catch(Exception ex) {
log.error(Util.getMessage("MBeanDeRegistrationFailed"), ex);
}
Util.close(diag, server);
log.debug("router stopped");
}
@ManagedOperation(description="Dumps the contents of the routing table")
public String dumpRoutingTable() {
return server.printConnections();
}
@ManagedOperation(description="Dumps the address mappings")
public String dumpAddressMappings() {
StringBuilder sb=new StringBuilder();
for(Map.Entry> entry: address_mappings.entrySet()) {
String group=entry.getKey();
Map val=entry.getValue();
if(val == null)
continue;
sb.append(group).append(":\n");
for(Map.Entry entry2: val.entrySet()) {
Address logical_addr=entry2.getKey();
Entry val2=entry2.getValue();
if(val2 == null)
continue;
sb.append(String.format(" %s: %s (client_addr: %s, uuid:%s)\n", val2.logical_name, val2.phys_addr, val2.client_addr, logical_addr));
}
}
return sb.toString();
}
@Override public void receive(Address sender, byte[] buf, int offset, int length) {
receive(sender, ByteBuffer.wrap(buf, offset, length));
}
@Override
public void receive(Address sender, ByteBuffer buf) {
int original_pos=buf.position();
GossipType type;
try {
type=GossipType.values()[buf.get()];
}
catch(Exception ex) {
log.error("failed reading data from %s: %s", sender, ex);
return;
}
switch(type) {
case REGISTER:
handleRegister(sender, new ByteArrayDataInputStream(buf));
break;
case MESSAGE:
// we already read the type, now read group and dest (minimal info required to route the message)
// this way, we don't need to copy the buffer
try {
DataInput in=new ByteArrayDataInputStream(buf);
String group=Bits.readString(in);
Address dest=Util.readAddress(in);
route(group, dest, buf.position(original_pos));
if(dump_msgs == DumpMessages.ALL) {
ByteArrayDataInputStream input=new ByteArrayDataInputStream(buf);
GossipData data=new GossipData();
data.readFrom(input);
dump(data);
}
}
catch(Throwable t) {
log.error(Util.getMessage("FailedReadingRequest"), t);
return;
}
break;
case HEARTBEAT:
handleHeartbeat(sender);
break;
case GET_MBRS:
handleGetMembersRequest(sender, new ByteArrayDataInputStream(buf));
break;
case UNREGISTER:
handleUnregister(new ByteArrayDataInputStream(buf));
break;
}
}
public void receive(Address sender, DataInput in) throws Exception {
GossipType type=GossipType.values()[in.readByte()];
GossipData request=null;
switch(type) {
case REGISTER:
handleRegister(sender, in);
break;
case MESSAGE:
try {
// inefficient: we should transfer bytes from input stream to output stream, but that is not
// available natively
if((request=readRequest(in, type)) != null) {
ByteArrayDataOutputStream out=getOutputStream(request.sender, request.serializedSize());
// we might be concurrent traffic from *different* (senders) TcpConnections for the
// *same* target address, so we have to synchronized below in order to avoid corruption
// of data (https://issues.redhat.com/browse/JGRP-2722)
synchronized(out) {
out.position(0);
request.writeTo(out);
route(request.group, request.addr, out.buffer(), 0, out.position());
}
if(dump_msgs == DumpMessages.ALL)
dump(request);
}
}
catch(Throwable t) {
log.error(Util.getMessage("FailedReadingRequest"), t);
return;
}
break;
case HEARTBEAT:
request=readRequest(in, type);
handleHeartbeat(sender);
break;
case GET_MBRS:
handleGetMembersRequest(sender, in);
break;
case UNREGISTER:
handleUnregister(in);
break;
}
}
public Map handleProbe(String... keys) {
Map map=new TreeMap<>();
for(String key: keys) {
if(key.startsWith("ops")) {
map.put(key, ReflectUtils.listOperations(getClass()));
continue;
}
if(key.startsWith("op") || key.startsWith("invoke")) {
int index=key.indexOf('=');
if(index == -1)
throw new IllegalArgumentException(String.format("\\= not found in operation %s", key));
String op=key.substring(index + 1).trim();
try {
ReflectUtils.invokeOperation(map, op, this);
}
catch(Exception ex) {
throw new IllegalArgumentException(String.format("operation %s failed: %s", op, ex));
}
continue;
}
if(key.startsWith("jmx")) {
int index=key.indexOf('=');
String tmp;
if(index == -1)
tmp=null;
else
tmp=key.substring(index+1);
ReflectUtils.handleAttributeAccess(map, tmp, this);
continue;
}
if(key.startsWith("keys")) {
StringBuilder sb=new StringBuilder();
for(DiagnosticsHandler.ProbeHandler handler : diag.getProbeHandlers()) {
String[] tmp=handler.supportedKeys();
if(tmp != null) {
for(String s: tmp)
sb.append(s).append(" ");
}
}
map.put(key, sb.toString());
continue;
}
if(key.startsWith("member-addrs")) {
InetSocketAddress sa=(InetSocketAddress)diag.getLocalAddress();
if(sa != null) {
Set physical_addrs=Set.of(new IpAddress(sa.getAddress(), sa.getPort()));
// Set physical_addrs=diag.getLocalAddress();
String list=Util.print(physical_addrs);
map.put(key, list);
}
continue;
}
if(key.startsWith("dump")) {
map.put(key, Util.dumpThreads());
}
}
return map;
}
public String[] supportedKeys() {
return new String[]{"ops", "op", "invoke", "keys", "member-addrs", "dump"};
}
protected ByteArrayDataOutputStream getOutputStream(Address mbr, int size) {
return output_streams.computeIfAbsent(mbr, __ -> new ByteArrayDataOutputStream(size));
}
protected void handleHeartbeat(Address sender) {
GossipData rsp=new GossipData(GossipType.HEARTBEAT);
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(rsp.serializedSize());
try {
rsp.writeTo(out);
server.send(sender, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending %s to %s: %s", GossipType.HEARTBEAT, sender, ex);
}
}
protected void handleRegister(Address sender, DataInput in) {
GossipData req=readRequest(in, GossipType.REGISTER);
if(req != null) {
String group=req.getGroup();
Address addr=req.getAddress();
PhysicalAddress phys_addr=req.getPhysicalAddress();
String logical_name=req.getLogicalName();
addAddressMapping(sender, group, addr, phys_addr, logical_name);
if(log.isDebugEnabled())
log.debug("added %s (%s) to group %s", logical_name, phys_addr, group);
if(dump_msgs == DumpMessages.REGISTRATION || dump_msgs == DumpMessages.ALL)
System.out.printf("added %s (%s) to group %s\n", logical_name, phys_addr, group);
}
}
protected void handleUnregister(DataInput in) {
GossipData req=readRequest(in, GossipType.UNREGISTER);
if(req != null)
removeAddressMapping(req.getGroup(), req.getAddress());
}
protected void handleGetMembersRequest(Address sender, DataInput in) {
GossipData req=readRequest(in, GossipType.GET_MBRS);
if(req == null)
return;
GossipData rsp=new GossipData(GossipType.GET_MBRS_RSP, req.getGroup(), null);
Map members=address_mappings.get(req.getGroup());
if(members != null) {
for(Map.Entry entry : members.entrySet()) {
Address logical_addr=entry.getKey();
PhysicalAddress phys_addr=entry.getValue().phys_addr;
String logical_name=entry.getValue().logical_name;
PingData data=new PingData(logical_addr, true, logical_name, phys_addr);
rsp.addPingData(data);
}
}
if(dump_msgs == DumpMessages.ALL || log.isDebugEnabled()) {
String rsps=rsp.ping_data == null? null
: rsp.ping_data.stream().map(r -> String.format("%s (%s)", r.getLogicalName(), r.getPhysicalAddr()))
.collect(Collectors.joining(", "));
if(rsps != null) {
if(log.isDebugEnabled())
log.debug("get(%s) -> %s", req.getGroup(), rsps);
if(dump_msgs == DumpMessages.ALL)
System.out.printf("get(%s) -> %s\n", req.getGroup(), rsps);
}
}
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(rsp.serializedSize());
try {
rsp.writeTo(out);
server.send(sender, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending %s to %s: %s", GossipType.GET_MBRS_RSP, sender, ex);
}
}
protected static void dump(GossipData data) {
Util.parse(data.buffer, data.offset, data.length, MSG_CONSUMER, null, null, false, false);
}
@Override
public void connectionClosed(Connection conn) {
removeFromAddressMappings(conn.peerAddress());
}
@Override
public void connectionEstablished(Connection conn) {
log.debug("connection to %s established", conn.peerAddress());
}
protected GossipData readRequest(DataInput in) {
GossipData data=new GossipData();
try {
data.readFrom(in);
return data;
}
catch(Exception ex) {
log.error(Util.getMessage("FailedReadingRequest"), ex);
return null;
}
}
protected GossipData readRequest(DataInput in, GossipType type) {
GossipData data=new GossipData(type);
try {
data.readFrom(in, false);
return data;
}
catch(Exception ex) {
log.error(Util.getMessage("FailedReadingRequest"), ex);
return null;
}
}
protected void addAddressMapping(Address sender, String group, Address addr, PhysicalAddress phys_addr, String logical_name) {
NameCache.add(addr, logical_name);
ConcurrentMap m=address_mappings.get(group);
if(m == null) {
ConcurrentMap existing=this.address_mappings.putIfAbsent(group, m=new ConcurrentHashMap<>());
if(existing != null)
m=existing;
}
m.put(addr, new Entry(sender, phys_addr, logical_name));
}
protected void removeAddressMapping(String group, Address addr) {
Map m=address_mappings.get(group);
if(m == null)
return;
Entry e=m.get(addr);
if(e != null) {
if(log.isDebugEnabled())
log.debug("removed %s (%s) from group %s", e.logical_name, e.phys_addr, group);
if(dump_msgs == DumpMessages.REGISTRATION || dump_msgs == DumpMessages.ALL)
System.out.printf("removed %s (%s) from group %s\n", e.logical_name, e.phys_addr, group);
}
if(m.remove(addr) != null && m.isEmpty())
address_mappings.remove(group);
output_streams.remove(addr);
}
protected void removeFromAddressMappings(Address client_addr) {
if(client_addr == null) return;
Set> suspects=null; // group/address pairs
for(Map.Entry> entry: address_mappings.entrySet()) {
ConcurrentMap map=entry.getValue();
for(Map.Entry entry2: map.entrySet()) {
Entry e=entry2.getValue();
if(client_addr.equals(e.client_addr)) {
map.remove(entry2.getKey());
output_streams.remove(entry2.getKey());
log.debug("connection to %s closed", client_addr);
if(log.isDebugEnabled())
log.debug("removed %s (%s) from group %s", e.logical_name, e.phys_addr, entry.getKey());
if(dump_msgs == DumpMessages.REGISTRATION || dump_msgs == DumpMessages.ALL)
System.out.printf("removed %s (%s) from group %s\n", e.logical_name, e.phys_addr, entry.getKey());
if(map.isEmpty())
address_mappings.remove(entry.getKey());
if(suspects == null) suspects=new HashSet<>();
suspects.add(new Tuple<>(entry.getKey(), entry2.getKey()));
break;
}
}
}
if(emit_suspect_events && suspects != null && !suspects.isEmpty()) {
for(Tuple suspect: suspects) {
String group=suspect.getVal1();
Address addr=suspect.getVal2();
ConcurrentMap map=address_mappings.get(group);
if(map == null)
continue;
GossipData data=new GossipData(GossipType.SUSPECT, group, addr);
sendToAllMembersInGroup(map.entrySet(), data);
}
}
}
protected void route(String group, Address dest, byte[] msg, int offset, int length) {
ConcurrentMap map=address_mappings.get(group);
if(map == null)
return;
if(dest != null) { // unicast
Entry entry=map.get(dest);
if(entry != null)
sendToMember(entry.client_addr, msg, offset, length);
else
log.warn("dest %s in cluster %s not found", dest, group);
}
else { // multicast - send to all members in group
Set> dests=map.entrySet();
sendToAllMembersInGroup(dests, msg, offset, length);
}
}
protected void route(String group, Address dest, ByteBuffer buf) {
ConcurrentMap map=address_mappings.get(group);
if(map == null)
return;
if(dest != null) { // unicast
Entry entry=map.get(dest);
if(entry != null)
sendToMember(entry.client_addr, buf);
else
log.warn("dest %s in cluster %s not found", dest, group);
}
else { // multicast - send to all members in group
Set> dests=map.entrySet();
sendToAllMembersInGroup(dests, buf);
}
}
protected void sendToAllMembersInGroup(Set> dests, GossipData request) {
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(request.serializedSize());
try {
request.writeTo(out);
}
catch(Exception ex) {
log.error("failed marshalling gossip data %s: %s; dropping request", request, ex);
return;
}
for(Map.Entry entry: dests) {
Entry e=entry.getValue();
if(e == null /* || e.phys_addr == null */)
continue;
try {
server.send(e.client_addr, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending message to %s (%s): %s", e.logical_name, e.phys_addr, ex);
}
}
}
protected void sendToAllMembersInGroup(Set> dests, byte[] buf, int offset, int len) {
for(Map.Entry entry: dests) {
Entry e=entry.getValue();
if(e == null /* || e.phys_addr == null */)
continue;
try {
server.send(e.client_addr, buf, offset, len);
}
catch(Exception ex) {
log.error("failed sending message to %s (%s): %s", e.logical_name, e.phys_addr, ex);
}
}
}
protected void sendToAllMembersInGroup(Set> dests, ByteBuffer buf) {
for(Map.Entry entry: dests) {
Entry e=entry.getValue();
if(e == null /* || e.phys_addr == null */)
continue;
try {
server.send(e.client_addr, buf.duplicate());
}
catch(Exception ex) {
log.error("failed sending message to %s (%s): %s", e.logical_name, e.phys_addr, ex);
}
}
}
protected void sendToMember(Address dest, GossipData request) {
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(request.serializedSize());
try {
request.writeTo(out);
server.send(dest, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending unicast message to %s: %s", dest, ex);
}
}
protected void sendToMember(Address dest, ByteBuffer buf) {
try {
server.send(dest, buf);
}
catch(Exception ex) {
log.error("failed sending unicast message to %s: %s", dest, ex);
}
}
protected void sendToMember(Address dest, byte[] buf, int offset, int len) {
try {
server.send(dest, buf, offset, len);
}
catch(Exception ex) {
log.error("failed sending unicast message to %s: %s", dest, ex);
}
}
protected static class Entry {
protected final PhysicalAddress phys_addr;
protected final String logical_name;
protected final Address client_addr; // address of the client which registered an item
public Entry(Address client_addr, PhysicalAddress phys_addr, String logical_name) {
this.phys_addr=phys_addr;
this.logical_name=logical_name;
this.client_addr=client_addr;
}
public String toString() {return String.format("client=%s, name=%s, addr=%s", client_addr, logical_name, phys_addr);}
}
public static void main(String[] args) throws Exception {
int port=12001;
int recv_buf_size=0, max_length=0;
long expiry_time=0, reaper_interval=0;
boolean diag_enabled=true, diag_enable_udp=true, diag_enable_tcp=false;
InetAddress diag_mcast_addr=null, diag_bind_addr=null;
int diag_port=7500, diag_port_range=50, diag_ttl=8, soLinger=-1;
List diag_bind_interfaces=null;
String diag_passcode=null;
// Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759)") in TCP (use_nio=false)
boolean non_blocking_sends=false;
// When sending and non_blocking, how many messages to queue max
int max_send_queue=128;
TLS tls=new TLS();
long start=System.currentTimeMillis();
String bind_addr=null;
boolean jmx=false, nio=false, suspects=true;
DumpMessages dump_msgs = DumpMessages.NONE;
for(int i=0; i < args.length; i++) {
String arg=args[i];
if("-port".equals(arg)) {
port=Integer.parseInt(args[++i]);
continue;
}
if("-bindaddress".equals(arg) || "-bind_addr".equals(arg)) {
bind_addr=args[++i];
continue;
}
if("-recv_buf_size".equals(args[i])) {
recv_buf_size=Integer.parseInt(args[++i]);
continue;
}
if("-expiry".equals(arg)) {
expiry_time=Long.parseLong(args[++i]);
continue;
}
if("-reaper_interval".equals(arg)) {
reaper_interval=Long.parseLong(args[++i]);
continue;
}
if("-jmx".equals(arg)) {
jmx=Boolean.parseBoolean(args[++i]);
continue;
}
if("-solinger".equals(arg)) {
soLinger=Integer.parseInt(args[++i]);
continue;
}
if("-nio".equals(arg)) {
nio=Boolean.parseBoolean(args[++i]);
continue;
}
if("-non_blocking_sends".equals(arg)) {
non_blocking_sends=Boolean.parseBoolean(args[++i]);
continue;
}
if("max_send_queue".equals(arg)) {
max_send_queue=Integer.parseInt(args[++i]);
continue;
}
if("-suspect".equals(arg)) {
suspects=Boolean.parseBoolean(args[++i]);
continue;
}
if("-dump_msgs".equals(arg)) {
dump_msgs=DumpMessages.parse(args[++i]);
continue;
}
if("-max_length".equals(arg)) {
max_length=Integer.parseInt(args[++i]);
continue;
}
if("-diag_enabled".equals(arg)) {
diag_enabled=Boolean.parseBoolean(args[++i]);
continue;
}
if("-diag_enable_udp".equals(arg)) {
diag_enable_udp=Boolean.parseBoolean(args[++i]);
continue;
}
if("-diag_enable_tcp".equals(arg)) {
diag_enable_tcp=Boolean.parseBoolean(args[++i]);
continue;
}
if("-diag_mcast_addr".equals(arg)) {
diag_mcast_addr=InetAddress.getByName(args[++i]);
continue;
}
if("-diag_bind_addr".equals(arg)) {
diag_bind_addr=InetAddress.getByName(args[++i]);
continue;
}
if("-diag_port".equals(arg)) {
diag_port=Integer.parseInt(args[++i]);
continue;
}
if("-diag_port_range".equals(arg)) {
diag_port_range=Integer.parseInt(args[++i]);
continue;
}
if("-diag_ttl".equals(arg)) {
diag_ttl=Integer.parseInt(args[++i]);
continue;
}
if("-diag_bind_interfaces".equals(arg)) {
diag_bind_interfaces=Util.parseInterfaceList(args[++i]);
continue;
}
if("-diag_passcode".equals(arg)) {
diag_passcode=args[++i];
continue;
}
if("-tls_protocol".equals(arg)) {
tls.enabled(true).setProtocols(args[++i].split(","));
continue;
}
if("-tls_cipher_suites".equals(arg)) {
tls.enabled(true).setCipherSuites(args[++i].split(","));
continue;
}
if("-tls_provider".equals(arg)) {
tls.enabled(true).setProvider(args[++i]);
continue;
}
if("-tls_keystore_path".equals(arg)) {
tls.enabled(true).setKeystorePath(args[++i]);
continue;
}
if("-tls_keystore_password".equals(arg)) {
tls.enabled(true).setKeystorePassword(args[++i]);
continue;
}
if("-tls_keystore_type".equals(arg)) {
tls.enabled(true).setKeystoreType(args[++i]);
continue;
}
if("-tls_keystore_alias".equals(arg)) {
tls.enabled(true).setKeystoreAlias(args[++i]);
continue;
}
if("-tls_truststore_path".equals(arg)) {
tls.enabled(true).setTruststorePath(args[++i]);
continue;
}
if("-tls_truststore_password".equals(arg)) {
tls.enabled(true).setTruststorePassword(args[++i]);
continue;
}
if("-tls_truststore_type".equals(arg)) {
tls.enabled(true).setTruststoreType(args[++i]);
continue;
}
if("-tls_client_auth".equals(arg)) {
tls.enabled(true).setClientAuth(TLSClientAuth.valueOf(args[++i].toUpperCase()));
continue;
}
if("-tls_sni_matcher".equals(arg)) {
tls.enabled(true).getSniMatchers().add(SNIHostName.createSNIMatcher(args[++i]));
continue;
}
help();
return;
}
if(tls.enabled() && nio)
throw new IllegalArgumentException("Cannot use NIO with TLS");
GossipRouter router=new GossipRouter(bind_addr, port)
.jmx(jmx).expiryTime(expiry_time).reaperInterval(reaper_interval)
.useNio(nio)
.recvBufferSize(recv_buf_size)
.lingerTimeout(soLinger)
.emitSuspectEvents(suspects)
.dumpMessages(dump_msgs)
.maxLength(max_length)
.tls(tls).nonBlockingSends(non_blocking_sends).maxSendQueue(max_send_queue);
router.diagHandler().setEnabled(diag_enabled)
.enableUdp(diag_enable_udp)
.enableTcp(diag_enable_tcp)
.setMcastAddress(diag_mcast_addr)
.setBindAddress(diag_bind_addr)
.setPort(diag_port)
.setPortRange(diag_port_range)
.setTtl(diag_ttl)
.setBindInterfaces(diag_bind_interfaces)
.setPasscode(diag_passcode);
String type="";
if(tls.enabled()) {
tls.init();
SSLContext context=tls.createContext();
SocketFactory socket_factory=tls.createSocketFactory(context);
router.socketFactory(socket_factory);
type=String.format(" (%s/%s)", tls.getProtocols()!=null?String.join(",",tls.getProtocols()):"TLS",
context.getProvider());
}
router.start();
long time=System.currentTimeMillis()-start;
IpAddress local=(IpAddress)router.localAddress();
System.out.printf("\nGossipRouter started in %d ms listening on %s:%s%s\n",
time, bind_addr != null? bind_addr : "0.0.0.0", local.getPort(), type);
}
public enum DumpMessages {
NONE,
REGISTRATION,
ALL;
static DumpMessages parse(String s) {
s = s.trim();
if (s.isEmpty() || s.equalsIgnoreCase("false")) {
return NONE;
} else if (s.equalsIgnoreCase("true")) {
return ALL;
} else {
return valueOf(s.toUpperCase());
}
}
}
static void help() {
System.out.println();
System.out.println("GossipRouter [-port ] [-bind_addr ] [options]");
System.out.println();
System.out.println("Options:");
System.out.println();
System.out.println(" -jmx - Expose attributes and operations via JMX.\n");
System.out.println(" -recv_buf_size - Sets the receive buffer");
System.out.println();
System.out.println(" -solinger - Time for setting SO_LINGER on connections");
System.out.println();
System.out.println(" -expiry - Time for closing idle connections. 0 means don't expire.");
System.out.println();
System.out.println(" -reaper_interval - Time for check for expired connections. 0 means don't check.");
System.out.println();
System.out.println(" -nio - Whether or not to use non-blocking connections (NIO)");
System.out.println();
System.out.println(" -non_blocking_sends - Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759))");
System.out.println();
System.out.println(" -max_send_queue - When sending and non_blocking, how many messages to queue max");
System.out.println();
System.out.println(" -max_length - The max size (in bytes) of a message");
System.out.println();
System.out.println(" -suspect - Whether or not to use send SUSPECT events when a conn is closed");
System.out.println();
System.out.println(" -dump_msgs