org.graylog2.inputs.transports.UdpTransportTest.java Source code

Java tutorial

Introduction

Here is the source code for org.graylog2.inputs.transports.UdpTransportTest.java

Source

/**
 * This file is part of Graylog.
 *
 * Graylog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Graylog 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Graylog.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.graylog2.inputs.transports;

import com.github.joschi.jadconfig.util.Size;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.Callables;
import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.commons.lang3.SystemUtils;
import org.graylog2.plugin.LocalMetricRegistry;
import org.graylog2.plugin.configuration.Configuration;
import org.graylog2.plugin.configuration.ConfigurationRequest;
import org.graylog2.plugin.inputs.MessageInput;
import org.graylog2.plugin.inputs.MisfireException;
import org.graylog2.plugin.inputs.transports.NettyTransport;
import org.graylog2.plugin.inputs.util.ThroughputCounter;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.FixedReceiveBufferSizePredictorFactory;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.ReceiveBufferSizePredictorFactory;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.UpstreamMessageEvent;
import org.jboss.netty.util.HashedWheelTimer;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import static com.jayway.awaitility.Awaitility.await;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class UdpTransportTest {
    private static final String BIND_ADDRESS = "127.0.0.1";
    private static final int PORT = 0;
    private static final int RECV_BUFFER_SIZE = 1024;
    private static final ImmutableMap<String, Object> CONFIG_SOURCE = ImmutableMap.<String, Object>of(
            NettyTransport.CK_BIND_ADDRESS, BIND_ADDRESS, NettyTransport.CK_PORT, PORT,
            NettyTransport.CK_RECV_BUFFER_SIZE, RECV_BUFFER_SIZE);
    private static final Configuration CONFIGURATION = new Configuration(CONFIG_SOURCE);

    private UdpTransport udpTransport;
    private ThroughputCounter throughputCounter;
    private LocalMetricRegistry localMetricRegistry;

    @Before
    public void setUp() throws Exception {
        throughputCounter = new ThroughputCounter(new HashedWheelTimer());
        localMetricRegistry = new LocalMetricRegistry();
        udpTransport = new UdpTransport(CONFIGURATION, throughputCounter, localMetricRegistry);
    }

    @Test
    public void transportReceivesDataSmallerThanRecvBufferSize() throws Exception {
        final CountingChannelUpstreamHandler handler = new CountingChannelUpstreamHandler();
        final UdpTransport transport = launchTransportForBootStrapTest(handler);
        final InetSocketAddress localAddress = (InetSocketAddress) transport.getLocalAddress();

        sendUdpDatagram(BIND_ADDRESS, localAddress.getPort(), 100);
        await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return !handler.getBytesWritten().isEmpty();
            }
        });
        transport.stop();

        assertThat(handler.getBytesWritten()).containsOnly(100);
    }

    @Test
    public void transportReceivesDataExactlyRecvBufferSize() throws Exception {
        final CountingChannelUpstreamHandler handler = new CountingChannelUpstreamHandler();
        final UdpTransport transport = launchTransportForBootStrapTest(handler);
        final InetSocketAddress localAddress = (InetSocketAddress) transport.getLocalAddress();

        // This will be variable depending on the version of the IP protocol and the UDP packet size.
        final int udpOverhead = 16;
        final int maxPacketSize = RECV_BUFFER_SIZE - udpOverhead;

        sendUdpDatagram(BIND_ADDRESS, localAddress.getPort(), maxPacketSize);
        await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return !handler.getBytesWritten().isEmpty();
            }
        });
        transport.stop();

        assertThat(handler.getBytesWritten()).containsOnly(maxPacketSize);
    }

    @Test
    public void transportDiscardsDataLargerRecvBufferSizeOnMacOsX() throws Exception {
        assumeTrue(SystemUtils.IS_OS_MAC_OSX);

        final CountingChannelUpstreamHandler handler = new CountingChannelUpstreamHandler();
        final UdpTransport transport = launchTransportForBootStrapTest(handler);
        final InetSocketAddress localAddress = (InetSocketAddress) transport.getLocalAddress();

        sendUdpDatagram(BIND_ADDRESS, localAddress.getPort(), 2 * RECV_BUFFER_SIZE);
        Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);

        transport.stop();

        assertThat(handler.getBytesWritten()).isEmpty();
    }

    @Test
    public void transportTruncatesDataLargerRecvBufferSizeOnLinux() throws Exception {
        assumeTrue(SystemUtils.IS_OS_LINUX);

        final CountingChannelUpstreamHandler handler = new CountingChannelUpstreamHandler();
        final UdpTransport transport = launchTransportForBootStrapTest(handler);
        final InetSocketAddress localAddress = (InetSocketAddress) transport.getLocalAddress();

        sendUdpDatagram(BIND_ADDRESS, localAddress.getPort(), 2 * RECV_BUFFER_SIZE);
        await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return !handler.getBytesWritten().isEmpty();
            }
        });
        transport.stop();

        assertThat(handler.getBytesWritten()).containsExactly(RECV_BUFFER_SIZE);
    }

    private UdpTransport launchTransportForBootStrapTest(final ChannelHandler channelHandler)
            throws MisfireException {
        final UdpTransport transport = new UdpTransport(CONFIGURATION, throughputCounter,
                new LocalMetricRegistry()) {
            @Override
            protected LinkedHashMap<String, Callable<? extends ChannelHandler>> getBaseChannelHandlers(
                    MessageInput input) {
                final LinkedHashMap<String, Callable<? extends ChannelHandler>> handlers = new LinkedHashMap<>();
                handlers.put("counter", Callables.returning(channelHandler));
                handlers.putAll(super.getFinalChannelHandlers(input));
                return handlers;
            }
        };

        final MessageInput messageInput = mock(MessageInput.class);
        when(messageInput.getId()).thenReturn("TEST");
        when(messageInput.getName()).thenReturn("TEST");

        transport.launch(messageInput);

        return transport;
    }

    @Test
    public void receiveBufferSizeIsDefaultSize() throws Exception {
        assertThat(udpTransport.getBootstrap().getOption("receiveBufferSize")).isEqualTo(RECV_BUFFER_SIZE);
    }

    @Test
    public void receiveBufferSizeIsNotLimited() throws Exception {
        final int recvBufferSize = Ints.saturatedCast(Size.megabytes(1L).toBytes());
        ImmutableMap<String, Object> source = ImmutableMap.<String, Object>of(NettyTransport.CK_BIND_ADDRESS,
                BIND_ADDRESS, NettyTransport.CK_PORT, PORT, NettyTransport.CK_RECV_BUFFER_SIZE, recvBufferSize);
        Configuration config = new Configuration(source);
        UdpTransport udpTransport = new UdpTransport(config, throughputCounter, new LocalMetricRegistry());

        assertThat(udpTransport.getBootstrap().getOption("receiveBufferSize")).isEqualTo(recvBufferSize);
    }

    @Test
    public void receiveBufferSizePredictorIsUsingDefaultSize() throws Exception {
        ReceiveBufferSizePredictorFactory receiveBufferSizePredictorFactory = (ReceiveBufferSizePredictorFactory) udpTransport
                .getBootstrap().getOption("receiveBufferSizePredictorFactory");
        assertThat(receiveBufferSizePredictorFactory).isInstanceOf(FixedReceiveBufferSizePredictorFactory.class);
        assertThat(receiveBufferSizePredictorFactory.getPredictor().nextReceiveBufferSize())
                .isEqualTo(RECV_BUFFER_SIZE);
    }

    @Test
    public void getMetricSetReturnsLocalMetricRegistry() {
        assertThat(udpTransport.getMetricSet()).isSameAs(localMetricRegistry);
    }

    @Test
    public void testDefaultReceiveBufferSize() throws Exception {
        final UdpTransport.Config config = new UdpTransport.Config();
        final ConfigurationRequest requestedConfiguration = config.getRequestedConfiguration();

        assertThat(requestedConfiguration.getField(NettyTransport.CK_RECV_BUFFER_SIZE).getDefaultValue())
                .isEqualTo(262144);
    }

    private void sendUdpDatagram(String hostname, int port, int size) throws IOException {
        final InetAddress address = InetAddress.getByName(hostname);
        final byte[] data = new byte[size];
        final DatagramPacket packet = new DatagramPacket(data, data.length, address, port);

        DatagramSocket toSocket = null;
        try {
            toSocket = new DatagramSocket();
            toSocket.send(packet);
        } finally {
            if (toSocket != null) {
                toSocket.close();
            }
        }
    }

    public static class CountingChannelUpstreamHandler extends SimpleChannelUpstreamHandler {
        private final List<Integer> bytesWritten = new ArrayList<>();

        @Override
        public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
            if (e instanceof UpstreamMessageEvent) {
                ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
                try {
                    synchronized (bytesWritten) {
                        bytesWritten.add(buffer.readableBytes());
                    }
                } finally {
                    super.messageReceived(ctx, e);
                }
            }
        }

        public List<Integer> getBytesWritten() {
            return bytesWritten;
        }
    }
}