You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2020/12/28 10:55:59 UTC

[ignite] branch master updated: IGNITE-13555 Java thin: add IPv6 address support

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

ptupitsyn 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 64dbfcf  IGNITE-13555 Java thin: add IPv6 address support
64dbfcf is described below

commit 64dbfcf574cdf0beb635eaa900daaca22d5a5185
Author: Varvara Kozhukhova <53...@users.noreply.github.com>
AuthorDate: Mon Dec 28 13:55:30 2020 +0300

    IGNITE-13555 Java thin: add IPv6 address support
    
    - Change HostAndPortRange.parse method to support addresses like [IPv6_host]:port1..port2, because previous implementation didn't recognized IPv6.
    - Add tests for HostAndPortRange.parse method for both IPv4 and IPv6 hosts.
---
 .../processors/odbc/ClientListenerProcessor.java   |   2 +-
 .../ignite/internal/util/HostAndPortRange.java     | 133 +++++++++++----
 .../org/apache/ignite/client/ConnectionTest.java   |  31 +++-
 .../apache/ignite/client/LocalIgniteCluster.java   |  14 +-
 .../ignite/internal/util/HostAndPortRangeTest.java | 181 +++++++++++++++++++++
 .../ignite/testsuites/IgniteUtilSelfTestSuite.java |   5 +-
 6 files changed, 317 insertions(+), 49 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
index 517d213..4a51fb0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
@@ -201,7 +201,7 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
                 if (lastErr != null)
                     throw new IgniteCheckedException("Failed to bind to any [host:port] from the range [" +
                         "host=" + host + ", portFrom=" + cliConnCfg.getPort() + ", portTo=" + portTo +
-                        ", lastErr=" + lastErr + ']');
+                        ", lastErr=" + lastErr + ']', lastErr);
 
                 if (!U.IGNITE_MBEANS_DISABLED)
                     registerMBean();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/HostAndPortRange.java b/modules/core/src/main/java/org/apache/ignite/internal/util/HostAndPortRange.java
index 9bfca7f..0255275 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/HostAndPortRange.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/HostAndPortRange.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.internal.util;
 
 import java.io.Serializable;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.util.typedef.F;
 
@@ -53,60 +55,106 @@ public class HostAndPortRange implements Serializable {
 
         String host;
 
+        String portStr;
         int portFrom;
         int portTo;
 
         if (F.isEmpty(addrStr))
             throw createParseError(addrStr, errMsgPrefix, "Address is empty");
 
-        final int colIdx = addrStr.indexOf(':');
-
-        if (colIdx > 0) {
-            String portFromStr;
-            String portToStr;
+        if (addrStr.charAt(0) == '[') { // IPv6 with port(s)
+            int hostEndIdx = addrStr.indexOf(']');
+            
+            if (hostEndIdx == -1)
+                throw createParseError(addrStr, errMsgPrefix, "Failed to parse IPv6 address, missing ']'");
+
+            host = addrStr.substring(1, hostEndIdx);
+            
+            if (hostEndIdx == addrStr.length() - 1) { // no port specified, using default
+                portFrom = dfltPortFrom;
+                portTo = dfltPortTo;
+            }
+            else { // port specified
+                portStr = addrStr.substring(hostEndIdx + 2);
 
-            host = addrStr.substring(0, colIdx);
+                int[] ports = verifyPortStr(addrStr, errMsgPrefix, portStr);
+                portFrom = ports[0];
+                portTo = ports[1];
+            }
+        }
+        else { // IPv4 || IPv6 without port || empty host
+            final int colIdx = addrStr.lastIndexOf(':');
+            
+            if (colIdx > 0) {
+                if (addrStr.lastIndexOf(':', colIdx - 1) != -1) { // IPv6 without [] and port
+                    try {
+                        Inet6Address.getByName(addrStr);
+                        host = addrStr;
+                        portFrom = dfltPortFrom;
+                        portTo = dfltPortTo;
+                    }
+                    catch (UnknownHostException e) {
+                        throw createParseError(addrStr, errMsgPrefix, "IPv6 is incorrect", e);
+                    }
+                }
+                else {
+                    host = addrStr.substring(0, colIdx);
+                    portStr = addrStr.substring(colIdx + 1);
+                    int[] ports = verifyPortStr(addrStr, errMsgPrefix, portStr);
+                    portFrom = ports[0];
+                    portTo = ports[1];
+                }
+            }
+            else if (colIdx == 0)
+                throw createParseError(addrStr, errMsgPrefix, "Host name is empty");
+            else { // Port is not specified, use defaults.
+                host = addrStr;
 
-            String portStr = addrStr.substring(colIdx + 1, addrStr.length());
+                portFrom = dfltPortFrom;
+                portTo = dfltPortTo;
+            }
+        }
 
-            if (F.isEmpty(portStr))
-                throw createParseError(addrStr, errMsgPrefix, "port range is not specified");
+        return new HostAndPortRange(host, portFrom, portTo);
+    }
 
-            int portRangeIdx = portStr.indexOf("..");
+    /**
+     * Verifies string containing single port or ports range.
+     *
+     * @param addrStr Address String.
+     * @param errMsgPrefix Error message prefix.
+     * @param portStr Port or port range string.
+     * @return Array of int[portFrom, portTo].
+     * @throws IgniteCheckedException If failed.
+     */
+    private static int[] verifyPortStr(String addrStr, String errMsgPrefix, String portStr)
+        throws IgniteCheckedException {
+        String portFromStr;
+        String portToStr;
 
-            if (portRangeIdx >= 0) {
-                // Port range is specified.
-                portFromStr = portStr.substring(0, portRangeIdx);
-                portToStr = portStr.substring(portRangeIdx + 2, portStr.length());
-            }
-            else {
-                // Single port is specified.
-                portFromStr = portStr;
-                portToStr = portStr;
-            }
+        if (F.isEmpty(portStr))
+            throw createParseError(addrStr, errMsgPrefix, "port range is not specified");
 
-            portFrom = parsePort(portFromStr, addrStr, errMsgPrefix);
-            portTo = parsePort(portToStr, addrStr, errMsgPrefix);
+        int portRangeIdx = portStr.indexOf("..");
 
-            if (portFrom > portTo)
-                throw createParseError(addrStr, errMsgPrefix, "start port cannot be less than end port");
+        if (portRangeIdx >= 0) {
+            // Port range is specified.
+            portFromStr = portStr.substring(0, portRangeIdx);
+            portToStr = portStr.substring(portRangeIdx + 2);
         }
         else {
-            // Host name not specified.
-            if (colIdx == 0)
-                throw createParseError(addrStr, errMsgPrefix, "Host name is empty");
-
-            // Port is not specified, use defaults.
-            host = addrStr;
-
-            portFrom = dfltPortFrom;
-            portTo = dfltPortTo;
+            // Single port is specified.
+            portFromStr = portStr;
+            portToStr = portStr;
         }
 
-        if (F.isEmpty(host))
-            throw createParseError(addrStr, errMsgPrefix, "Host name is empty");
+        int portFrom = parsePort(portFromStr, addrStr, errMsgPrefix);
+        int portTo = parsePort(portToStr, addrStr, errMsgPrefix);
 
-        return new HostAndPortRange(host, portFrom, portTo);
+        if (portFrom > portTo)
+            throw createParseError(addrStr, errMsgPrefix, "start port cannot be less than end port");
+
+        return new int[] {portFrom, portTo};
     }
 
     /**
@@ -145,6 +193,19 @@ public class HostAndPortRange implements Serializable {
     }
 
     /**
+     * Create parse error with cause - nested exception.
+     *
+     * @param addrStr Address string.
+     * @param errMsgPrefix Error message prefix.
+     * @param errMsg Error message.
+     * @param cause Cause exception.
+     * @return Exception.
+     */
+    private static IgniteCheckedException createParseError(String addrStr, String errMsgPrefix, String errMsg, Throwable cause) {
+        return new IgniteCheckedException(errMsgPrefix + " (" + errMsg + "): " + addrStr, cause);
+    }
+
+    /**
      * Constructor.
      *
      * @param host Host.
diff --git a/modules/core/src/test/java/org/apache/ignite/client/ConnectionTest.java b/modules/core/src/test/java/org/apache/ignite/client/ConnectionTest.java
index d591428..b394f3c 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/ConnectionTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/ConnectionTest.java
@@ -19,53 +19,68 @@ package org.apache.ignite.client;
 
 import org.apache.ignite.Ignition;
 import org.apache.ignite.configuration.ClientConfiguration;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
  * Checks if it can connect to a valid address from the node address list.
  */
 public class ConnectionTest {
+    /** IPv4 default host. */
+    public static final String IPv4_HOST = "127.0.0.1";
+
+    /** IPv6 default host. */
+    public static final String IPv6_HOST = "::1";
+
     /** */
     @Test(expected = org.apache.ignite.client.ClientException.class)
     public void testEmptyNodeAddress() throws Exception {
-        testConnection("");
+        testConnection(IPv4_HOST, "");
     }
 
     /** */
     @Test(expected = org.apache.ignite.client.ClientException.class)
     public void testNullNodeAddress() throws Exception {
-        testConnection(null);
+        testConnection(IPv4_HOST, null);
     }
 
     /** */
     @Test(expected = org.apache.ignite.client.ClientException.class)
     public void testNullNodeAddresses() throws Exception {
-        testConnection(null, null);
+        testConnection(IPv4_HOST, null, null);
     }
 
     /** */
     @Test
     public void testValidNodeAddresses() throws Exception {
-        testConnection(Config.SERVER);
+        testConnection(IPv4_HOST, Config.SERVER);
     }
 
     /** */
     @Test(expected = org.apache.ignite.client.ClientConnectionException.class)
     public void testInvalidNodeAddresses() throws Exception {
-        testConnection("127.0.0.1:47500", "127.0.0.1:10801");
+        testConnection(IPv4_HOST, "127.0.0.1:47500", "127.0.0.1:10801");
     }
 
     /** */
     @Test
     public void testValidInvalidNodeAddressesMix() throws Exception {
-        testConnection("127.0.0.1:47500", "127.0.0.1:10801", Config.SERVER);
+        testConnection(IPv4_HOST, "127.0.0.1:47500", "127.0.0.1:10801", Config.SERVER);
+    }
+
+    /** */
+    @Ignore("IPv6 is not enabled by default on some systems.")
+    @Test
+    public void testIPv6NodeAddresses() throws Exception {
+        testConnection(IPv6_HOST, "[::1]:10800");
     }
 
     /**
      * @param addrs Addresses to connect.
+     * @param host LocalIgniteCluster host.
      */
-    private void testConnection(String... addrs) throws Exception {
-        try (LocalIgniteCluster cluster = LocalIgniteCluster.start(1);
+    private void testConnection(String host, String... addrs) throws Exception {
+        try (LocalIgniteCluster cluster = LocalIgniteCluster.start(1, host);
              IgniteClient client = Ignition.startClient(new ClientConfiguration()
                      .setAddresses(addrs))) {
         }
diff --git a/modules/core/src/test/java/org/apache/ignite/client/LocalIgniteCluster.java b/modules/core/src/test/java/org/apache/ignite/client/LocalIgniteCluster.java
index c6e1593..50ae358 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/LocalIgniteCluster.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/LocalIgniteCluster.java
@@ -35,7 +35,7 @@ import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
  */
 public class LocalIgniteCluster implements AutoCloseable {
     /** Host. */
-    private static final String HOST = "127.0.0.1";
+    private static String host = "127.0.0.1";
 
     /** Randomizer. */
     private static final Random rnd = new Random();
@@ -68,12 +68,20 @@ public class LocalIgniteCluster implements AutoCloseable {
     }
 
     /**
-     * Create and start start the cluster.
+     * Create and start start the cluster with default host.
      */
     public static LocalIgniteCluster start(int initSize) {
         return new LocalIgniteCluster(initSize);
     }
 
+    /**
+     * Create and start start the cluster with custom host.
+     */
+    public static LocalIgniteCluster start(int initSize, String host) {
+        LocalIgniteCluster.host = host;
+        return new LocalIgniteCluster(initSize);
+    }
+
     /** {@inheritDoc} */
     @Override public synchronized void close() {
         srvs.forEach(Ignite::close);
@@ -155,7 +163,7 @@ public class LocalIgniteCluster implements AutoCloseable {
         IgniteConfiguration igniteCfg = Config.getServerConfiguration();
 
         igniteCfg.setClientConnectorConfiguration(new ClientConnectorConfiguration()
-            .setHost(HOST)
+            .setHost(host)
             .setPort(nodeCfg.getClientPort())
         );
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/HostAndPortRangeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/HostAndPortRangeTest.java
new file mode 100644
index 0000000..1299e32
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/HostAndPortRangeTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.internal.util;
+
+import java.net.UnknownHostException;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.testframework.junits.common.GridCommonTest;
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests HostAndPortRange parse method.
+ */
+@GridCommonTest(group = "Utils")
+public class HostAndPortRangeTest extends GridCommonAbstractTest {
+    /**
+     * Tests correct input address with IPv4 host and port range.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv4WithPortRange() throws IgniteCheckedException {
+        String addrStr = "127.0.0.1:8080..8090";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+        HostAndPortRange expected = new HostAndPortRange("127.0.0.1", 8080, 8090);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Tests correct input address with IPv4 host and single port.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv4WithSinglePort() throws IgniteCheckedException {
+        String addrStr = "127.0.0.1:8080";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+        HostAndPortRange expected = new HostAndPortRange("127.0.0.1", 8080, 8080);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Tests correct input address with IPv4 host and no port.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv4NoPort() throws IgniteCheckedException {
+        String addrStr = "127.0.0.1";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+        HostAndPortRange expected = new HostAndPortRange("127.0.0.1", 18360, 18362);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Tests correct input address with IPv6 host and port range.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv6WithPortRange() throws IgniteCheckedException {
+        String addrStr = "[::1]:8080..8090";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+        HostAndPortRange expected = new HostAndPortRange("::1", 8080, 8090);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Tests correct input address with IPv6 host and single port.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv6WithSinglePort() throws IgniteCheckedException {
+        String addrStr = "[3ffe:2a00:100:7031::]:8080";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+        HostAndPortRange expected = new HostAndPortRange("3ffe:2a00:100:7031::", 8080, 8080);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Tests correct input address with IPv6 host and no port.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv6NoPort() throws IgniteCheckedException {
+        String addrStr = "::FFFF:129.144.52.38";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+        HostAndPortRange expected = new HostAndPortRange("::FFFF:129.144.52.38", 18360, 18362);
+        assertEquals(expected, actual);
+    }
+
+    @Rule
+    public ExpectedException expectedEx = ExpectedException.none();
+
+    /**
+     * Tests incorrect input address with IPv6 host (no brackets) and port.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseIPv6IncorrectHost() throws IgniteCheckedException {
+        expectedEx.expect(IgniteCheckedException.class);
+        expectedEx.expectMessage("IPv6 is incorrect");
+        expectedEx.expectCause(IsInstanceOf.instanceOf(UnknownHostException.class));
+        String addrStr = "3ffe:2a00:100:7031";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+    }
+
+    /**
+     * Tests empty host and port.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseNoHost() throws IgniteCheckedException {
+        expectedEx.expect(IgniteCheckedException.class);
+        expectedEx.expectMessage("Host name is empty");
+        String addrStr = ":8080";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+    }
+
+    /**
+     * Tests empty address string.
+     *
+     * @throws IgniteCheckedException on incorrect host/port
+     */
+    @Test
+    public void testParseNoAddress() throws IgniteCheckedException {
+        expectedEx.expect(IgniteCheckedException.class);
+        expectedEx.expectMessage("Address is empty");
+        String addrStr = "";
+        String errMsgPrefix = "";
+        int dfltPortFrom = 18360;
+        int dfltPortTo = 18362;
+        HostAndPortRange actual = HostAndPortRange.parse(addrStr, dfltPortFrom, dfltPortTo, errMsgPrefix);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java
index 6e1dd3a..99b380d 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.util.DistributedProcessCoordinatorLeftTest;
 import org.apache.ignite.internal.util.GridArraysSelfTest;
 import org.apache.ignite.internal.util.GridConcurrentMultiPairQueueTest;
 import org.apache.ignite.internal.util.GridCountDownCallbackTest;
+import org.apache.ignite.internal.util.HostAndPortRangeTest;
 import org.apache.ignite.internal.util.IgniteDevOnlyLogTest;
 import org.apache.ignite.internal.util.IgniteExceptionRegistrySelfTest;
 import org.apache.ignite.internal.util.IgniteUtilsSelfTest;
@@ -138,7 +139,9 @@ import org.junit.runners.Suite;
 
     DistributedProcessCoordinatorLeftTest.class,
 
-    BasicRateLimiterTest.class
+    BasicRateLimiterTest.class,
+
+    HostAndPortRangeTest.class
 })
 public class IgniteUtilSelfTestSuite {
 }