com.addthis.meshy.MeshyServer.java Source code

Java tutorial

Introduction

Here is the source code for com.addthis.meshy.MeshyServer.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.addthis.meshy;

import javax.annotation.Nullable;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import com.addthis.basis.util.LessBytes;
import com.addthis.basis.util.Parameter;

import com.addthis.meshy.service.message.MessageFileSystem;
import com.addthis.meshy.service.peer.PeerSource;

import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.hash.Hashing;

import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Counter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.Promise;
import static java.nio.charset.StandardCharsets.UTF_8;

public class MeshyServer extends Meshy {
    protected static final Logger log = LoggerFactory.getLogger(MeshyServer.class);

    private static final boolean autoMesh = Parameter.boolValue("meshy.autoMesh", false);
    // permit peering in local VM
    private static final boolean allowPeerLocal = Parameter.boolValue("meshy.peer.local", true);
    private static final int autoMeshTimeout = Parameter.intValue("meshy.autoMeshTimeout", 60000);

    static final Counter peerCountMetric = Metrics.newCounter(Meshy.class, "peerCount");

    private static final ArrayList<byte[]> vmLocalNet = new ArrayList<>(3);
    private static final HashMap<String, VirtualFileSystem[]> vfsCache = new HashMap<>();
    private static final HashSet<String> blockedPeers = new HashSet<>();

    public static final MessageFileSystem messageFileSystem = new MessageFileSystem();

    static {
        /* collect local valid interfaces for fast lookup */
        try {
            Enumeration<NetworkInterface> enNet = NetworkInterface.getNetworkInterfaces();
            while (enNet.hasMoreElements()) {
                NetworkInterface net = enNet.nextElement();
                if (net.isLoopback() || net.isPointToPoint() || !net.isUp()) {
                    continue;
                }
                Enumeration<InetAddress> enAddr = net.getInetAddresses();
                while (enAddr.hasMoreElements()) {
                    byte[] addr = enAddr.nextElement().getAddress();
                    /* only allow IPV4 at the moment */
                    if (addr.length == 4) {
                        vmLocalNet.add(addr);
                    }
                }
            }
            log.info("local net: {}", vmLocalNet);

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

    }

    public static void resetFileSystems() {
        vfsCache.clear();
    }

    /**
     * cache for use of multiple servers in one VM
     */
    protected static VirtualFileSystem[] loadFileSystems(File rootDir) {
        String cacheKey = rootDir != null ? rootDir.getAbsolutePath() : ".";
        VirtualFileSystem[] vfsList = vfsCache.get(cacheKey);
        if (vfsList == null) {
            LinkedList<VirtualFileSystem> load = new LinkedList<>();
            load.add(messageFileSystem);
            if (rootDir != null) {
                load.add(new LocalFileSystem(rootDir));
            }
            String registerVMs = Parameter.value("meshy.vms");
            if (registerVMs != null) {
                for (String vm : Splitter.on(",").omitEmptyStrings().trimResults().split(registerVMs)) {
                    try {
                        load.add((VirtualFileSystem) Class.forName(vm).newInstance());
                    } catch (Throwable t) {
                        log.error("failure loading VM {}", vm, t);
                    }
                }
            }
            vfsList = load.toArray(new VirtualFileSystem[load.size()]);
            vfsCache.put(cacheKey, vfsList);
        }
        return vfsList;
    }

    private final int serverPort;
    private final File rootDir;
    private final VirtualFileSystem[] filesystems;
    private final EventLoopGroup bossGroup;
    private final String serverUuid;
    private final MeshyServerGroup group;
    private final AtomicInteger serverPeers;

    private final Promise<?> closeFuture;

    private final InetSocketAddress serverLocal;
    private final NetworkInterface serverNetIf;

    public MeshyServer(int port) throws IOException {
        this(port, new File("."));
    }

    public MeshyServer(int port, File rootDir) throws IOException {
        this(port, rootDir, null, new MeshyServerGroup());
    }

    public MeshyServer(final int port, final File rootDir, @Nullable String[] netif, final MeshyServerGroup group)
            throws IOException {
        super();
        this.group = group;
        this.rootDir = rootDir;
        this.filesystems = loadFileSystems(rootDir);
        this.serverPeers = new AtomicInteger(0);
        bossGroup = new NioEventLoopGroup(1);
        ServerBootstrap bootstrap = new ServerBootstrap()
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .option(ChannelOption.SO_BACKLOG, 1024).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)
                .option(ChannelOption.SO_REUSEADDR, true)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childOption(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, HIGH_WATERMARK)
                .childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, LOW_WATERMARK)
                .channel(NioServerSocketChannel.class).group(bossGroup, workerGroup)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(final NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelState(MeshyServer.this, ch));
                    }
                });
        /* bind to one or more interfaces, if supplied, otherwise all */
        if ((netif == null) || (netif.length == 0)) {
            ServerSocketChannel serverChannel = (ServerSocketChannel) bootstrap.bind(new InetSocketAddress(port))
                    .syncUninterruptibly().channel();
            serverLocal = serverChannel.localAddress();
        } else {
            InetSocketAddress primaryServerLocal = null;
            for (String net : netif) {
                NetworkInterface nicif = NetworkInterface.getByName(net);
                if (nicif == null) {
                    log.warn("missing speficied NIC: {}", net);
                    continue;
                }
                for (InterfaceAddress addr : nicif.getInterfaceAddresses()) {
                    InetAddress inAddr = addr.getAddress();
                    if (inAddr.getAddress().length != 4) {
                        log.trace("skip non-ipV4 address: {}", inAddr);
                        continue;
                    }
                    ServerSocketChannel serverChannel = (ServerSocketChannel) bootstrap
                            .bind(new InetSocketAddress(inAddr, port)).syncUninterruptibly().channel();
                    if (primaryServerLocal != null) {
                        log.info("server [{}-*] binding to extra address: {}", super.getUUID(), primaryServerLocal);
                    }
                    primaryServerLocal = serverChannel.localAddress();
                }
            }
            if (primaryServerLocal == null) {
                throw new IllegalArgumentException("no valid interface / port specified");
            }
            serverLocal = primaryServerLocal;
        }
        this.serverNetIf = NetworkInterface.getByInetAddress(serverLocal.getAddress());
        this.serverPort = serverLocal.getPort();
        if (serverNetIf != null) {
            serverUuid = super.getUUID() + "-" + serverPort + "-" + serverNetIf.getName();
        } else {
            serverUuid = super.getUUID() + "-" + serverPort;
        }
        log.info("server [{}] on {} @ {}", getUUID(), serverLocal, rootDir);
        closeFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
        workerGroup.terminationFuture().addListener((Future<Object> workerFuture) -> {
            bossGroup.terminationFuture().addListener((Future<Object> bossFuture) -> {
                if (!workerFuture.isSuccess()) {
                    closeFuture.tryFailure(workerFuture.cause());
                } else if (!bossFuture.isSuccess()) {
                    closeFuture.tryFailure(bossFuture.cause());
                } else {
                    closeFuture.trySuccess(null);
                }
            });
        });
        addMessageFileSystemPaths();
        group.join(this);
        if (autoMesh) {
            startAutoMesh(serverPort, autoMeshTimeout);
        }
    }

    private void addMessageFileSystemPaths() {
        messageFileSystem.addPath("/meshy/" + getUUID() + "/stats", (fileName, options) -> {
            StringBuilder sb = new StringBuilder();
            for (String line : group.getLastStats()) {
                sb.append(line);
                sb.append("\n");
            }
            return sb.toString().getBytes(UTF_8);
        });
        messageFileSystem.addPath("/meshy/statsMap", (fileName, options) -> {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Map<String, Integer> stats = group.getLastStatsMap();
            try {
                LessBytes.writeInt(stats.size(), out);
                for (Map.Entry<String, Integer> e : stats.entrySet()) {
                    LessBytes.writeString(e.getKey(), out);
                    LessBytes.writeInt(e.getValue(), out);
                }
            } catch (IOException e) {
                //ByteArrayOutputStreams do not throw IOExceptions
            }
            return out.toByteArray();
        });
        messageFileSystem.addPath("/meshy/host/ban", (fileName, options) -> {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            String hostName = options.get("host");
            synchronized (blockedPeers) {
                try {
                    if (hostName == null) {
                        for (String peer : blockedPeers) {
                            LessBytes.writeString(peer + "\n", out);
                        }
                    } else {
                        if (blockedPeers.contains(hostName)) {
                            LessBytes.writeString(hostName + " already in blocked peers\n", out);
                        } else {
                            LessBytes.writeString(hostName + " added to blocked peers\n", out);
                        }
                        if (dropPeer(hostName)) {
                            LessBytes.writeString(hostName + " connection closed (async)\n", out);
                        } else {
                            LessBytes.writeString(hostName + " connection not found\n", out);
                        }
                    }
                } catch (IOException ignored) {
                }
            }
            return out.toByteArray();
        });
    }

    public File getRootDir() {
        return rootDir;
    }

    public MeshyServer[] getMembers() {
        return group.getMembers();
    }

    @Override
    public String getUUID() {
        return serverUuid;
    }

    @Override
    protected void channelConnected(Channel channel, ChannelState channelState) {
        super.channelConnected(channel, channelState);
        /* servers peer with other servers once a channel comes up */
        // assign unique id (local or remote inferred)
        channelState.setName("temp-uuid-" + nextSession.incrementAndGet());
        InetSocketAddress address = (InetSocketAddress) channel.remoteAddress();
        if (channel.parent() == null) {
            log.debug("{} >>> starting peering with {}", MeshyServer.this, address);
            new PeerSource(this, channelState.getName());
        }
    }

    @Override
    protected void channelClosed(Channel channel, ChannelState channelState) {
        super.channelClosed(channel, channelState);
        if (channelState.getRemoteAddress() != null) {
            serverPeers.decrementAndGet();
        }
    }

    @Override
    public void close() {
        log.debug("{} exiting", this);
        closeAsync().syncUninterruptibly();
    }

    @Override
    public Future<?> closeAsync() {
        bossGroup.shutdownGracefully();
        super.closeAsync();
        return closeFuture;
    }

    public Future<?> closeFuture() {
        return closeFuture;
    }

    public int getLocalPort() {
        return serverPort;
    }

    public NetworkInterface getNetIf() {
        return serverNetIf;
    }

    public InetSocketAddress getLocalAddress() {
        return serverLocal;
    }

    public VirtualFileSystem[] getFileSystems() {
        return filesystems;
    }

    /**
     * blocking call.  used by main() command-line forced peering
     */
    public void connectPeer(InetSocketAddress address) {
        ChannelFuture future = connectToPeer(null, address);
        if (future == null) {
            log.info("{} peer connect returned null future to {}", MeshyServer.this, address);
            return;
        }
        /* wait for connection to complete */
        future.awaitUninterruptibly();
        if (!future.isSuccess()) {
            log.warn("{} peer connect fail to {}", MeshyServer.this, address);
        }
    }

    public boolean blockPeer(final String peerUuid) {
        synchronized (blockedPeers) {
            blockedPeers.add(peerUuid);
        }
        return dropPeer(peerUuid);
    }

    public boolean dropPeer(final String peerUuid) {
        boolean hitAtLeastOnce = false;
        synchronized (connectedChannels) {
            for (ChannelState state : connectedChannels) {
                if (state.getName().equals(peerUuid) && state.getChannel().isOpen()) {
                    state.getChannel().close();
                    hitAtLeastOnce = true;
                }
            }
        }
        return hitAtLeastOnce;
    }

    /**
     * connects to a peer address.  if peerUuid is not know, method blocks
     */
    @Nullable
    public ChannelFuture connectToPeer(@Nullable final String peerUuid, final InetSocketAddress pAddr) {
        synchronized (blockedPeers) {
            if (peerUuid != null && blockedPeers.contains(peerUuid)) {
                return null;
            }
        }
        updateLastEventTime();
        log.debug("{} request connect to {} @ {}", MeshyServer.this, peerUuid, pAddr);
        /* skip peering with self (and other meshes started in the same vm) */
        if (peerUuid != null && group.hasUuid(peerUuid)) {
            log.debug("{} skipping {} .. it's me", this, peerUuid);
            return null;
        }
        /* skip peering with self */
        try {
            for (Enumeration<NetworkInterface> eni = NetworkInterface.getNetworkInterfaces(); eni
                    .hasMoreElements();) {
                for (Enumeration<InetAddress> eaddr = eni.nextElement().getInetAddresses(); eaddr
                        .hasMoreElements();) {
                    InetAddress addr = eaddr.nextElement();
                    if (addr.equals(pAddr.getAddress()) && pAddr.getPort() == serverPort) {
                        log.debug("{} skipping myself {} : {}", this, addr, serverPort);
                        return null;
                    }
                }
            }
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        /* check for local peering */
        if (!allowPeerLocal) {
            byte[] peerAddr = pAddr.getAddress().getAddress();
            for (byte[] addr : vmLocalNet) {
                if (LessBytes.equals(peerAddr, addr)) {
                    log.info("peer reject local {}", pAddr);
                    return null;
                }
            }
        }
        /* skip peering again */
        log.debug("{} peer.check (uuid={} addr={})", this, peerUuid, pAddr);
        synchronized (connectedChannels) {
            for (ChannelState channelState : connectedChannels) {
                log.trace(" --> state={}", channelState);
                if (peerUuid != null && channelState.getName() != null && channelState.getName().equals(peerUuid)) {
                    log.trace("{} 1.peer.uuid {} already connected", this, peerUuid);
                    return null;
                }
                InetSocketAddress remoteAddr = channelState.getRemoteAddress();
                if (remoteAddr != null && remoteAddr.equals(pAddr)) {
                    log.trace("{} 2.peer.addr {} already connected", this, pAddr);
                    return null;
                }
            }
            /* already actively peering with this uuid */
            if (peerUuid != null && !inPeering.add(peerUuid)) {
                log.trace("{} skip already peering {}", MeshyServer.this, peerUuid);
                return null;
            }
        }
        log.debug("{} connecting to {} @ {}", this, peerUuid, pAddr);
        /* connect to peer */
        ChannelFuture connectionFuture = connect(pAddr);
        if (peerUuid != null) {
            connectionFuture.addListener(f -> {
                if (!f.isSuccess()) {
                    synchronized (connectedChannels) {
                        inPeering.remove(peerUuid);
                    }
                }
            });
        }
        return connectionFuture;
    }

    public boolean promoteToNamedServerPeer(ChannelState peerState, String newUuid, InetSocketAddress newAddr) {
        synchronized (connectedChannels) {
            for (ChannelState channelState : connectedChannels) {
                if (newUuid.equals(channelState.getName())) {
                    log.info("rejecting peerage for {} @ {} (to {} @ {}) because uuid matches existing {} for: {}",
                            peerState.getName(), peerState.getChannelRemoteAddress(), newUuid, newAddr,
                            channelState, this);
                    return false;
                }
                if (newAddr.equals(channelState.getRemoteAddress())) {
                    log.info(
                            "rejecting peerage for {} @ {} (to {} @ {}) because address matches existing {} for: {}",
                            peerState.getName(), peerState.getChannelRemoteAddress(), newUuid, newAddr,
                            channelState, this);
                    return false;
                }
            }
            log.info("promoting {} @ {} to named server peer as {} @ {} for: {}", peerState.getName(),
                    peerState.getChannelRemoteAddress(), newUuid, newAddr, this);
            peerState.setName(newUuid);
            peerState.setRemoteAddress(newAddr);
            serverPeers.incrementAndGet();
            return true;
        }
    }

    public int getServerPeerCount() {
        return serverPeers.get();
    }

    public boolean shouldBeConnector(String newUuid) {
        int myHash = Hashing.murmur3_32().hashUnencodedChars(getUUID()).asInt();
        int peerHash = Hashing.murmur3_32().hashUnencodedChars(newUuid).asInt();
        boolean useGreater = ((myHash ^ peerHash) & 0x1) == 0;

        int compareUuidStrings = getUUID().compareTo(newUuid);
        if (useGreater) {
            return compareUuidStrings > 0;
        } else {
            return compareUuidStrings < 0;
        }
    }

    ServerStats getStats() {
        return new ServerStats(this);
    }

    /** thread that uses UDP to auto-mesh server instances */
    private void startAutoMesh(final int port, final int timeout) {
        // create UDP broadcast / listener
        Thread t = new Thread(new AutoMeshTask(this, group, timeout, port),
                "AutoMesh Peer Listener (port: " + port + ")");
        t.setDaemon(true);
        t.start();
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this).add("serverPort", serverPort).add("serverUuid", serverUuid)
                .add("channelCount", getChannelCount()).add("peeredCount", getServerPeerCount()).toString();
    }
}