You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by il...@apache.org on 2021/04/08 13:26:57 UTC

[ignite] branch master updated: IGNITE-14499 Support dynamic DNS in TcpDiscoveryVmIpFinder - Fixes #8981.

This is an automated email from the ASF dual-hosted git repository.

ilyak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 31ac65e  IGNITE-14499 Support dynamic DNS in TcpDiscoveryVmIpFinder - Fixes #8981.
31ac65e is described below

commit 31ac65eb853d45b305981252e742448bcf352629
Author: aalexandrov@gridgain.com <aa...@gridgain.com>
AuthorDate: Thu Apr 8 16:26:30 2021 +0300

    IGNITE-14499 Support dynamic DNS in TcpDiscoveryVmIpFinder - Fixes #8981.
    
    Signed-off-by: Ilya Kasnacheev <il...@gmail.com>
---
 .../tcp/ipfinder/vm/TcpDiscoveryVmIpFinder.java    | 104 ++--
 .../vm/TcpDiscoveryVmIpFinderDnsResolveTest.java   | 630 +++++++++++++++++++++
 .../IgniteSpiDiscoverySelfTestSuite.java           |   2 +
 3 files changed, 696 insertions(+), 40 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinder.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinder.java
index 9b60dd9..a6170b4 100644
--- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinder.java
+++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinder.java
@@ -17,7 +17,9 @@
 
 package org.apache.ignite.spi.discovery.tcp.ipfinder.vm;
 
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -54,9 +56,13 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
     @LoggerResource
     private IgniteLogger log;
 
-    /** Addresses. */
+    /** Addresses from the configuration or IGNITE_TCP_DISCOVERY_ADDRESSES. */
     @GridToStringInclude
-    private Collection<InetSocketAddress> addrs;
+    private Collection<String> addrs = new ArrayList<>();
+
+    /** Registered InetSocketAddresses. */
+    @GridToStringInclude
+    private Collection<InetSocketAddress> registeredAddrs = new LinkedHashSet<>();
 
     /**
      * Initialize from system property.
@@ -65,15 +71,13 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
         String ips = IgniteSystemProperties.getString(IGNITE_TCP_DISCOVERY_ADDRESSES);
 
         if (!F.isEmpty(ips)) {
-            Collection<InetSocketAddress> addrsList = new LinkedHashSet<>();
-
             for (String s : ips.split(",")) {
                 if (!F.isEmpty(s)) {
                     s = s.trim();
 
                     if (!F.isEmpty(s)) {
                         try {
-                            addrsList.addAll(address(s));
+                            addrs.add(s);
                         }
                         catch (IgniteSpiException e) {
                             throw new IgniteException(e);
@@ -81,11 +85,7 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
                     }
                 }
             }
-
-            addrs = addrsList;
         }
-        else
-            addrs = new LinkedHashSet<>();
     }
 
     /**
@@ -136,12 +136,11 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
         if (F.isEmpty(addrs))
             return this;
 
-        Collection<InetSocketAddress> newAddrs = new LinkedHashSet<>();
-
+        // Validate the spot that addresses can be resolved, to preserve the existing behavior.
         for (String ipStr : addrs)
-            newAddrs.addAll(address(ipStr));
+            address(ipStr);
 
-        this.addrs = newAddrs;
+        this.addrs = new ArrayList<>(addrs);
 
         return this;
     }
@@ -183,8 +182,20 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
                 return addresses(ipStr, "\\:", errMsg);
         }
 
-        // Provided address does not contain port (will use default one).
-        return Collections.singleton(new InetSocketAddress(ipStr, 0));
+        Collection<InetSocketAddress> col = new LinkedHashSet<>();
+
+        try {
+            InetAddress[] inetAddresses = InetAddress.getAllByName(ipStr);
+
+            for (InetAddress addrs : inetAddresses)
+                col.add(new InetSocketAddress(addrs, 0));
+        }
+        catch (UnknownHostException ignored) {
+            // Preserve existing behavior on UnknownHostException
+            col = Collections.singleton(new InetSocketAddress(ipStr, 0));
+        }
+
+        return col;
     }
 
     /**
@@ -204,36 +215,44 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
         if (tokens.length == 2) {
             String addrStr = tokens[0];
             String portStr = tokens[1];
+            int port1, port2;
 
             if (portStr.contains("..")) {
-                try {
-                    int port1 = Integer.parseInt(portStr.substring(0, portStr.indexOf("..")));
-                    int port2 = Integer.parseInt(portStr.substring(portStr.indexOf("..") + 2, portStr.length()));
+                port1 = Integer.parseInt(portStr.substring(0, portStr.indexOf("..")));
+                port2 = Integer.parseInt(portStr.substring(portStr.indexOf("..") + 2, portStr.length()));
+            }
+            else
+                port1 = port2 = Integer.parseInt(portStr);
 
-                    if (port2 < port1 || port1 == port2 || port1 <= 0 || port2 <= 0)
-                        throw new IgniteSpiException(errMsg);
+            if ((port1 != port2 && port2 < port1) || port1 <= 0 || port2 <= 0)
+                throw new IgniteSpiException(errMsg);
 
-                    Collection<InetSocketAddress> res = new ArrayList<>(port2 - port1);
+            try {
+                Collection<InetSocketAddress> res = new ArrayList<>();
 
-                    // Upper bound included.
+                InetAddress[] inetAddresses;
+
+                try {
+                    inetAddresses = InetAddress.getAllByName(addrStr);
+                }
+                catch (UnknownHostException e) {
+                    // Ignore
                     for (int i = port1; i <= port2; i++)
                         res.add(new InetSocketAddress(addrStr, i));
 
                     return res;
                 }
-                catch (IllegalArgumentException e) {
-                    throw new IgniteSpiException(errMsg, e);
-                }
-            }
-            else {
-                try {
-                    int port = Integer.parseInt(portStr);
 
-                    return Collections.singleton(new InetSocketAddress(addrStr, port));
-                }
-                catch (IllegalArgumentException e) {
-                    throw new IgniteSpiException(errMsg, e);
+                for (InetAddress curAddr : inetAddresses) {
+                    // Upper bound included.
+                    for (int i = port1; i <= port2; i++)
+                        res.add(new InetSocketAddress(curAddr, i));
                 }
+
+                return res;
+            }
+            catch (IllegalArgumentException e) {
+                throw new IgniteSpiException(errMsg, e);
             }
         }
         else
@@ -242,25 +261,30 @@ public class TcpDiscoveryVmIpFinder extends TcpDiscoveryIpFinderAdapter {
 
     /** {@inheritDoc} */
     @Override public synchronized Collection<InetSocketAddress> getRegisteredAddresses() {
-        return Collections.unmodifiableCollection(addrs);
+        Collection<InetSocketAddress> resolvedAddrs = new LinkedHashSet<>();
+
+        for (String ipStr : addrs)
+            resolvedAddrs.addAll(address(ipStr));
+
+        resolvedAddrs.addAll(registeredAddrs);
+
+        return resolvedAddrs;
     }
 
     /** {@inheritDoc} */
     @Override public synchronized void registerAddresses(Collection<InetSocketAddress> addrs) {
         assert !F.isEmpty(addrs);
 
-        this.addrs = new LinkedHashSet<>(this.addrs);
-
-        this.addrs.addAll(addrs);
+        if (!F.isEmpty(addrs))
+            this.registeredAddrs.addAll(addrs);
     }
 
     /** {@inheritDoc} */
     @Override public synchronized void unregisterAddresses(Collection<InetSocketAddress> addrs) {
         assert !F.isEmpty(addrs);
 
-        this.addrs = new LinkedHashSet<>(this.addrs);
-
-        this.addrs.removeAll(addrs);
+        if (!F.isEmpty(addrs))
+            this.registeredAddrs.removeAll(addrs);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinderDnsResolveTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinderDnsResolveTest.java
new file mode 100644
index 0000000..371cdc7
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/ipfinder/vm/TcpDiscoveryVmIpFinderDnsResolveTest.java
@@ -0,0 +1,630 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.spi.discovery.tcp.ipfinder.vm;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**  */
+public class TcpDiscoveryVmIpFinderDnsResolveTest extends GridCommonAbstractTest {
+
+    /** Fqnd */
+    private static String FQDN = "test.domain";
+
+    /** Multiple Fqnd */
+    private static String MULTI_FQDN = "multi.test.domain";
+
+    /** Incorrect fqnd */
+    private static String BAD_FQDN = "bad.domain";
+
+    /** Incorrect ip */
+    private static String BAD_IP = "555.0.0.0";
+
+    /** Incorrect ipv6 */
+    private static String BAD_IP_6 = "[oo00::0000:ooo:ooo:ooo]";
+
+    /** local host address */
+    private static String LOCAL_HOST = "localhost";
+
+    /** Ip1 */
+    private static String IP1 = "10.0.0.1";
+
+    /** Ip2 */
+    private static String IP2 = "10.0.0.2";
+
+    /** DNS service */
+    private static TwoIpRoundRobinDnsService hostNameSvc;
+
+    /** original DNS */
+    private static Object nameSvc;
+
+    /**
+     */
+    @BeforeClass
+    public static void before() throws Exception {
+        hostNameSvc = new TwoIpRoundRobinDnsService(FQDN, MULTI_FQDN, IP1, IP2);
+
+        INameService.install(hostNameSvc);
+    }
+
+    /**
+     */
+    @AfterClass
+    public static void cleanup() throws Exception {
+        INameService.uninstall();
+    }
+
+    /**
+     * Current test checks that in case if DNS will not be able to resolve hostname,
+     * then no exceptions were thrown from getRegisteredAddresses method.
+     */
+    @Test
+    public void testFqdnResolveWhenDnsCantResolveHostName() {
+        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        Set<String> addrs = new HashSet<>();
+
+        addrs.add(BAD_FQDN);
+        addrs.add(BAD_FQDN + ":47500");
+        addrs.add(BAD_FQDN + ":47501..47502");
+
+        addrs.add(BAD_IP);
+        addrs.add(BAD_IP + ":47500");
+        addrs.add(BAD_IP + ":47501..47502");
+
+        addrs.add(BAD_IP_6);
+        addrs.add(BAD_IP_6 + ":47500");
+        addrs.add(BAD_IP_6 + ":47501..47502");
+
+        ipFinder.setAddresses(addrs);
+
+        Collection<InetSocketAddress> resolved = ipFinder.getRegisteredAddresses();
+
+        assertNotNull(resolved);
+
+        //for every host 4 entries (for 0, 47500, 47501, 47502)
+        assertEquals(resolved.size(), 12);
+
+        Iterator<InetSocketAddress> it = resolved.iterator();
+
+        System.out.println("Resolved addresses " + resolved);
+
+        while (it.hasNext()) {
+            InetSocketAddress addr = it.next();
+
+            assertNull(addr.getAddress());
+        }
+    }
+
+    /**
+     * Current test checks that TcpDiscoveryVmIpFinder will return new IP if DNS service will change the FQDN resolution.
+     *
+     * @param fqdn - A fully qualified domain name.
+     * @param expectedCount - how many addresses should be resolved.
+     * @throws Exception on error.
+     */
+    public void fqdnResolveAfterDnsHostChange(String fqdn, int expectedCount) throws Exception {
+        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        Set<String> addrs = new HashSet<>();
+
+        addrs.add(fqdn);
+
+        ipFinder.setAddresses(addrs);
+
+        Collection<InetSocketAddress> resolved1 = ipFinder.getRegisteredAddresses();
+        
+        assertEquals(expectedCount, resolved1.size());
+
+        InetSocketAddress addr1 = resolved1.iterator().next();
+
+        //because of JAVA networkaddress cache ttl can be turn on.
+        //will be great to change current test and run it in separate JVM.
+        //and set there -Dsun.net.inetaddr.ttl=0 -Dsun.net.inetaddr.negative.ttl=0
+        Thread.sleep(50_000);
+
+        Collection<InetSocketAddress> resolved2 = ipFinder.getRegisteredAddresses();
+
+        assertEquals(expectedCount, resolved2.size());
+
+        InetSocketAddress addr2 = resolved2.iterator().next();
+
+        log.info("Adrrs1 - " + addr1.getAddress() + " Adrrs2 - " + addr2.getAddress());
+
+        assertFalse("Addresses weren't resolved second time. Probably DNS cache has TTL more then 1 min, if yes " +
+                "then please mute this test. Adrrs1 - " + addr1.getAddress() + " Adrrs2 - " + addr2.getAddress(),
+            addr1.equals(addr2));
+    }
+
+    /**
+     * Current test checks that TcpDiscoveryVmIpFinder will return new IP if DNS service will change the FQDN resolution.
+     * There are no ports set, only hostname.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testFqdnResolveAfterDnsHostChange() throws Exception {
+        fqdnResolveAfterDnsHostChange(FQDN, 1);
+    }
+
+    /**
+     * Current test checks that TcpDiscoveryVmIpFinder will return new IP if DNS service will change the FQDN resolution.
+     * Port will be used.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testFqdnWithPortResolveAfterDnsHostChange() throws Exception {
+        fqdnResolveAfterDnsHostChange(FQDN + ":47500", 1);
+    }
+
+    /**
+     * Current test checks that TcpDiscoveryVmIpFinder will return new IP if DNS service will change the FQDN resolution.
+     * Port range will be used,
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testFqdnWithPortRangeResolveAfterDnsHostChange() throws Exception {
+        fqdnResolveAfterDnsHostChange(FQDN + ":47500..47501", 2);
+    }
+
+    /**
+     * Current test checks that in case of roundrobin DNS server that returns two IP if we set the FQDN name with port range
+     * then every pair of host/port addresses will have the same IP (not different).
+     */
+    @Test
+    public void testFqdnWithPortRangeResolveWithTwoIpRoundRobinDns() {
+        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        Set<String> addrs = new HashSet<>();
+
+        addrs.add(FQDN + ":47500..47509");
+
+        ipFinder.setAddresses(addrs);
+
+        Collection<InetSocketAddress> resolved = ipFinder.getRegisteredAddresses();
+
+        log.info("Resolved addresses are " + resolved);
+
+        assertTrue(resolved.size() == 10);
+
+        Iterator<InetSocketAddress> it = resolved.iterator();
+
+        InetSocketAddress first = it.next();
+
+        String ip = first.getAddress().getHostAddress();
+
+        String host = first.getHostName();
+
+        while (it.hasNext()) {
+            InetSocketAddress curr = it.next();
+
+            assertTrue("IP address isn't the same. ip - " + curr.getAddress().getHostAddress() + " expected " + ip,
+                ip.equals(curr.getAddress().getHostAddress()));
+
+            assertTrue("FQDN isn't the same. cur - " + curr.getHostName() + " expected " + host,
+                host.equals(curr.getHostName()));
+        }
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has FQDN name and additional one "registeredAddress"
+     * then both of them can be resolved.
+     *
+     * @param fgdn - A fully qualified domain name.
+     * @param expectedSize - how many addresses should be resolved.
+     * @param expectedDomainName - expected FQDN name.
+     */
+    public void fqdnResolveWithRegisteredAddrs(String fgdn, int expectedSize, String expectedDomainName) {
+        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        Set<String> addrs = new HashSet<>();
+
+        addrs.add(fgdn);
+
+        ipFinder.setAddresses(addrs);
+
+        Collection<InetSocketAddress> registerAddrs = new HashSet<>();
+
+        registerAddrs.add(new InetSocketAddress(LOCAL_HOST, 0));
+
+        ipFinder.registerAddresses(registerAddrs);
+
+        Collection<InetSocketAddress> resolved = ipFinder.getRegisteredAddresses();
+
+        assertTrue(resolved.size() == expectedSize);
+
+        log.info("Resolved addresses are " + resolved);
+
+        Iterator<InetSocketAddress> it = resolved.iterator();
+
+        while (it.hasNext()) {
+            InetSocketAddress addr = it.next();
+
+            assertNotNull(addr);
+
+            assertTrue(expectedDomainName.equals(addr.getHostName()) || LOCAL_HOST.equals(addr.getHostName()));
+        }
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has FQDN name and additional one "registeredAddress"
+     * then both of them can be resolved. There are no ports will be used, only hostname.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testFqdnResolveWithRegisteredAddrs() throws Exception {
+        fqdnResolveWithRegisteredAddrs(FQDN, 2, FQDN);
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has MULTI_FQDN (resolved to two ip) name and additional one "registeredAddress"
+     * then both of them can be resolved. There are no ports will be used, only hostname.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testMultiFqdnResolveWithRegisteredAddrs() throws Exception {
+        fqdnResolveWithRegisteredAddrs(MULTI_FQDN, 3, MULTI_FQDN);
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has FQDN name and additional one "registeredAddress"
+     * then both of them can be resolved. Port will be used.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testFqdnWithPortResolveWithRegisteredAddrs() throws Exception {
+        fqdnResolveWithRegisteredAddrs(FQDN + ":47500", 2, FQDN);
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has MULTI_FQDN (resolved to two ip) name and additional one "registeredAddress"
+     * then both of them can be resolved. Port will be used.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testMultiFqdnWithPortResolveWithRegisteredAddrs() throws Exception {
+        fqdnResolveWithRegisteredAddrs(MULTI_FQDN + ":47500", 3, MULTI_FQDN);
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has FQDN name and additional one "registeredAddress"
+     * then both of them can be resolved. Port range will be used.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testFqdnWithPortRangeResolveWithRegisteredAddrs() throws Exception {
+        fqdnResolveWithRegisteredAddrs(FQDN + ":47500..47501", 3, FQDN);
+    }
+
+    /**
+     * Current test checks that in case if TcpDiscoveryVmIpFinder has MULTI_FQDN (resolved to two ip) name and additional one "registeredAddress"
+     * then both of them can be resolved. Port range will be used.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testMultiFqdnWithPortRangeResolveWithRegisteredAddrs() throws Exception {
+        fqdnResolveWithRegisteredAddrs(MULTI_FQDN + ":47500..47501", 5, MULTI_FQDN);
+    }
+
+    /**
+     * Current test checks that in case if FQDN name with port range can be resolved in several IP addresses at the same time
+     * then should be returned set with full ip/port sets for all ip addresses that can be resolved.
+     */
+    @Test
+    public void testMultiFqdnResolveWithPortRange() {
+        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        Collection<String> addrs = new ArrayList<>();
+
+        addrs.add(MULTI_FQDN + ":47500..47509");
+
+        ipFinder.setAddresses(addrs);
+
+        Collection<InetSocketAddress> resolved = ipFinder.getRegisteredAddresses();
+
+        assertTrue(resolved.size() == 20);
+
+        log.info("Resolved addresses are " + resolved);
+
+        Iterator<InetSocketAddress> it = resolved.iterator();
+
+        int cntIp1 = 0;
+
+        int cntIp2 = 0;
+
+        while (it.hasNext()) {
+            InetSocketAddress curr = it.next();
+
+            assertTrue(MULTI_FQDN.equals(curr.getHostName()));
+
+            if (IP1.equals(curr.getAddress().getHostAddress()))
+                cntIp1++;
+            if (IP2.equals(curr.getAddress().getHostAddress()))
+                cntIp2++;
+        }
+
+        assertTrue(cntIp1 == 10);
+
+        assertTrue(cntIp2 == 10);
+    }
+
+    /**
+     * Current test checks that if FQDN name can be resolved in several IP addresses at the same time
+     * then all of them should be returned.
+     *
+     * @param fqdn - A fully qualified domain name.
+     */
+    public void multiFqdnResolve(String fqdn) {
+        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        Collection<String> addrs = new ArrayList<>();
+
+        addrs.add(fqdn);
+
+        ipFinder.setAddresses(addrs);
+
+        Collection<InetSocketAddress> resolved = ipFinder.getRegisteredAddresses();
+
+        assertTrue(resolved.size() == 2);
+
+        log.info("Resolved addresses are " + resolved);
+
+        Iterator<InetSocketAddress> it = resolved.iterator();
+
+        InetSocketAddress addr1 = it.next();
+
+        InetSocketAddress addr2 = it.next();
+
+        assertNotNull(addr1);
+
+        assertNotNull(addr2);
+
+        assertTrue(MULTI_FQDN.equals(addr1.getHostName()) && MULTI_FQDN.equals(addr2.getHostName()));
+
+        assertFalse("Addresses are the same. Adrrs1 - " + addr1.getAddress() +
+            " Adrrs2 - " + addr2.getAddress(), addr1.equals(addr2));
+    }
+
+    /**
+     * Current test checks that if FQDN name can be resolved in several IP addresses at the same time
+     * then all of them should be returned. There are no ports, only host name
+     */
+    @Test
+    public void testMultiFqdnResolve() {
+        multiFqdnResolve(MULTI_FQDN);
+    }
+
+    /**
+     * Current test checks that if FQDN name can be resolved in several IP addresses at the same time
+     * then all of them should be returned. Only one port will be set.
+     */
+    @Test
+    public void testMultiFqdnWithPortResolve() {
+        multiFqdnResolve(MULTI_FQDN + ":47500");
+    }
+
+    /**
+     * Custom hostname service.
+     */
+    @SuppressWarnings("restriction")
+    public static class TwoIpRoundRobinDnsService implements INameService {
+        /** ip1 */
+        private final String ip1;
+
+        /** ip2 */
+        private final String ip2;
+
+        /** fqdn */
+        private final String fqdn;
+
+        /** multiple fqdn */
+        private final String multipleFqdn;
+
+        /** change flag */
+        private boolean needReturnIp1 = false;
+
+        /** */
+        public TwoIpRoundRobinDnsService(String fqdn, String multipleFqdn, String ip1, String ip2) {
+            this.multipleFqdn = multipleFqdn;
+            this.ip1 = ip1;
+            this.ip2 = ip2;
+            this.fqdn = fqdn;
+        }
+
+        /** {@inheritDoc} */
+        @Override public InetAddress[] lookupAllHostAddr(String paramStr) throws UnknownHostException {
+            if (fqdn.equals(paramStr)) {
+                String ip = needReturnIp1 ? ip1 : ip2;
+
+                needReturnIp1 = !needReturnIp1;
+
+                final byte[] arrOfByte = sun.net.util.IPAddressUtil.textToNumericFormatV4(ip);
+
+                final InetAddress addr = InetAddress.getByAddress(paramStr, arrOfByte);
+
+                return new InetAddress[] {addr};
+            }
+            else if (multipleFqdn.equals(paramStr)) {
+                final byte[] arrOfByte1 = sun.net.util.IPAddressUtil.textToNumericFormatV4(ip1);
+
+                final byte[] arrOfByte2 = sun.net.util.IPAddressUtil.textToNumericFormatV4(ip2);
+
+                final InetAddress addr1 = InetAddress.getByAddress(paramStr, arrOfByte1);
+
+                final InetAddress addr2 = InetAddress.getByAddress(paramStr, arrOfByte2);
+
+                return new InetAddress[] {addr1, addr2};
+            }
+            else
+                throw new UnknownHostException();
+        }
+
+        /** {@inheritDoc} */
+        @Override public String getHostByAddr(byte[] paramArrOfByte) throws UnknownHostException {
+            throw new UnknownHostException();
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            TwoIpRoundRobinDnsService svc = (TwoIpRoundRobinDnsService)o;
+            return needReturnIp1 == svc.needReturnIp1 &&
+                Objects.equals(ip1, svc.ip1) &&
+                Objects.equals(ip2, svc.ip2) &&
+                Objects.equals(fqdn, svc.fqdn) &&
+                Objects.equals(multipleFqdn, svc.multipleFqdn);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+
+            return Objects.hash(ip1, ip2, fqdn, multipleFqdn, needReturnIp1);
+        }
+    }
+
+    /** */
+    public interface INameService extends InvocationHandler {
+        /** */
+        static void install(
+            INameService dns) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException {
+            final Class<?> inetAddrCls = InetAddress.class;
+
+            Object neu;
+
+            Field nameSvcField;
+
+            try {
+                //JAVA 9+ class
+                final Class<?> iface = Class.forName("java.net.InetAddress$NameService");
+
+                nameSvcField = inetAddrCls.getDeclaredField("nameService");
+
+                neu = Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[] {iface}, dns);
+            }
+            catch (final ClassNotFoundException | NoSuchFieldException e) {
+                //JAVA <8 class
+                nameSvcField = inetAddrCls.getDeclaredField("nameServices");
+
+                final Class<?> iface = Class.forName("sun.net.spi.nameservice.NameService");
+
+                neu = Collections.singletonList(Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[] {iface}, dns));
+            }
+
+            nameSvcField.setAccessible(true);
+
+            nameSvc = nameSvcField.get(inetAddrCls);
+
+            nameSvcField.set(inetAddrCls, neu);
+        }
+
+        /** */
+        static void uninstall() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+            final Class<?> inetAddrCls = InetAddress.class;
+
+            Field nameSvcField;
+
+            try {
+                //JAVA 9+ class
+                Class.forName("java.net.InetAddress$NameService");
+
+                nameSvcField = inetAddrCls.getDeclaredField("nameService");
+            }
+            catch (final ClassNotFoundException | NoSuchFieldException e) {
+                //JAVA <8 class
+                nameSvcField = inetAddrCls.getDeclaredField("nameServices");
+            }
+
+            nameSvcField.setAccessible(true);
+
+            nameSvcField.set(inetAddrCls, nameSvc);
+        }
+
+        /**
+         * Lookup a host mapping by name. Retrieve the IP addresses associated with a host
+         *
+         * @param host the specified hostname
+         * @return array of IP addresses for the requested host
+         * @throws UnknownHostException if no IP address for the {@code host} could be found
+         */
+        InetAddress[] lookupAllHostAddr(final String host) throws UnknownHostException;
+
+        /**
+         * Lookup the host corresponding to the IP address provided
+         *
+         * @param addr byte array representing an IP address
+         * @return {@code String} representing the host name mapping
+         * @throws UnknownHostException if no host found for the specified IP address
+         */
+        String getHostByAddr(final byte[] addr) throws UnknownHostException;
+
+        /** */
+        @Override default Object invoke(final Object proxy, final Method method,
+            final Object[] args) throws Throwable {
+            switch (method.getName()) {
+                case "lookupAllHostAddr":
+                    return lookupAllHostAddr((String)args[0]);
+                case "getHostByAddr":
+                    return getHostByAddr((byte[])args[0]);
+                default:
+                    final StringBuilder o = new StringBuilder();
+
+                    o.append(method.getReturnType().getCanonicalName() + " " + method.getName() + "(");
+
+                    final Class<?>[] ps = method.getParameterTypes();
+
+                    for (int i = 0; i < ps.length; ++i) {
+                        if (i > 0)
+                            o.append(", ");
+
+                        o.append(ps[i].getCanonicalName()).append(" p").append(i);
+                    }
+
+                    o.append(")");
+
+                    throw new UnsupportedOperationException(o.toString());
+            }
+        }
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java
index f29d54a..8b0bcbc 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java
@@ -75,6 +75,7 @@ import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryWithWrongServerTest;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinderSelfTest;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinderSelfTest;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinderSelfTest;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinderDnsResolveTest;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinderSelfTest;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.BeforeClass;
@@ -88,6 +89,7 @@ import static org.apache.ignite.IgniteSystemProperties.IGNITE_OVERRIDE_MCAST_GRP
  */
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
+    TcpDiscoveryVmIpFinderDnsResolveTest.class,
     TcpDiscoveryVmIpFinderSelfTest.class,
     TcpDiscoverySharedFsIpFinderSelfTest.class,
     TcpDiscoveryJdbcIpFinderSelfTest.class,