IGNITE-7192: Implemented JDBC support FQDN to multiple IPs This closes #3439
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/9fd0f4cd Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/9fd0f4cd Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/9fd0f4cd Branch: refs/heads/ignite-7725 Commit: 9fd0f4cd2080713850edd10cf31800e10969385d Parents: 210ad20 Author: Roman Guseinov <[email protected]> Authored: Fri Feb 16 12:57:26 2018 +0300 Committer: Igor Sapego <[email protected]> Committed: Fri Feb 16 12:59:05 2018 +0300 ---------------------------------------------------------------------- .../jdbc/suite/IgniteJdbcDriverTestSuite.java | 2 + .../ignite/jdbc/thin/JdbcThinTcpIoTest.java | 165 +++++++++++++++++++ .../internal/jdbc/thin/JdbcThinTcpIo.java | 94 ++++++++++- 3 files changed, 257 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/9fd0f4cd/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java index c004817..4530ae7 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java @@ -69,6 +69,7 @@ import org.apache.ignite.jdbc.thin.JdbcThinUpdateStatementSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinComplexDmlDdlSkipReducerOnUpdateSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinInsertStatementSkipReducerOnUpdateSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinMergeStatementSkipReducerOnUpdateSelfTest; +import org.apache.ignite.jdbc.thin.JdbcThinTcpIoTest; import org.apache.ignite.jdbc.thin.JdbcThinUpdateStatementSkipReducerOnUpdateSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinWalModeChangeSelfTest; @@ -134,6 +135,7 @@ public class IgniteJdbcDriverTestSuite extends TestSuite { // New thin JDBC suite.addTest(new TestSuite(JdbcThinConnectionSelfTest.class)); + suite.addTest(new TestSuite(JdbcThinTcpIoTest.class)); suite.addTest(new TestSuite(JdbcThinConnectionSSLTest.class)); suite.addTest(new TestSuite(JdbcThinPreparedStatementSelfTest.class)); suite.addTest(new TestSuite(JdbcThinResultSetSelfTest.class)); http://git-wip-us.apache.org/repos/asf/ignite/blob/9fd0f4cd/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTcpIoTest.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTcpIoTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTcpIoTest.java new file mode 100644 index 0000000..07780dd --- /dev/null +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTcpIoTest.java @@ -0,0 +1,165 @@ +/* + * 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.jdbc.thin; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.sql.SQLException; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.ignite.internal.jdbc.thin.ConnectionPropertiesImpl; +import org.apache.ignite.internal.jdbc.thin.JdbcThinTcpIo; +import org.apache.ignite.internal.processors.odbc.ClientListenerProtocolVersion; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +/** + * Tests for JdbcThinTcpIo. + */ +@SuppressWarnings("ThrowableNotThrown") +public class JdbcThinTcpIoTest extends GridCommonAbstractTest { + /** Server port range. */ + private static final int[] SERVER_PORT_RANGE = {59000, 59020}; + + /** Inaccessible addresses. */ + private static final String INACCESSIBLE_ADDRESSES[] = {"123.45.67.89", "123.45.67.90"}; + + /** + * Create test server socket. + * + * @return Server socket. + */ + private ServerSocket createServerSocket(CountDownLatch checkConnection) { + for (int port = SERVER_PORT_RANGE[0]; port <= SERVER_PORT_RANGE[1]; port++) { + try { + final ServerSocket serverSock = new ServerSocket(port); + + System.out.println("Created server socket: " + port); + + if (checkConnection != null) { + new Thread(new Runnable() { + @Override public void run() { + try (Socket sock = serverSock.accept()) { + checkConnection.countDown(); + } + catch (IOException ignore) { + // No-op + } + } + }).start(); + } + + return serverSock; + } + catch (IOException ignore) { + // No-op + } + } + + fail("Server socket wasn't created."); + + return null; + } + + /** + * Create JdbcThinTcpIo instance. + * + * @param addrs IP-addresses. + * @param port Server socket port. + * @return JdbcThinTcpIo instance. + * @throws SQLException On connection error or reject. + */ + private JdbcThinTcpIo createTcpIo(String[] addrs, int port) throws SQLException { + ConnectionPropertiesImpl connProps = new ConnectionPropertiesImpl(); + + connProps.setHost("test.domain.name"); + + connProps.setPort(port); + + return new JdbcThinTcpIo(connProps) { + @Override protected InetAddress[] getAllAddressesByHost(String host) throws UnknownHostException { + InetAddress[] addresses = new InetAddress[addrs.length]; + + for (int i = 0; i < addrs.length; i++) + addresses[i] = InetAddress.getByName(addrs[i]); + + return addresses; + } + + @Override public void handshake(ClientListenerProtocolVersion ver) { + // Skip handshake. + } + }; + } + + /** + * Test connection to host which has inaccessible A-records. + * + * @throws SQLException On connection error or reject. + * @throws IOException On IO error in handshake. + */ + public void testHostWithManyAddresses() throws SQLException, IOException, InterruptedException { + CountDownLatch connectionAccepted = new CountDownLatch(1); + + try (ServerSocket sock = createServerSocket(connectionAccepted)) { + String[] addrs = {INACCESSIBLE_ADDRESSES[0], "127.0.0.1", INACCESSIBLE_ADDRESSES[1]}; + + JdbcThinTcpIo jdbcThinTcpIo = createTcpIo(addrs, sock.getLocalPort()); + + try { + jdbcThinTcpIo.start(500); + + // Check connection + assertTrue(connectionAccepted.await(1000, TimeUnit.MILLISECONDS)); + } + finally { + jdbcThinTcpIo.close(); + } + } + } + + /** + * Test exception text (should contain inaccessible ip addresses list). + * + * @throws SQLException On connection error or reject. + * @throws IOException On IO error in handshake. + */ + public void testExceptionMessage() throws SQLException, IOException { + try (ServerSocket sock = createServerSocket(null)) { + String[] addrs = {INACCESSIBLE_ADDRESSES[0], INACCESSIBLE_ADDRESSES[1]}; + + JdbcThinTcpIo jdbcThinTcpIo = createTcpIo(addrs, sock.getLocalPort()); + + Throwable throwable = GridTestUtils.assertThrows(null, new Callable<Object>() { + @Override public Object call() throws Exception { + jdbcThinTcpIo.start(500); + return null; + } + }, SQLException.class, null); + + String msg = throwable.getMessage(); + + for (String addr : addrs) + assertTrue(String.format("Exception message should contain %s", addr), msg.contains(addr)); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/9fd0f4cd/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java index 4b2c477..7aa6c33 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java @@ -20,9 +20,13 @@ package org.apache.ignite.internal.jdbc.thin; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.UnknownHostException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.binary.BinaryWriterExImpl; @@ -102,7 +106,7 @@ public class JdbcThinTcpIo { * * @param connProps Connection properties. */ - JdbcThinTcpIo(ConnectionProperties connProps) { + public JdbcThinTcpIo(ConnectionProperties connProps) { this.connProps = connProps; } @@ -111,6 +115,79 @@ public class JdbcThinTcpIo { * @throws IOException On IO error in handshake. */ public void start() throws SQLException, IOException { + start(0); + } + + /** + * @param timeout Socket connection timeout in ms. + * @throws SQLException On connection error or reject. + * @throws IOException On IO error in handshake. + */ + public void start(int timeout) throws SQLException, IOException { + InetAddress[] addrs; + + try { + addrs = getAllAddressesByHost(connProps.getHost()); + } + catch (UnknownHostException exception) { + throw new SQLException("Failed to connect to server [host=" + connProps.getHost() + + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, exception); + } + + List<String> inaccessibleAddrs = null; + + List<Exception> exceptions = null; + + for (InetAddress addr : addrs) { + try { + connect(addr, timeout); + + break; + } + catch (IOException | SQLException exception) { + if (inaccessibleAddrs == null) + inaccessibleAddrs = new ArrayList<>(); + + inaccessibleAddrs.add(addr.getHostAddress()); + + if (exceptions == null) + exceptions = new ArrayList<>(); + + exceptions.add(exception); + } + } + + if ((inaccessibleAddrs != null) && (inaccessibleAddrs.size() == addrs.length) && (exceptions != null)) { + if (exceptions.size() == 1) { + Exception ex = exceptions.get(0); + + if (ex instanceof SQLException) + throw (SQLException)ex; + else if (ex instanceof IOException) + throw (IOException)ex; + } + + SQLException e = new SQLException("Failed to connect to server [host=" + connProps.getHost() + ", addresses=" + + inaccessibleAddrs + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED); + + for (Exception ex : exceptions) + e.addSuppressed(ex); + + throw e; + } + + handshake(CURRENT_VER); + } + + /** + * Connect to host. + * + * @param addr Address. + * @param timeout Socket connection timeout in ms. + * @throws IOException On IO error. + * @throws SQLException On connection reject. + */ + private void connect(InetAddress addr, int timeout) throws IOException, SQLException { Socket sock; if (ConnectionProperties.SSL_MODE_REQUIRE.equalsIgnoreCase(connProps.getSslMode())) @@ -119,10 +196,10 @@ public class JdbcThinTcpIo { sock = new Socket(); try { - sock.connect(new InetSocketAddress(connProps.getHost(), connProps.getPort())); + sock.connect(new InetSocketAddress(addr, connProps.getPort()), timeout); } catch (IOException e) { - throw new SQLException("Failed to connect to server [host=" + connProps.getHost() + + throw new SQLException("Failed to connect to server [host=" + addr + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); } } @@ -149,8 +226,17 @@ public class JdbcThinTcpIo { throw new SQLException("Failed to connect to server [host=" + connProps.getHost() + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); } + } - handshake(CURRENT_VER); + /** + * Get all addresses by host name. + * + * @param host Host name. + * @return Addresses. + * @throws UnknownHostException If host is unavailable. + */ + protected InetAddress[] getAllAddressesByHost(String host) throws UnknownHostException { + return InetAddress.getAllByName(host); } /**
