io.vertx.test.core.HostnameResolutionTest.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.test.core.HostnameResolutionTest.java

Source

/*
 * Copyright (c) 2011-2013 The original author or authors
 *  ------------------------------------------------------
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *      The Eclipse Public License is available at
 *      http://www.eclipse.org/legal/epl-v10.html
 *
 *      The Apache License v2.0 is available at
 *      http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.test.core;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.vertx.core.VertxException;
import io.vertx.core.VertxOptions;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.dns.AddressResolverOptions;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpServer;
import io.vertx.core.impl.AddressResolver;
import io.vertx.core.impl.VertxImpl;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetServer;
import io.vertx.core.net.NetServerOptions;
import io.vertx.test.fakedns.FakeDNSServer;
import org.junit.Test;

import java.io.File;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 */
public class HostnameResolutionTest extends VertxTestBase {

    private FakeDNSServer dnsServer;
    private InetSocketAddress dnsServerAddress;

    @Override
    public void setUp() throws Exception {
        dnsServer = FakeDNSServer.testResolveASameServer("127.0.0.1");
        dnsServer.start();
        dnsServerAddress = dnsServer.localAddress();
        super.setUp();
    }

    @Override
    protected void tearDown() throws Exception {
        if (dnsServer.isStarted()) {
            dnsServer.stop();
        }
        super.tearDown();
    }

    @Override
    protected VertxOptions getOptions() {
        VertxOptions options = super.getOptions();
        options.getAddressResolverOptions()
                .addServer(dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort());
        options.getAddressResolverOptions().setOptResourceEnabled(false);
        return options;
    }

    @Test
    public void testAsyncResolve() throws Exception {
        ((VertxImpl) vertx).resolveAddress("vertx.io", onSuccess(resolved -> {
            assertEquals("127.0.0.1", resolved.getHostAddress());
            testComplete();
        }));
        await();
    }

    @Test
    public void testAsyncResolveFail() throws Exception {
        ((VertxImpl) vertx).resolveAddress("vertx.com", onFailure(failure -> {
            assertEquals(UnknownHostException.class, failure.getClass());
            testComplete();
        }));
        await();
    }

    @Test
    public void testNet() throws Exception {
        testNet("vertx.io");
    }

    private void testNet(String hostname) throws Exception {
        NetClient client = vertx.createNetClient();
        NetServer server = vertx.createNetServer().connectHandler(so -> {
            so.handler(buff -> {
                so.write(buff);
                so.close();
            });
        });
        try {
            CountDownLatch listenLatch = new CountDownLatch(1);
            server.listen(1234, hostname, onSuccess(s -> {
                listenLatch.countDown();
            }));
            awaitLatch(listenLatch);
            client.connect(1234, hostname, onSuccess(so -> {
                Buffer buffer = Buffer.buffer();
                so.handler(buffer::appendBuffer);
                so.closeHandler(v -> {
                    assertEquals(Buffer.buffer("foo"), buffer);
                    testComplete();
                });
                so.write(Buffer.buffer("foo"));
            }));
            await();
        } finally {
            client.close();
            server.close();
        }
    }

    @Test
    public void testHttp() throws Exception {
        HttpClient client = vertx.createHttpClient();
        HttpServer server = vertx.createHttpServer().requestHandler(req -> {
            req.response().end("foo");
        });
        try {
            CountDownLatch listenLatch = new CountDownLatch(1);
            server.listen(8080, "vertx.io", onSuccess(s -> {
                listenLatch.countDown();
            }));
            awaitLatch(listenLatch);
            client.getNow(8080, "vertx.io", "/somepath", resp -> {
                Buffer buffer = Buffer.buffer();
                resp.handler(buffer::appendBuffer);
                resp.endHandler(v -> {
                    assertEquals(Buffer.buffer("foo"), buffer);
                    testComplete();
                });
            });
            await();
        } finally {
            client.close();
            server.close();
        }
    }

    @Test
    public void testOptions() {
        AddressResolverOptions options = new AddressResolverOptions();
        assertEquals(AddressResolverOptions.DEFAULT_OPT_RESOURCE_ENABLED, options.isOptResourceEnabled());
        assertEquals(AddressResolverOptions.DEFAULT_SERVERS, options.getServers());
        assertEquals(AddressResolverOptions.DEFAULT_CACHE_MIN_TIME_TO_LIVE, options.getCacheMinTimeToLive());
        assertEquals(AddressResolverOptions.DEFAULT_CACHE_MAX_TIME_TO_LIVE, options.getCacheMaxTimeToLive());
        assertEquals(AddressResolverOptions.DEFAULT_CACHE_NEGATIVE_TIME_TO_LIVE,
                options.getCacheNegativeTimeToLive());
        assertEquals(AddressResolverOptions.DEFAULT_QUERY_TIMEOUT, options.getQueryTimeout());
        assertEquals(AddressResolverOptions.DEFAULT_MAX_QUERIES, options.getMaxQueries());
        assertEquals(AddressResolverOptions.DEFAULT_RD_FLAG, options.getRdFlag());
        assertEquals(AddressResolverOptions.DEFAULT_NDOTS, options.getNdots());
        assertEquals(AddressResolverOptions.DEFAULT_SEACH_DOMAINS, options.getSearchDomains());

        boolean optResourceEnabled = TestUtils.randomBoolean();
        List<String> servers = Arrays.asList("1.2.3.4", "5.6.7.8");
        int minTTL = TestUtils.randomPositiveInt();
        int maxTTL = minTTL + 1000;
        int negativeTTL = TestUtils.randomPositiveInt();
        int queryTimeout = 1 + TestUtils.randomPositiveInt();
        int maxQueries = 1 + TestUtils.randomPositiveInt();
        boolean rdFlag = TestUtils.randomBoolean();
        int ndots = TestUtils.randomPositiveInt() - 2;
        List<String> searchDomains = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            searchDomains.add(TestUtils.randomAlphaString(15));
        }

        assertSame(options, options.setOptResourceEnabled(optResourceEnabled));
        assertSame(options, options.setServers(new ArrayList<>(servers)));
        assertSame(options, options.setCacheMinTimeToLive(0));
        assertSame(options, options.setCacheMinTimeToLive(minTTL));
        try {
            options.setCacheMinTimeToLive(-1);
            fail("Should throw exception");
        } catch (IllegalArgumentException e) {
            // OK
        }
        assertSame(options, options.setCacheMaxTimeToLive(0));
        assertSame(options, options.setCacheMaxTimeToLive(maxTTL));
        try {
            options.setCacheMaxTimeToLive(-1);
            fail("Should throw exception");
        } catch (IllegalArgumentException e) {
            // OK
        }
        assertSame(options, options.setCacheNegativeTimeToLive(0));
        assertSame(options, options.setCacheNegativeTimeToLive(negativeTTL));
        try {
            options.setCacheNegativeTimeToLive(-1);
            fail("Should throw exception");
        } catch (IllegalArgumentException e) {
            // OK
        }
        assertSame(options, options.setQueryTimeout(queryTimeout));
        try {
            options.setQueryTimeout(0);
            fail("Should throw exception");
        } catch (IllegalArgumentException e) {
            // OK
        }
        assertSame(options, options.setMaxQueries(maxQueries));
        try {
            options.setMaxQueries(0);
            fail("Should throw exception");
        } catch (IllegalArgumentException e) {
            // OK
        }
        assertSame(options, options.setRdFlag(rdFlag));
        assertSame(options, options.setSearchDomains(searchDomains));
        assertSame(options, options.setNdots(ndots));
        try {
            options.setNdots(-2);
            fail("Should throw exception");
        } catch (IllegalArgumentException e) {
            // OK
        }

        assertEquals(optResourceEnabled, options.isOptResourceEnabled());
        assertEquals(servers, options.getServers());
        assertEquals(minTTL, options.getCacheMinTimeToLive());
        assertEquals(maxTTL, options.getCacheMaxTimeToLive());
        assertEquals(negativeTTL, options.getCacheNegativeTimeToLive());
        assertEquals(queryTimeout, options.getQueryTimeout());
        assertEquals(maxQueries, options.getMaxQueries());
        assertEquals(rdFlag, options.getRdFlag());
        assertEquals(ndots, options.getNdots());
        assertEquals(searchDomains, options.getSearchDomains());

        // Test copy and json copy
        AddressResolverOptions copy = new AddressResolverOptions(options);
        AddressResolverOptions jsonCopy = new AddressResolverOptions(options.toJson());

        options.setOptResourceEnabled(AddressResolverOptions.DEFAULT_OPT_RESOURCE_ENABLED);
        options.getServers().clear();
        options.setCacheMinTimeToLive(AddressResolverOptions.DEFAULT_CACHE_MIN_TIME_TO_LIVE);
        options.setCacheMaxTimeToLive(AddressResolverOptions.DEFAULT_CACHE_MAX_TIME_TO_LIVE);
        options.setCacheNegativeTimeToLive(AddressResolverOptions.DEFAULT_CACHE_NEGATIVE_TIME_TO_LIVE);
        options.setQueryTimeout(AddressResolverOptions.DEFAULT_QUERY_TIMEOUT);
        options.setMaxQueries(AddressResolverOptions.DEFAULT_MAX_QUERIES);
        options.setRdFlag(AddressResolverOptions.DEFAULT_RD_FLAG);
        options.setNdots(AddressResolverOptions.DEFAULT_NDOTS);
        options.setSearchDomains(AddressResolverOptions.DEFAULT_SEACH_DOMAINS);

        assertEquals(optResourceEnabled, copy.isOptResourceEnabled());
        assertEquals(servers, copy.getServers());
        assertEquals(minTTL, copy.getCacheMinTimeToLive());
        assertEquals(maxTTL, copy.getCacheMaxTimeToLive());
        assertEquals(negativeTTL, copy.getCacheNegativeTimeToLive());
        assertEquals(queryTimeout, copy.getQueryTimeout());
        assertEquals(maxQueries, copy.getMaxQueries());
        assertEquals(rdFlag, copy.getRdFlag());
        assertEquals(ndots, copy.getNdots());
        assertEquals(searchDomains, copy.getSearchDomains());

        assertEquals(optResourceEnabled, jsonCopy.isOptResourceEnabled());
        assertEquals(servers, jsonCopy.getServers());
        assertEquals(minTTL, jsonCopy.getCacheMinTimeToLive());
        assertEquals(maxTTL, jsonCopy.getCacheMaxTimeToLive());
        assertEquals(negativeTTL, jsonCopy.getCacheNegativeTimeToLive());
        assertEquals(queryTimeout, jsonCopy.getQueryTimeout());
        assertEquals(maxQueries, jsonCopy.getMaxQueries());
        assertEquals(rdFlag, jsonCopy.getRdFlag());
        assertEquals(ndots, jsonCopy.getNdots());
        assertEquals(searchDomains, jsonCopy.getSearchDomains());
    }

    @Test
    public void testDefaultJsonOptions() {
        AddressResolverOptions options = new AddressResolverOptions(new JsonObject());
        assertEquals(AddressResolverOptions.DEFAULT_OPT_RESOURCE_ENABLED, options.isOptResourceEnabled());
        assertEquals(AddressResolverOptions.DEFAULT_SERVERS, options.getServers());
        assertEquals(AddressResolverOptions.DEFAULT_CACHE_MIN_TIME_TO_LIVE, options.getCacheMinTimeToLive());
        assertEquals(AddressResolverOptions.DEFAULT_CACHE_MAX_TIME_TO_LIVE, options.getCacheMaxTimeToLive());
        assertEquals(AddressResolverOptions.DEFAULT_CACHE_NEGATIVE_TIME_TO_LIVE,
                options.getCacheNegativeTimeToLive());
        assertEquals(AddressResolverOptions.DEFAULT_QUERY_TIMEOUT, options.getQueryTimeout());
        assertEquals(AddressResolverOptions.DEFAULT_MAX_QUERIES, options.getMaxQueries());
        assertEquals(AddressResolverOptions.DEFAULT_RD_FLAG, options.getRdFlag());
        assertEquals(AddressResolverOptions.DEFAULT_SEACH_DOMAINS, options.getSearchDomains());
        assertEquals(AddressResolverOptions.DEFAULT_NDOTS, options.getNdots());
    }

    @Test
    public void testAsyncResolveConnectIsNotifiedOnChannelEventLoop() throws Exception {
        CountDownLatch listenLatch = new CountDownLatch(1);
        NetServer s = vertx.createNetServer().connectHandler(so -> {
        });
        s.listen(1234, "localhost", onSuccess(v -> listenLatch.countDown()));
        awaitLatch(listenLatch);
        AtomicReference<Thread> channelThread = new AtomicReference<>();
        CountDownLatch connectLatch = new CountDownLatch(1);
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.group(vertx.nettyEventLoopGroup());
        bootstrap.resolver(((VertxInternal) vertx).nettyAddressResolverGroup());
        bootstrap.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                channelThread.set(Thread.currentThread());
                connectLatch.countDown();
            }
        });
        ChannelFuture channelFut = bootstrap.connect("localhost", 1234);
        awaitLatch(connectLatch);
        channelFut.addListener(v -> {
            assertTrue(v.isSuccess());
            assertEquals(channelThread.get(), Thread.currentThread());
            testComplete();
        });
        await();
    }

    @Test
    public void testInvalidHostsConfig() {
        try {
            AddressResolverOptions options = new AddressResolverOptions().setHostsPath("whatever.txt");
            vertx(new VertxOptions().setAddressResolverOptions(options));
            fail();
        } catch (VertxException ignore) {
        }
    }

    @Test
    public void testResolveFromClasspath() {
        VertxInternal vertx = (VertxInternal) vertx(new VertxOptions()
                .setAddressResolverOptions(new AddressResolverOptions().setHostsPath("hosts_config.txt")));
        vertx.resolveAddress("server.net", onSuccess(addr -> {
            assertEquals("192.168.0.15", addr.getHostAddress());
            assertEquals("server.net", addr.getHostName());
            testComplete();
        }));
        await();
    }

    @Test
    public void testResolveFromFile() {
        File f = new File(new File(new File(new File("src"), "test"), "resources"), "hosts_config.txt");
        VertxInternal vertx = (VertxInternal) vertx(new VertxOptions()
                .setAddressResolverOptions(new AddressResolverOptions().setHostsPath(f.getAbsolutePath())));
        vertx.resolveAddress("server.net", onSuccess(addr -> {
            assertEquals("192.168.0.15", addr.getHostAddress());
            assertEquals("server.net", addr.getHostName());
            testComplete();
        }));
        await();
    }

    @Test
    public void testResolveFromBuffer() {
        VertxInternal vertx = (VertxInternal) vertx(new VertxOptions().setAddressResolverOptions(
                new AddressResolverOptions().setHostsValue(Buffer.buffer("192.168.0.15 server.net"))));
        vertx.resolveAddress("server.net", onSuccess(addr -> {
            assertEquals("192.168.0.15", addr.getHostAddress());
            assertEquals("server.net", addr.getHostName());
            testComplete();
        }));
        await();
    }

    @Test
    public void testCaseInsensitiveResolveFromHosts() {
        VertxInternal vertx = (VertxInternal) vertx(new VertxOptions()
                .setAddressResolverOptions(new AddressResolverOptions().setHostsPath("hosts_config.txt")));
        vertx.resolveAddress("SERVER.NET", onSuccess(addr -> {
            assertEquals("192.168.0.15", addr.getHostAddress());
            assertEquals("server.net", addr.getHostName());
            testComplete();
        }));
        await();
    }

    @Test
    public void testResolveMissingLocalhost() throws Exception {

        InetAddress localhost = InetAddress.getByName("localhost");

        // Set a dns resolver that won't resolve localhost
        dnsServer.stop();
        dnsServer = FakeDNSServer.testResolveASameServer("127.0.0.1");
        dnsServer.start();
        dnsServerAddress = (InetSocketAddress) dnsServer.getTransports()[0].getAcceptor().getLocalAddress();

        // Test using the resolver API
        VertxInternal vertx = (VertxInternal) vertx(
                new VertxOptions().setAddressResolverOptions(new AddressResolverOptions()
                        .addServer(
                                dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort())
                        .setOptResourceEnabled(false)));
        CompletableFuture<Void> test1 = new CompletableFuture<>();
        vertx.resolveAddress("localhost", ar -> {
            if (ar.succeeded()) {
                InetAddress resolved = ar.result();
                if (resolved.equals(localhost)) {
                    test1.complete(null);
                } else {
                    test1.completeExceptionally(new AssertionError("Unexpected localhost value " + resolved));
                }
            } else {
                test1.completeExceptionally(ar.cause());
            }
        });
        test1.get(10, TimeUnit.SECONDS);

        CompletableFuture<Void> test2 = new CompletableFuture<>();
        vertx.resolveAddress("LOCALHOST", ar -> {
            if (ar.succeeded()) {
                InetAddress resolved = ar.result();
                if (resolved.equals(localhost)) {
                    test2.complete(null);
                } else {
                    test2.completeExceptionally(new AssertionError("Unexpected localhost value " + resolved));
                }
            } else {
                test2.completeExceptionally(ar.cause());
            }
        });
        test2.get(10, TimeUnit.SECONDS);

        // Test using bootstrap
        CompletableFuture<Void> test3 = new CompletableFuture<>();
        NetServer server = vertx
                .createNetServer(new NetServerOptions().setPort(1234).setHost(localhost.getHostAddress()));
        server.connectHandler(so -> {
            so.write("hello").end();
        });
        server.listen(ar -> {
            if (ar.succeeded()) {
                test3.complete(null);
            } else {
                test3.completeExceptionally(ar.cause());
            }
        });
        test3.get(10, TimeUnit.SECONDS);

        CompletableFuture<Void> test4 = new CompletableFuture<>();
        NetClient client = vertx.createNetClient();
        client.connect(1234, "localhost", ar -> {
            if (ar.succeeded()) {
                test4.complete(null);
            } else {
                test4.completeExceptionally(ar.cause());
            }
        });
        test4.get(10, TimeUnit.SECONDS);
    }

    @Test
    public void testSearchDomain() throws Exception {

        Map<String, String> records = new HashMap<>();
        records.put("host1.foo.com", "127.0.0.1");
        records.put("host1", "127.0.0.2");
        records.put("host3", "127.0.0.3");
        records.put("host4.sub.foo.com", "127.0.0.4");
        records.put("host5.sub.foo.com", "127.0.0.5");
        records.put("host5.sub", "127.0.0.6");

        dnsServer.stop();
        dnsServer = FakeDNSServer.testResolveA(records);
        dnsServer.start();
        VertxInternal vertx = (VertxInternal) vertx(
                new VertxOptions().setAddressResolverOptions(new AddressResolverOptions()
                        .addServer(
                                dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort())
                        .setOptResourceEnabled(false).addSearchDomain("foo.com")));

        // host1 resolves host1.foo.com with foo.com search domain
        CountDownLatch latch1 = new CountDownLatch(1);
        vertx.resolveAddress("host1", onSuccess(resolved -> {
            assertEquals("127.0.0.1", resolved.getHostAddress());
            latch1.countDown();
        }));
        awaitLatch(latch1);

        // "host1." absolute query
        CountDownLatch latch2 = new CountDownLatch(1);
        vertx.resolveAddress("host1.", onSuccess(resolved -> {
            assertEquals("127.0.0.2", resolved.getHostAddress());
            latch2.countDown();
        }));
        awaitLatch(latch2);

        // "host2" not resolved
        CountDownLatch latch3 = new CountDownLatch(1);
        vertx.resolveAddress("host2", onFailure(cause -> {
            assertTrue(cause instanceof UnknownHostException);
            latch3.countDown();
        }));
        awaitLatch(latch3);

        // "host3" does not contain a dot or is not absolute
        CountDownLatch latch4 = new CountDownLatch(1);
        vertx.resolveAddress("host3", onFailure(cause -> {
            assertTrue(cause instanceof UnknownHostException);
            latch4.countDown();
        }));
        awaitLatch(latch4);

        // "host3." does not contain a dot but is absolute
        CountDownLatch latch5 = new CountDownLatch(1);
        vertx.resolveAddress("host3.", onSuccess(resolved -> {
            assertEquals("127.0.0.3", resolved.getHostAddress());
            latch5.countDown();
        }));
        awaitLatch(latch5);

        // "host4.sub" contains a dot but not resolved then resolved to "host4.sub.foo.com" with "foo.com" search domain
        CountDownLatch latch6 = new CountDownLatch(1);
        vertx.resolveAddress("host4.sub", onSuccess(resolved -> {
            assertEquals("127.0.0.4", resolved.getHostAddress());
            latch6.countDown();
        }));
        awaitLatch(latch6);

        // "host5.sub" contains a dot and is resolved
        CountDownLatch latch7 = new CountDownLatch(1);
        vertx.resolveAddress("host5.sub", onSuccess(resolved -> {
            assertEquals("127.0.0.6", resolved.getHostAddress());
            latch7.countDown();
        }));
        awaitLatch(latch7);
    }

    @Test
    public void testMultipleSearchDomain() throws Exception {

        Map<String, String> records = new HashMap<>();
        records.put("host1.foo.com", "127.0.0.1");
        records.put("host2.bar.com", "127.0.0.2");
        records.put("host3.bar.com", "127.0.0.3");
        records.put("host3.foo.com", "127.0.0.4");

        dnsServer.stop();
        dnsServer = FakeDNSServer.testResolveA(records);
        dnsServer.start();
        VertxInternal vertx = (VertxInternal) vertx(
                new VertxOptions().setAddressResolverOptions(new AddressResolverOptions()
                        .addServer(
                                dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort())
                        .setOptResourceEnabled(false).addSearchDomain("foo.com").addSearchDomain("bar.com")));

        // "host1" resolves via the "foo.com" search path
        CountDownLatch latch1 = new CountDownLatch(1);
        vertx.resolveAddress("host1", onSuccess(resolved -> {
            assertEquals("127.0.0.1", resolved.getHostAddress());
            latch1.countDown();
        }));
        awaitLatch(latch1);

        // "host2" resolves via the "bar.com" search path
        CountDownLatch latch2 = new CountDownLatch(1);
        vertx.resolveAddress("host2", onSuccess(resolved -> {
            assertEquals("127.0.0.2", resolved.getHostAddress());
            latch2.countDown();
        }));
        awaitLatch(latch2);

        // "host3" resolves via the the "foo.com" search path as it is the first one
        CountDownLatch latch3 = new CountDownLatch(1);
        vertx.resolveAddress("host3", onSuccess(resolved -> {
            assertEquals("127.0.0.4", resolved.getHostAddress());
            latch3.countDown();
        }));
        awaitLatch(latch3);

        // "host4" does not resolve
        vertx.resolveAddress("host4", onFailure(cause -> {
            assertTrue(cause instanceof UnknownHostException);
            testComplete();
        }));

        await();
    }

    @Test
    public void testSearchDomainWithNdots2() throws Exception {

        Map<String, String> records = new HashMap<>();
        records.put("host1.sub.foo.com", "127.0.0.1");
        records.put("host2.sub.foo.com", "127.0.0.2");
        records.put("host2.sub", "127.0.0.3");

        dnsServer.stop();
        dnsServer = FakeDNSServer.testResolveA(records);
        dnsServer.start();
        VertxInternal vertx = (VertxInternal) vertx(
                new VertxOptions().setAddressResolverOptions(new AddressResolverOptions()
                        .addServer(
                                dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort())
                        .setOptResourceEnabled(false).addSearchDomain("foo.com").setNdots(2)));

        CountDownLatch latch1 = new CountDownLatch(1);
        vertx.resolveAddress("host1.sub", onSuccess(resolved -> {
            assertEquals("127.0.0.1", resolved.getHostAddress());
            latch1.countDown();
        }));
        awaitLatch(latch1);

        // "host2.sub" is resolved with the foo.com search domain as ndots = 2
        CountDownLatch latch2 = new CountDownLatch(1);
        vertx.resolveAddress("host2.sub", onSuccess(resolved -> {
            assertEquals("127.0.0.2", resolved.getHostAddress());
            latch2.countDown();
        }));
        awaitLatch(latch2);
    }

    @Test
    public void testSearchDomainWithNdots0() throws Exception {

        Map<String, String> records = new HashMap<>();
        records.put("host1", "127.0.0.2");
        records.put("host1.foo.com", "127.0.0.3");

        dnsServer.stop();
        dnsServer = FakeDNSServer.testResolveA(records);
        dnsServer.start();
        VertxInternal vertx = (VertxInternal) vertx(
                new VertxOptions().setAddressResolverOptions(new AddressResolverOptions()
                        .addServer(
                                dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort())
                        .setOptResourceEnabled(false).addSearchDomain("foo.com").setNdots(0)));

        // "host1" resolves directly as ndots = 0
        CountDownLatch latch1 = new CountDownLatch(1);
        vertx.resolveAddress("host1", onSuccess(resolved -> {
            assertEquals("127.0.0.2", resolved.getHostAddress());
            latch1.countDown();
        }));
        awaitLatch(latch1);

        // "host1.foo.com" resolves to host1.foo.com
        CountDownLatch latch2 = new CountDownLatch(1);
        vertx.resolveAddress("host1.foo.com", onSuccess(resolved -> {
            assertEquals("127.0.0.3", resolved.getHostAddress());
            latch2.countDown();
        }));
        awaitLatch(latch2);
    }

    @Test
    public void testNetSearchDomain() throws Exception {
        Map<String, String> records = new HashMap<>();
        records.put("host1.foo.com", "127.0.0.1");
        dnsServer.stop();
        dnsServer = FakeDNSServer.testResolveA(records);
        dnsServer.start();
        vertx.close();
        vertx = vertx(new VertxOptions().setAddressResolverOptions(new AddressResolverOptions()
                .addServer(dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort())
                .setOptResourceEnabled(false).addSearchDomain("foo.com")));
        testNet("host1");
    }

    @Test
    public void testParseResolvConf() {
        assertEquals(-1, AddressResolver.parseNdotsOptionFromResolvConf("options"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("\noptions ndots: 4"));
        assertEquals(-1, AddressResolver.parseNdotsOptionFromResolvConf("boptions ndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf(" options ndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("\toptions ndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("\foptions ndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("\n options ndots: 4"));

        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options\tndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options\fndots: 4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options  ndots: 4"));
        assertEquals(-1, AddressResolver.parseNdotsOptionFromResolvConf("options\nndots: 4"));

        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:\t4"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:  4"));
        assertEquals(-1, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:\n4"));

        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4 "));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4\t"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4\f"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4\n"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4\r"));
        assertEquals(-1, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4_"));

        assertEquals(2, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4\noptions ndots:2"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options ndots:4 debug"));
        assertEquals(4, AddressResolver.parseNdotsOptionFromResolvConf("options debug ndots:4"));

        assertEquals(false, AddressResolver.parseRotateOptionFromResolvConf("options"));
        assertEquals(true, AddressResolver.parseRotateOptionFromResolvConf("options rotate"));
        assertEquals(true, AddressResolver.parseRotateOptionFromResolvConf("options rotate\n"));
        assertEquals(false, AddressResolver.parseRotateOptionFromResolvConf("options\nrotate"));
    }

    @Test
    public void testResolveLocalhost() {
        AddressResolver resolver = new AddressResolver((VertxImpl) vertx, new AddressResolverOptions());

        resolver.resolveHostname("LOCALHOST", res -> {
            if (res.succeeded()) {
                assertEquals("localhost", res.result().getHostName().toLowerCase(Locale.ENGLISH));
                resolver.resolveHostname("LocalHost", res2 -> {
                    if (res2.succeeded()) {
                        assertEquals("localhost", res2.result().getHostName().toLowerCase(Locale.ENGLISH));
                        resolver.resolveHostname("localhost", res3 -> {
                            if (res3.succeeded()) {
                                assertEquals("localhost", res3.result().getHostName().toLowerCase(Locale.ENGLISH));
                                testComplete();
                            } else {
                                fail(res3.cause());
                            }
                        });
                    } else {
                        fail(res2.cause());
                    }
                });
            } else {
                fail(res.cause());
            }
        });

        await();
    }

    @Test
    public void testRotationalServerSelection() throws Exception {
        testServerSelection(true);
    }

    @Test
    public void testFirstServerSelection() throws Exception {
        testServerSelection(false);
    }

    private void testServerSelection(boolean rotateServers) throws Exception {
        int num = VertxOptions.DEFAULT_EVENT_LOOP_POOL_SIZE + 4;
        List<FakeDNSServer> dnsServers = new ArrayList<>();
        try {
            for (int index = 1; index <= num; index++) {
                FakeDNSServer server = new FakeDNSServer(
                        FakeDNSServer.A_store(Collections.singletonMap("vertx.io", "127.0.0." + index)));
                server.port(FakeDNSServer.PORT + index);
                server.start();
                dnsServers.add(server);
            }
            AddressResolverOptions options = new AddressResolverOptions();
            options.setRotateServers(rotateServers);
            options.setOptResourceEnabled(false);
            for (int i = 0; i < num; i++) {
                InetSocketAddress dnsServerAddress = dnsServers.get(i).localAddress();
                options.addServer(
                        dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort());
            }
            AddressResolver resolver = new AddressResolver((VertxImpl) vertx, options);
            for (int i = 0; i < num; i++) {
                CompletableFuture<InetAddress> result = new CompletableFuture<>();
                resolver.resolveHostname("vertx.io", ar -> {
                    if (ar.succeeded()) {
                        result.complete(ar.result());
                    } else {
                        result.completeExceptionally(ar.cause());
                    }
                });
                String resolved = result.get(10, TimeUnit.SECONDS).getHostAddress();
                int expected;
                if (rotateServers) {
                    // Expect from [1,...,DEFAULT_EVENT_LOOP_POOL_SIZE[ as we round robin but then we cache the result
                    // so it checks that round robin cross event loops
                    expected = 1 + i % VertxOptions.DEFAULT_EVENT_LOOP_POOL_SIZE;
                } else {
                    expected = 1;
                }
                assertEquals("127.0.0." + expected, resolved);
            }
        } finally {
            dnsServers.forEach(FakeDNSServer::stop);
        }
    }

    @Test
    public void testServerFailover() throws Exception {
        FakeDNSServer server = new FakeDNSServer(
                FakeDNSServer.A_store(Collections.singletonMap("vertx.io", "127.0.0.1")))
                        .port(FakeDNSServer.PORT + 2);
        try {
            AddressResolverOptions options = new AddressResolverOptions();
            options.setOptResourceEnabled(false);
            options.setMaxQueries(4); // 2 + 2
            server.start();
            InetSocketAddress dnsServerAddress = server.localAddress();
            // First server is unreachable
            options.addServer(dnsServerAddress.getAddress().getHostAddress() + ":" + (FakeDNSServer.PORT + 1));
            // Second server is the failed over server
            options.addServer(dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort());
            AddressResolver resolver = new AddressResolver((VertxImpl) vertx, options);
            CompletableFuture<InetAddress> result = new CompletableFuture<>();
            resolver.resolveHostname("vertx.io", ar -> {
                if (ar.succeeded()) {
                    result.complete(ar.result());
                } else {
                    result.completeExceptionally(ar.cause());
                }
            });
            String resolved = result.get(10, TimeUnit.SECONDS).getHostAddress();
            assertEquals("127.0.0.1", resolved);
        } finally {
            server.stop();
        }
    }
}