
net.openhft.chronicle.map.NodeDiscovery Maven / Gradle / Ivy
/*
* Copyright (C) 2015 higherfrequencytrading.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package net.openhft.chronicle.map;
import net.openhft.chronicle.hash.replication.RemoteNodeValidator;
import net.openhft.chronicle.hash.replication.ReplicationHub;
import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig;
import net.openhft.chronicle.hash.replication.UdpTransportConfig;
import net.openhft.lang.collection.ATSDirectBitSet;
import net.openhft.lang.collection.DirectBitSet;
import net.openhft.lang.io.ByteBufferBytes;
import net.openhft.lang.io.Bytes;
import net.openhft.lang.io.serialization.BytesMarshallable;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static java.nio.ByteBuffer.allocateDirect;
import static java.util.concurrent.TimeUnit.SECONDS;
import static net.openhft.chronicle.map.ConcurrentExpiryMap.getDefaultAddress;
import static net.openhft.chronicle.map.DiscoveryNodeBytesMarshallable.ProposedNodes;
import static net.openhft.chronicle.map.NodeDiscoveryBroadcaster.BOOTSTRAP_BYTES;
import static net.openhft.chronicle.map.NodeDiscoveryBroadcaster.LOG;
interface NodeDiscoveryEventListener {
/**
* called when we have received a UDP message, this is called after the message has been parsed
*/
void onRemoteNodeEvent(KnownNodes remoteNode, ConcurrentExpiryMap proposedIdentifiersWithHost);
}
/**
* @author Rob Austin.
*
* This code is in alpha, and still has to undergo refactoring
*/
class NodeDiscovery {
public static final int DEFAULT_UDP_BROADCAST_PORT = 8129;
public static final short DEFAULT_TCP_PORT = (short) 8123;
public static final int SERIALIZED_ENTRY_SIZE = 1024;
public static final InetAddress IP_MULIT_CAST_GROUP;
static {
InetAddress mask = null;
try {
mask = Inet4Address.getByName("224.0.8.0");
} catch (Exception e) {
mask = null;
}
IP_MULIT_CAST_GROUP = mask;
}
private final AddressAndPort ourAddressAndPort;
private final UdpTransportConfig udpConfig;
private final DiscoveryNodeBytesMarshallable discoveryNodeBytesMarshallable;
private final AtomicReference nodeDiscoveryEventListenerAtomicReference =
new AtomicReference();
private KnownNodes knownNodes;
public NodeDiscovery() throws IOException {
this((short) DEFAULT_UDP_BROADCAST_PORT, DEFAULT_TCP_PORT, getDefaultAddress(), IP_MULIT_CAST_GROUP);
}
public NodeDiscovery(short udpBroadcastPort,
short tcpPort,
@NotNull InetAddress tcpAddress,
@NotNull InetAddress udpMultiCastGroup) throws IOException {
this.ourAddressAndPort = new AddressAndPort(tcpAddress.getAddress(), tcpPort);
this.udpConfig = UdpTransportConfig.multiCast(udpMultiCastGroup, udpBroadcastPort,
ConcurrentExpiryMap.defaultNetworkInterface());
knownNodes = new KnownNodes();
discoveryNodeBytesMarshallable = new DiscoveryNodeBytesMarshallable
(knownNodes, nodeDiscoveryEventListenerAtomicReference, ourAddressAndPort);
final NodeDiscoveryBroadcaster nodeDiscoveryBroadcaster
= new NodeDiscoveryBroadcaster(udpConfig, SERIALIZED_ENTRY_SIZE, discoveryNodeBytesMarshallable);
discoveryNodeBytesMarshallable.setModificationNotifier(nodeDiscoveryBroadcaster);
}
private static Collection toInetSocketCollection(Set source)
throws UnknownHostException {
// make a safe copy
final HashSet addressAndPorts = new HashSet(source);
if (addressAndPorts.isEmpty())
return Collections.emptyList();
ArrayList addresses =
new ArrayList(addressAndPorts.size());
for (final AddressAndPort addressAndPort : addressAndPorts) {
addresses.add(new InetSocketAddress(InetAddress.getByAddress(addressAndPort.address())
.getHostAddress(), addressAndPort.port()));
}
return addresses;
}
static byte proposeRandomUnusedIdentifier(final DirectBitSet knownIdentifiers,
boolean isFirstTime) throws UnknownHostException {
byte possible;
// the first time, rather than choosing a random number, we will choose the last value of the IP
// address as our random number, ( or at least something that is based upon it)
if (isFirstTime) {
byte[] address = InetAddress.getLocalHost().getAddress();
int lastAddress = address[address.length - 1];
if (lastAddress > 127)
lastAddress = lastAddress - 127;
if (lastAddress > 127)
lastAddress = lastAddress - 127;
possible = (byte) lastAddress;
} else
possible = (byte) (Math.random() * 128);
int count = 0;
for (; ; ) {
if (knownIdentifiers.setIfClear(possible)) {
return possible;
}
count++;
if (count == 128) {
throw new IllegalStateException("The grid is full, its not possible for any more nodes to " +
"going the grid.");
}
if (possible == Byte.MAX_VALUE)
possible = 0;
else
possible++;
}
}
public synchronized ReplicationHubFindByName mapByName() throws
IOException, InterruptedException {
final AtomicInteger ourProposedIdentifier = new AtomicInteger();
final AtomicBoolean useAnotherIdentifier = new AtomicBoolean();
final Set knownHostPorts = new ConcurrentSkipListSet();
final DirectBitSet knownAndProposedIdentifiers = new ATSDirectBitSet(new ByteBufferBytes(ByteBuffer.allocate
(128 / 8)));
final AtomicReference countDownLatch
= new AtomicReference(new CountDownLatch(1));
final NodeDiscoveryEventListener nodeDiscoveryEventListener = new NodeDiscoveryEventListener() {
@Override
public void onRemoteNodeEvent(@NotNull final KnownNodes remoteNodes,
@NotNull final ConcurrentExpiryMap proposedIdentifiersWithHost) {
LOG.info("onRemoteNodeEvent " + remoteNodes + ", proposedIdentifiersWithHost=" + proposedIdentifiersWithHost);
knownHostPorts.addAll(remoteNodes.addressAndPorts());
orBitSets(remoteNodes.identifiers(), knownAndProposedIdentifiers);
for (ProposedNodes proposedIdentifierWithHost :
proposedIdentifiersWithHost.values()) {
if (!proposedIdentifierWithHost.addressAndPort().equals(ourAddressAndPort)) {
int proposedIdentifier = proposedIdentifierWithHost.identifier();
if (proposedIdentifier != -1) {
knownAndProposedIdentifiers.set(proposedIdentifier, true);
}
knownHostPorts.add(proposedIdentifierWithHost.addressAndPort());
if (proposedIdentifier == ourProposedIdentifier.get())
useAnotherIdentifier.set(true);
}
}
countDownLatch.get().countDown();
}
};
nodeDiscoveryEventListenerAtomicReference.set(nodeDiscoveryEventListener);
final ProposedNodes ourHostPort = new
ProposedNodes(ourAddressAndPort, (byte) -1);
// to start with we will send a bootstrap that just contains our hostname without and identifier
for (int i = 0; i < 10; i++) {
discoveryNodeBytesMarshallable.sendBootStrap(ourHostPort);
// once the count down latch is trigger we know we go something back from one of the nodes
if (countDownLatch.get().await(i * 20, TimeUnit.MILLISECONDS))
break;
}
// we should now get back some identifiers
// the identifiers will come back to the callback on the nio thread, the update arrives at the
// onRemoteNodeEvent
Thread.sleep(1000);
byte identifier;
// now we are going to propose an identifier
boolean isFistTime = true;
OUTER:
for (; ; ) {
useAnotherIdentifier.set(false);
identifier = proposeRandomUnusedIdentifier(knownAndProposedIdentifiers, isFistTime);
ourProposedIdentifier.set(identifier);
LOG.info("proposing to use identifier=" + identifier);
isFistTime = false;
final ProposedNodes proposedNodes = new
ProposedNodes(ourAddressAndPort, identifier);
Thread.sleep(1000);
countDownLatch.set(new CountDownLatch(1));
for (int i = 0; i < 20; i++) {
discoveryNodeBytesMarshallable.sendBootStrap(proposedNodes);
// once the count down latch is trigger we know we go something back from one of the nodes
if (countDownLatch.get().await(i * 20, TimeUnit.MILLISECONDS)) {
if (useAnotherIdentifier.get()) {
// given that another node host proposed the same identifier, we will choose a different one.
LOG.info("Another node is using identifier=" + identifier + ", " +
"going to have to select another one, we will wait 500ms before continuing");
continue OUTER;
} else {
break OUTER;
}
} else {
LOG.debug("timed-out getting a response from the server so sending another boot-strap " +
"message");
}
}
LOG.info("looks like we are the only node in the grid, so going to use identifier=" + identifier);
break;
}
// we should make a local copy as this may change
final RemoteNodeValidator remoteNodeValidator = new RemoteNodeValidator() {
private final ConcurrentMap identifiers = new ConcurrentHashMap();
@Override
public boolean validate(byte remoteIdentifier, SocketAddress remoteAddress) {
final SocketAddress lastKnownAddress = identifiers.putIfAbsent(remoteIdentifier, remoteAddress);
if (remoteAddress.equals(lastKnownAddress))
return true;
knownNodes.identifiers().set(remoteIdentifier);
return lastKnownAddress == null;
}
};
// remove self
final HashSet knownHostPorts0 = new HashSet<>(knownHostPorts);
knownHostPorts0.remove(ourAddressAndPort);
// add our identifier and host:port to the list of known identifiers
knownNodes.add(ourAddressAndPort, identifier);
/* final TcpTransportAndNetworkConfig tcpConfig = TcpTransportAndNetworkConfig
.of(ourAddressAndPort.getPort(),
toInetSocketCollection(knownHostPorts0));
*/
LOG.info("Using Remote identifier=" + identifier);
nodeDiscoveryEventListenerAtomicReference.set(null);
TcpTransportAndNetworkConfig tcpConfig = TcpTransportAndNetworkConfig.of(8123, new
InetSocketAddress("192.168.1.253", 8123));
final ReplicationHub replicationHub = ReplicationHub.builder()
.tcpTransportAndNetwork(tcpConfig)
.remoteNodeValidator(remoteNodeValidator)
.createWithId(identifier);
return new ReplicationHubFindByName(replicationHub);
}
/**
* bitwise OR's the two bit sets or put another way, merges the source bitset into the
* destination bitset and returns the destination
*
* @param source to or
* @param destination to or
* @return result.
*/
private DirectBitSet orBitSets(@NotNull final DirectBitSet source,
@NotNull final DirectBitSet destination) {
// merges the two bit-sets together
for (int i = (int) source.nextSetBit(0); i > 0;
i = (int) source.nextSetBit(i + 1)) {
try {
destination.set(i, true);
} catch (IndexOutOfBoundsException e) {
LOG.error("", e);
}
}
return destination;
}
}
/**
* Broadcast the nodes host ports and identifiers over UDP, to make it easy to join a grid of remote
* nodes just by name, this functionality requires UDP
*
* @author Rob Austin.
*/
class NodeDiscoveryBroadcaster extends UdpChannelReplicator {
public static final Logger LOG = LoggerFactory.getLogger(NodeDiscoveryBroadcaster.class.getName());
static final ByteBufferBytes BOOTSTRAP_BYTES;
private static final byte UNUSED = (byte) -1;
static {
final String BOOTSTRAP = "BOOTSTRAP";
BOOTSTRAP_BYTES = new ByteBufferBytes(ByteBuffer.allocate(2 + BOOTSTRAP.length()));
BOOTSTRAP_BYTES.write((short) BOOTSTRAP.length());
BOOTSTRAP_BYTES.append(BOOTSTRAP);
}
/**
* @param replicationConfig
* @param externalizable
* @throws java.io.IOException
*/
NodeDiscoveryBroadcaster(
final UdpTransportConfig replicationConfig,
final int serializedEntrySize,
final BytesMarshallable externalizable) throws IOException {
super(replicationConfig, UNUSED);
final UdpSocketChannelEntryWriter writer = new UdpSocketChannelEntryWriter(1024, externalizable, this);
final UdpSocketChannelEntryReader reader = new UdpSocketChannelEntryReader(1024, externalizable);
setReader(reader);
setWriter(writer);
start();
}
static String toString(DirectBitSet bitSet) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < bitSet.size(); i++) {
builder.append(bitSet.get(i) ? '1' : '0');
}
return builder.toString();
}
static class UdpSocketChannelEntryReader implements EntryReader {
private final ByteBuffer in;
private final ByteBufferBytes out;
private final BytesMarshallable externalizable;
/**
* @param serializedEntrySize the maximum size of an entry include the meta data
* @param externalizable supports reading and writing serialize entries
*/
UdpSocketChannelEntryReader(final int serializedEntrySize,
@NotNull final BytesMarshallable externalizable) {
// we make the buffer twice as large just to give ourselves headroom
in = allocateDirect(serializedEntrySize * 2);
out = new ByteBufferBytes(in);
out.limit(0);
in.clear();
this.externalizable = externalizable;
}
/**
* reads entries from the socket till it is empty
*
* @param socketChannel the socketChannel that we will read from
* @throws IOException
* @throws InterruptedException
*/
public void readAll(@NotNull final DatagramChannel socketChannel) throws IOException {
out.clear();
in.clear();
socketChannel.receive(in);
final int bytesRead = in.position();
if (bytesRead < SIZE_OF_SIZE + SIZE_OF_SIZE)
return;
out.limit(in.position());
final int invertedSize = out.readInt();
final int size = out.readInt();
// check the the first 4 bytes are the inverted len followed by the len
// we do this to check that this is a valid start of entry, otherwise we throw it away
if (~size != invertedSize)
return;
if (out.remaining() != size)
return;
externalizable.readMarshallable(out);
}
}
static class UdpSocketChannelEntryWriter implements EntryWriter {
private final ByteBuffer out;
private final ByteBufferBytes in;
@NotNull
private final BytesMarshallable externalizable;
private UdpChannelReplicator udpReplicator;
UdpSocketChannelEntryWriter(final int serializedEntrySize,
@NotNull final BytesMarshallable externalizable,
@NotNull final UdpChannelReplicator udpReplicator) {
this.externalizable = externalizable;
this.udpReplicator = udpReplicator;
// we make the buffer twice as large just to give ourselves headroom
out = allocateDirect(serializedEntrySize * 2);
in = new ByteBufferBytes(out);
}
/**
* @param socketChannel the socketChannel that we will write to
* @throws InterruptedException
* @throws IOException
*/
public int writeAll(@NotNull final DatagramChannel socketChannel)
throws InterruptedException, IOException {
out.clear();
in.clear();
// skip the size inverted
in.skip(SIZE_OF_SIZE);
// skip the size
in.skip(SIZE_OF_SIZE);
long start = in.position();
// writes the contents of
externalizable.writeMarshallable(in);
long size = in.position() - start;
// we'll write the size inverted at the start
in.writeInt(0, ~((int) size));
in.writeInt(SIZE_OF_SIZE, (int) size);
out.limit((int) in.position());
udpReplicator.disableWrites();
return socketChannel.write(out);
}
}
}
class KnownNodes implements BytesMarshallable {
private Bytes activeIdentifiersBitSetBytes;
private ConcurrentSkipListSet addressAndPorts = new ConcurrentSkipListSet();
private ATSDirectBitSet atsDirectBitSet;
KnownNodes() {
this.activeIdentifiersBitSetBytes = new ByteBufferBytes(ByteBuffer.allocate(128 / 8));
this.addressAndPorts = new ConcurrentSkipListSet();
this.atsDirectBitSet = new ATSDirectBitSet(this.activeIdentifiersBitSetBytes);
}
public Set addressAndPorts() {
return addressAndPorts;
}
public void add(AddressAndPort inetSocketAddress, byte identifier) {
identifiers().set(identifier);
addressAndPorts.add(inetSocketAddress);
}
/**
* all the known identifiers
*
* @return
*/
public DirectBitSet identifiers() {
return atsDirectBitSet;
}
@Override
public void readMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes in) throws IllegalStateException {
short size = in.readShort();
for (int i = 0; i < size; i++) {
final AddressAndPort addressAndPort = new AddressAndPort();
addressAndPort.readMarshallable(in);
addressAndPorts.add(addressAndPort);
}
final ByteBufferBytes activeIdentifiersBitSetBytes = new ByteBufferBytes(ByteBuffer.allocate(128 / 8));
activeIdentifiersBitSetBytes.readMarshallable(in);
final ATSDirectBitSet bitset = new ATSDirectBitSet(activeIdentifiersBitSetBytes);
for (long next = bitset.nextSetBit(0); next > 0; next = bitset.nextSetBit(next + 1)) {
atsDirectBitSet.set(next);
}
}
@Override
public void writeMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes out) {
// make a safe copy
final Set addressAndPorts = new HashSet(this.addressAndPorts);
// write the size
out.writeShort(addressAndPorts.size());
for (AddressAndPort bytesMarshallable : addressAndPorts) {
bytesMarshallable.writeMarshallable(out);
}
activeIdentifiersBitSetBytes.clear();
activeIdentifiersBitSetBytes.writeMarshallable(out);
}
@Override
public String toString() {
return "RemoteNodes{" +
" addressAndPorts=" + addressAndPorts +
", bitSet=" + NodeDiscoveryBroadcaster.toString(atsDirectBitSet) +
'}';
}
}
class AddressAndPort implements Comparable, BytesMarshallable {
final static int INADDRSZ = 16;
final static int INT16SZ = 2;
private byte[] address;
private short port;
AddressAndPort(byte[] address, short port) {
this.address = address;
this.port = port;
}
public AddressAndPort() {
}
static String numericToTextFormat(byte[] src) {
if (src.length == 4) {
return (src[0] & 0xff) + "." + (src[1] & 0xff) + "." + (src[2] & 0xff) + "." + (src[3] & 0xff);
}
StringBuilder sb = new StringBuilder(39);
for (int i = 0; i < (INADDRSZ / INT16SZ); i++) {
sb.append(Integer.toHexString(((src[i << 1] << 8) & 0xff00)
| (src[(i << 1) + 1] & 0xff)));
if (i < (INADDRSZ / INT16SZ) - 1) {
sb.append(":");
}
}
return sb.toString();
}
public byte[] getAddress() {
return address;
}
public short getPort() {
return port;
}
public byte[] address() {
return address;
}
public short port() {
return port;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || AddressAndPort.class != o.getClass()) return false;
AddressAndPort that = (AddressAndPort) o;
if (port != that.port) return false;
return Arrays.equals(address, that.address);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(address);
result = 31 * result + (int) port;
return result;
}
@Override
public int compareTo(AddressAndPort o) {
int i = 0;
for (byte b : address) {
int compare = Byte.compare(b, o.address[i++]);
if (compare != 0)
return compare;
}
return Short.compare(port, o.port);
}
@Override
public String toString() {
return "{" +
"address=" + numericToTextFormat(address) +
", port=" + port +
'}';
}
@Override
public void readMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes in) throws IllegalStateException {
short len = in.readShort();
address = new byte[len];
for (int i = 0; i < len; i++) {
address[i] = in.readByte();
}
port = in.readShort();
}
@Override
public void writeMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes out) {
out.writeShort(getAddress().length);
for (byte address : getAddress()) {
out.write(address);
}
out.writeShort(port);
}
}
class DiscoveryNodeBytesMarshallable implements BytesMarshallable {
final ConcurrentExpiryMap proposedIdentifiersWithHost = new
ConcurrentExpiryMap(AddressAndPort.class,
ProposedNodes.class);
private final KnownNodes remoteNode;
private final AtomicBoolean bootstrapRequired = new AtomicBoolean();
private final AtomicReference nodeDiscoveryEventListener;
Replica.ModificationNotifier modificationNotifier;
private AddressAndPort sourceAddressAndPort = new AddressAndPort();
private ProposedNodes ourProposedIdentifier;
private AddressAndPort ourAddressAndPort;
public DiscoveryNodeBytesMarshallable(@NotNull final KnownNodes remoteNode,
@NotNull final AtomicReference
nodeDiscoveryEventListener,
@NotNull final AddressAndPort ourAddressAndPort) {
this.remoteNode = remoteNode;
this.nodeDiscoveryEventListener = nodeDiscoveryEventListener;
this.ourAddressAndPort = ourAddressAndPort;
}
public ProposedNodes getOurProposedIdentifier() {
return ourProposedIdentifier;
}
private void setOurProposedIdentifier(ProposedNodes ourProposedIdentifier) {
this.ourProposedIdentifier = ourProposedIdentifier;
}
public void setModificationNotifier(Replica.ModificationNotifier modificationNotifier) {
this.modificationNotifier = modificationNotifier;
}
public KnownNodes getRemoteNodes() {
return remoteNode;
}
/**
* this is used to tell nodes that are connecting to us which host and ports are in our grid,
* along with all the identifiers.
*/
@Override
public void writeMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes out) {
if (bootstrapRequired.getAndSet(false)) {
writeBootstrap(out);
return;
}
// the host and port the message came from
ourAddressAndPort.writeMarshallable(out);
remoteNode.writeMarshallable(out);
// we are no going to broadcast all the nodes that have been bootstaping in the last second
proposedIdentifiersWithHost.expireEntries(System.currentTimeMillis() - SECONDS.toMillis(1));
proposedIdentifiersWithHost.writeMarshallable(out);
}
private boolean writeBootstrap(Bytes out) {
final ProposedNodes ourProposedIdentifier = getOurProposedIdentifier();
if (ourProposedIdentifier == null || ourProposedIdentifier.addressAndPort == null)
return false;
BOOTSTRAP_BYTES.clear();
out.write(BOOTSTRAP_BYTES, BOOTSTRAP_BYTES.position(), BOOTSTRAP_BYTES.remaining());
ourProposedIdentifier.writeMarshallable(out);
return true;
}
/**
* @param in
* @return returns true if the UDP message contains the text 'BOOTSTRAP'
*/
private ProposedNodes readBootstrapMessage(Bytes in) {
final long start = in.position();
try {
long size = BOOTSTRAP_BYTES.limit();
if (size > in.remaining())
return null;
// reads the text bootstrap
for (int i = 0; i < size; i++) {
final byte byteRead = in.readByte();
final byte expectedByte = BOOTSTRAP_BYTES.readByte(i);
if (!(expectedByte == byteRead))
return null;
}
final ProposedNodes result = new ProposedNodes();
result.readMarshallable(in);
return result;
} finally {
in.position(start);
}
}
@Override
public void readMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes in) throws IllegalStateException {
final ProposedNodes bootstrap = readBootstrapMessage(in);
if (bootstrap != null) {
if (bootstrap.addressAndPort().equals(this.ourAddressAndPort))
return;
if (LOG.isDebugEnabled())
LOG.debug("Received Bootstrap from " + bootstrap);
proposedIdentifiersWithHost.put(bootstrap.addressAndPort, bootstrap);
// we've received a bootstrap message so will will now rebroadcast what we know,
// after a random delay
// Thread.sleep((int) (Math.random() * 9.0));
// this is used to turn on the OP_WRITE, so that we can broadcast back the known host and
// ports in the grid
onChange();
return;
}
// the host and port the message came from
this.sourceAddressAndPort.readMarshallable(in);
if (sourceAddressAndPort.equals(ourAddressAndPort))
return;
LOG.debug("Received Proposal");
this.remoteNode.readMarshallable(in);
this.proposedIdentifiersWithHost.readMarshallable(in);
NodeDiscoveryEventListener listener = nodeDiscoveryEventListener.get();
if (listener != null)
listener.onRemoteNodeEvent(remoteNode, proposedIdentifiersWithHost);
}
public void onChange() {
if (modificationNotifier != null)
modificationNotifier.onChange();
}
/**
* sends a bootstrap message to the other nodes in the grid, the bootstrap message contains the
* host:port and perhaps even proposed identifier of the node that sent it.
*
* @param proposedNodes
*/
public void sendBootStrap(ProposedNodes proposedNodes) {
setOurProposedIdentifier(proposedNodes);
bootstrapRequired.set(true);
onChange();
}
static class ProposedNodes implements BytesMarshallable {
private byte identifier;
private long timestamp;
private AddressAndPort addressAndPort;
public ProposedNodes() {
}
ProposedNodes(@NotNull final AddressAndPort addressAndPort,
byte identifier) {
this.addressAndPort = addressAndPort;
this.identifier = identifier;
this.timestamp = System.currentTimeMillis();
}
public byte identifier() {
return identifier;
}
AddressAndPort addressAndPort() {
return addressAndPort;
}
@Override
public void readMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes in) throws IllegalStateException {
addressAndPort = new AddressAndPort();
addressAndPort.readMarshallable(in);
timestamp = in.readLong();
identifier = in.readByte();
}
@Override
public void writeMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes out) {
addressAndPort.writeMarshallable(out);
out.writeLong(timestamp);
out.writeByte(identifier);
}
@Override
public String toString() {
return "{" +
"identifier=" + identifier +
", timestamp=" + timestamp +
", addressAndPort=" + addressAndPort +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || ProposedNodes.class != o.getClass()) return false;
ProposedNodes that = (ProposedNodes) o;
if (identifier != that.identifier) return false;
return addressAndPort.equals(that.addressAndPort);
}
@Override
public int hashCode() {
int result = (int) identifier;
result = 31 * result + addressAndPort.hashCode();
return result;
}
}
}
class ConcurrentExpiryMap implements BytesMarshallable {
final ConcurrentMap map = new
ConcurrentHashMap();
private final Class kClass;
private final Class vClass;
// this is used for expiry
private final Queue>> queue = new ConcurrentLinkedQueue>>();
ConcurrentExpiryMap(Class kClass, Class vClass) {
this.kClass = kClass;
this.vClass = vClass;
}
public static void main(String... args) {
String ip;
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
// filters out 127.0.0.1 and inactive interfaces
if (iface.isLoopback() || !iface.isUp())
continue;
Enumeration addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
ip = addr.getHostAddress();
System.out.println(iface.getDisplayName() + " " + ip);
}
}
} catch (SocketException e) {
throw new RuntimeException(e);
}
}
/**
* private NetworkInterface defaultNetworkInterface() throws SocketException { NetworkInterface
* networkInterface = null
*
* for (String suggestedName : new String[]{"en0", "eth0"}) { networkInterface =
* NetworkInterface.getByName(suggestedName); if (networkInterface != null) break; } return
* networkInterface; }
*
* /**
*
* @return the default network interface, or
* @throws SocketException
*/
public static NetworkInterface defaultNetworkInterface() throws SocketException {
NetworkInterface networkInterface = null;
for (String suggestedName : new String[]{"en0", "eth0"}) {
networkInterface = NetworkInterface.getByName(suggestedName);
if (networkInterface != null)
break;
}
if (networkInterface != null)
return networkInterface;
Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();
if (networkInterfaces == null || !networkInterfaces.hasMoreElements())
return null;
return networkInterfaces.nextElement();
}
public static InetAddress getDefaultAddress() throws SocketException {
NetworkInterface networkInterface = ConcurrentExpiryMap.defaultNetworkInterface();
Enumeration inetAddresses = networkInterface.getInetAddresses();
// we will take the last one as this is likely to be an ipv address which is easier to work with
InetAddress inetAddress = null;
for (; inetAddresses.hasMoreElements(); ) {
inetAddress = inetAddresses.nextElement();
}
if (inetAddress == null)
throw new IllegalStateException();
return inetAddress;
}
@Override
public String toString() {
return "ConcurrentExpiryMap{" + map + '}';
}
@Override
public void readMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes in) throws IllegalStateException {
short size = in.readShort();
try {
for (int i = 0; i < size; i++) {
final K k = kClass.newInstance();
k.readMarshallable(in);
final V v = vClass.newInstance();
v.readMarshallable(in);
map.put(k, v);
}
} catch (Exception e) {
LOG.error("", e);
}
}
@Override
public void writeMarshallable(@net.openhft.lang.model.constraints.NotNull Bytes out) {
final Map safeCopy = new HashMap(map);
out.writeShort(safeCopy.size());
for (Map.Entry entry : safeCopy.entrySet()) {
entry.getKey().writeMarshallable(out);
entry.getValue().writeMarshallable(out);
}
}
void put(final K k, final V v) {
map.put(k, v);
final W w = new W(v);
queue.add(new Map.Entry>() {
@Override
public K getKey() {
return k;
}
@Override
public W getValue() {
return w;
}
@Override
public W setValue(W value) {
throw new UnsupportedOperationException();
}
});
}
java.util.Collection values() {
return map.values();
}
void expireEntries(long timeOlderThan) {
for (; ; ) {
final Map.Entry> e = this.queue.peek();
if (e == null)
break;
if (e.getValue().timestamp < timeOlderThan) {
// only remote it if it has not changed
map.remove(e.getKey(), e.getValue().v);
}
this.queue.poll();
}
}
class W {
final long timestamp;
final V v;
W(V v) {
this.v = v;
this.timestamp = System.currentTimeMillis();
}
}
}