All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.map.NodeDiscovery Maven / Gradle / Ivy

There is a newer version: 3.27ea0
Show newest version
/*
 *     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(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy