package org.infinispan.test.fwk;
import org.jgroups.Address;
import org.jgroups.Event;
import org.jgroups.PhysicalAddress;
import org.jgroups.View;
import org.jgroups.ViewId;
import org.jgroups.annotations.Property;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.protocols.DISCARD;
import org.jgroups.protocols.Discovery;
import org.jgroups.protocols.PingData;
import org.jgroups.protocols.pbcast.JoinRsp;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Promise;
import org.jgroups.util.Tuple;
import org.jgroups.util.UUID;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* This protocol allows for discovery to happen via data structures maintained
* in memory rather than relying on JGroup's transport. This allows for
* discovery to be much faster and predictable because it only relies on java
* method calls rather than network calls. Clearly, this protocol only works
* for clusters that are created in memory.
*
* @author Galder ZamarreƱo
* @since 5.0
*/
public class TEST_PING extends Discovery {
@Property(description="Test name. Default is empty String.")
private String testName = "";
private DISCARD discard;
// volatile in case resurrection happens from a different thread
private volatile boolean stopped;
// Note: Thread locals could work but if two nodes of the same cluster are
// started from different threads, a thread local based solution would not
// work, so we're sticking to an static solution
// <Test Name, Cluster Name -> <Node Name -> Discovery>>
private static ConcurrentMap<DiscoveryKey, ConcurrentMap<Address, TEST_PING>> all =
new ConcurrentHashMap<DiscoveryKey, ConcurrentMap<Address, TEST_PING>>();
static {
ClassConfigurator.addProtocol((short) 1320, TEST_PING.class);
}
@Override
protected List<PingData> findMembers(Promise<JoinRsp> promise,
int numExpectedRsps, boolean breakOnCoord, ViewId viewId) {
if (!stopped) {
Map<Address, TEST_PING> discoveries = registerInDiscoveries();
// Only send message if DISCARD is not used, or if DISCARD is
// configured but it's not discarding messages.
boolean discardEnabled = isDiscardEnabled(this);
if (!discardEnabled) {
if (!discoveries.isEmpty()) {
LinkedList<PingData> rsps = new LinkedList<PingData>();
// Make sure that concurrent startups within a test won't mess up discovery
synchronized (discoveries) {
for (TEST_PING discovery : discoveries.values()) {
// Avoid sending to self! Since there are single instances of
// discovery protocol in each node, just compare them by ref.
boolean traceEnabled = log.isTraceEnabled();
if (discovery != this) {
boolean remoteDiscardEnabled = isDiscardEnabled(discovery);
if (!remoteDiscardEnabled && !discovery.stopped) {
addPingRsp(rsps, discovery);
} else if (discovery.stopped) {
log.debug(String.format(
"%s is stopped, so no ping responses will be received",
discovery.getLocalAddr()));
} else {
if (traceEnabled)
log.trace("Skipping sending response cos DISCARD is on");
// If discard is, add an empty response
addPingRsp(new LinkedList<PingData>(), discovery);
}
} else {
if (traceEnabled)
log.trace("Skipping sending discovery to self");
}
}
}
return rsps;
} else {
log.debug("No other nodes yet, so skip sending get-members request");
return new LinkedList<PingData>();
}
} else {
log.debug("Not sending discovery because DISCARD is on");
return new LinkedList<PingData>();
}
} else {
log.debug("Discovery protocol already stopped, so don't look for members");
return new LinkedList<PingData>();
}
}
private boolean isDiscardEnabled(TEST_PING discovery) {
// Not pretty but since this protocol does not rely on the transport, the
// only possible way to discard messages is by hacking the protocol itself.
List<Protocol> protocols = discovery.getProtocolStack().getProtocols();
for (Protocol protocol : protocols) {
if (protocol instanceof DISCARD) {
discovery.discard = (DISCARD) protocol;
}
}
return discovery.discard != null && discovery.discard.isDiscardAll();
}
private void addPingRsp(LinkedList<PingData> rsps, TEST_PING discovery) {
// Rather than relying on transport (PING) or your own multicast channel
// (MPING), talk to other discovery instances directly via Java method
// calls and discover the other nodes in the cluster.
// Add mapping of remote's address -> physical addr to the local cache
mapAddrWithPhysicalAddr(this, discovery);
// Add mapping of local's address -> physical addr to the remote cache
mapAddrWithPhysicalAddr(discovery, this);
Address localAddr = discovery.getLocalAddr();
List<PhysicalAddress> physicalAddrs = Arrays.asList((PhysicalAddress)
discovery.down(new Event(Event.GET_PHYSICAL_ADDRESS, localAddr)));
String logicalName = UUID.get(localAddr);
PingData pingRsp = new PingData(localAddr, discovery.getJGroupsView(),
discovery.isServer(), logicalName, physicalAddrs);
if (log.isTraceEnabled())
log.trace(String.format("Returning ping rsp: %s", pingRsp));
rsps.add(pingRsp);
}
private void mapAddrWithPhysicalAddr(TEST_PING local, TEST_PING remote) {
PhysicalAddress physical_addr = (PhysicalAddress)
remote.down(new Event(Event.GET_PHYSICAL_ADDRESS, remote.getLocalAddr()));
local.down(new Event(Event.SET_PHYSICAL_ADDRESS,
new Tuple<Address, PhysicalAddress>(remote.getLocalAddr(), physical_addr)));
if (log.isTraceEnabled())
log.trace(String.format("Map %s with physical address %s in %s",
remote.getLocalAddr(), physical_addr, local));
}
private Map<Address, TEST_PING> registerInDiscoveries() {
DiscoveryKey key = new DiscoveryKey(testName, group_addr);
ConcurrentMap<Address, TEST_PING> discoveries = all.get(key);
if (discoveries == null) {
discoveries = new ConcurrentHashMap<Address, TEST_PING>();
ConcurrentMap ret = all.putIfAbsent(key, discoveries);
if (ret != null)
discoveries = ret;
}
boolean traceEnabled = log.isTraceEnabled();
if (traceEnabled)
log.trace(sf("Discoveries for %s are : %s", key, discoveries));
TEST_PING prev = discoveries.putIfAbsent(local_addr, this);
if (prev == null && traceEnabled)
log.trace(sf("Add discovery for %s to cache. The cache now contains: %s",
local_addr, discoveries));
return discoveries;
}
@Override
public void stop() {
log.debug(String.format("Stop discovery for %s", local_addr));
super.stop();
DiscoveryKey key = new DiscoveryKey(testName, group_addr);
Map<Address, TEST_PING> discoveries = all.get(key);
if (discoveries != null) {
removeDiscovery(key, discoveries);
} else {
log.debug(String.format(
"Test (%s) started but not registered discovery", key));
}
stopped = true;
}
private void removeDiscovery(DiscoveryKey key, Map<Address, TEST_PING> discoveries) {
discoveries.remove(local_addr);
if (discoveries.isEmpty()) {
boolean removed = all.remove(key, discoveries);
if (!removed && all.containsKey(key)) {
throw new IllegalStateException(String.format(
"Concurrent discovery removal for test=%s but not removed??",
testName));
}
}
}
protected Address getLocalAddr() {
return local_addr;
}
protected View getJGroupsView() {
return view;
}
protected boolean isServer() {
return is_server;
}
@Override
public Collection<PhysicalAddress> fetchClusterMembers(String cluster_name) {
// findMembers overriden, so no callback to this method
return null;
}
@Override
public boolean sendDiscoveryRequestsInParallel() {
// findMembers overriden, so no callback to this method
return false;
}
@Override
public boolean isDynamic() {
return false;
}
@Override
public String toString() {
return "TEST_PING@" + local_addr;
}
private static String sf(String format, Object ... args) {
return String.format(format, args);
}
static private class DiscoveryKey {
final String testName;
final String clusterName;
private DiscoveryKey(String testName, String clusterName) {
this.clusterName = clusterName;
this.testName = testName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DiscoveryKey that = (DiscoveryKey) o;
if (clusterName != null ?
!clusterName.equals(that.clusterName)
: that.clusterName != null)
return false;
if (testName != null ?
!testName.equals(that.testName) : that.testName != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = testName != null ? testName.hashCode() : 0;
result = 31 * result +
(clusterName != null ? clusterName.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "DiscoveryKey{" +
"clusterName='" + clusterName + '\'' +
", testName='" + testName + '\'' +
'}';
}
}
}