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 <53300653+vkozhukh...@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 { }