From d847dabdb0956e50e8ffaec44ad1e711da4cb370 Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Thu, 19 Jan 2023 00:54:09 -0800 Subject: [PATCH 1/2] Add throttling error message to tests (#2044) --- .../jdbc/fedauth/ErrorMessageTest.java | 37 +++++++++++-------- .../sqlserver/jdbc/fedauth/FedauthCommon.java | 1 + .../jdbc/resiliency/BasicConnectionTest.java | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java index 933e7559cc..7ca0ccbe73 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java @@ -12,7 +12,6 @@ import java.sql.SQLException; import java.util.Properties; -import com.microsoft.sqlserver.jdbc.TestUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -227,10 +226,11 @@ public void testADPasswordUnregisteredUserWithConnectionStringUserName() throws fail(EXPECTED_EXCEPTION_NOT_THROWN); } catch (SQLServerException e) { assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), - e.getMessage() + (e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + badUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_ADD)); + && e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_ADD)) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED)); } } @@ -248,10 +248,11 @@ public void testADPasswordUnregisteredUserWithDatasource() throws SQLException { fail(EXPECTED_EXCEPTION_NOT_THROWN); } catch (SQLServerException e) { assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), - e.getMessage() + (e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + badUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_ADD)); + && e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_ADD)) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED)); } } @@ -262,10 +263,11 @@ public void testADPasswordUnregisteredUserWithConnectionStringUser() throws SQLE fail(EXPECTED_EXCEPTION_NOT_THROWN); } catch (SQLServerException e) { assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), - e.getMessage() + (e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + badUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_ADD)); + && e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_ADD)) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED)); } } @@ -398,11 +400,12 @@ public void testADPasswordWrongPasswordWithConnectionStringUserName() throws SQL fail(EXPECTED_EXCEPTION_NOT_THROWN); } - assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), e.getMessage() + assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), (e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && (e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") - || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY))); + && e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") + || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY)) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED)); } } @@ -423,11 +426,12 @@ public void testADPasswordWrongPasswordWithDatasource() throws SQLException { fail(EXPECTED_EXCEPTION_NOT_THROWN); } - assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), e.getMessage() + assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), (e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && (e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") - || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY))); + && e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") + || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY)) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED)); } } @@ -442,11 +446,12 @@ public void testADPasswordWrongPasswordWithConnectionStringUser() throws SQLExce fail(EXPECTED_EXCEPTION_NOT_THROWN); } - assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), e.getMessage() + assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), (e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && (e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") - || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY))); + && e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") + || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY)) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED)); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java index 53f34adb65..aa26e7af3b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java @@ -83,6 +83,7 @@ public class FedauthCommon extends AbstractTest { static final String ERR_MSG_RESULTSET_IS_CLOSED = TestUtils.R_BUNDLE.getString("R_resultsetClosed"); static final String ERR_MSG_SOCKET_CLOSED = TestResource.getResource("R_socketClosed"); static final String ERR_TCPIP_CONNECTION = TestResource.getResource("R_tcpipConnectionToHost"); + static final String ERR_MSG_REQUEST_THROTTLED = "Request was throttled"; enum SqlAuthentication { NotSpecified, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java index 8b508e5b9c..050eb6a584 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java @@ -57,7 +57,7 @@ public void testBasicConnectionAAD() throws SQLException { org.junit.Assume.assumeTrue(azureServer != null && !azureServer.isEmpty()); basicReconnect("jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";user=" + azureUserName - + ";password=" + azurePassword + ";loginTimeout=30;Authentication=ActiveDirectoryPassword"); + + ";password=" + azurePassword + ";loginTimeout=90;Authentication=ActiveDirectoryPassword"); } @Test From b4aa61e2535983105177ecdc8bd3cc09c1f2f33f Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Thu, 19 Jan 2023 12:53:33 -0800 Subject: [PATCH 2/2] Check for jdk.net.ExtendedSocketOptions and ignore errors (#2043) --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 41 ++++++++++++++++++- .../sqlserver/jdbc/SQLServerJdbc42.java | 12 ------ .../sqlserver/jdbc/SQLServerJdbc43.java | 24 ----------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 9c5a5596b5..3766636878 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -15,6 +15,8 @@ import java.io.Reader; import java.io.Serializable; import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; @@ -25,6 +27,7 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketOption; import java.net.SocketTimeoutException; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -600,6 +603,13 @@ final class TDSChannel implements Serializable { private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Channel"); + /** + * From jdk.net.ExtendedSocketOption for setting TCP keep-alive options + */ + private static Method socketSetOptionMethod = null; + private static SocketOption socketKeepIdleOption = null; + private static SocketOption socketKeepIntervalOption = null; + final Logger getLogger() { return logger; } @@ -717,7 +727,7 @@ final InetSocketAddress open(String host, int port, int timeoutMillis, boolean u // Set socket options tcpSocket.setTcpNoDelay(true); tcpSocket.setKeepAlive(true); - DriverJDBCVersion.setSocketOptions(tcpSocket, this); + setSocketOptions(tcpSocket, this); // set SO_TIMEOUT int socketTimeout = con.getSocketTimeoutMilliseconds(); @@ -731,6 +741,35 @@ final InetSocketAddress open(String host, int port, int timeoutMillis, boolean u return (InetSocketAddress) channelSocket.getRemoteSocketAddress(); } + /** + * Set TCP keep-alive options for idle connection resiliency + */ + @SuppressWarnings("unchecked") + private void setSocketOptions(Socket tcpSocket, TDSChannel channel) throws IOException { + try { + if (socketSetOptionMethod == null) { + socketSetOptionMethod = Socket.class.getMethod("setOption", SocketOption.class, Object.class); + Class clazz = Class.forName("jdk.net.ExtendedSocketOptions"); + socketKeepIdleOption = (SocketOption) clazz.getDeclaredField("TCP_KEEPIDLE").get(null); + socketKeepIntervalOption = (SocketOption) clazz.getDeclaredField("TCP_KEEPINTERVAL").get(null); + } else { + if (logger.isLoggable(Level.FINER)) { + logger.finer(channel.toString() + ": Setting KeepAlive extended socket options."); + } + + socketSetOptionMethod.invoke(tcpSocket, socketKeepIdleOption, 30); // 30 seconds + socketSetOptionMethod.invoke(tcpSocket, socketKeepIntervalOption, 1); // 1 second + + } + } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException | IllegalAccessException + | InvocationTargetException e) { + if (logger.isLoggable(Level.FINER)) { + logger.finer( + channel.toString() + ": KeepAlive extended socket options not supported on this platform."); + } + } + } + /** * Disables SSL on this TDS channel. */ diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java index 118234199a..c11f6e3b26 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java @@ -5,10 +5,7 @@ package com.microsoft.sqlserver.jdbc; -import java.net.Socket; import java.sql.BatchUpdateException; -import java.util.logging.Level; -import java.util.logging.Logger; /** @@ -23,8 +20,6 @@ final class DriverJDBCVersion { static final int MAJOR = 4; static final int MINOR = 2; - private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.DriverJDBCVersion"); - static final boolean checkSupportsJDBC43() { return false; } @@ -45,11 +40,4 @@ static SQLServerConnection getSQLServerConnection(String parentInfo) throws SQLS static int getProcessId() { return pid; } - - static void setSocketOptions(Socket tcpSocket, TDSChannel channel) { - if (logger.isLoggable(Level.FINER)) { - logger.finer( - "Socket.supportedOptions() not available on this JVM. Extended KeepAlive options will not be set."); - } - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java index 73f5f8adba..1a6aebc65b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java @@ -5,15 +5,7 @@ package com.microsoft.sqlserver.jdbc; -import java.io.IOException; -import java.net.Socket; -import java.net.SocketOption; import java.sql.BatchUpdateException; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import jdk.net.ExtendedSocketOptions; /** @@ -28,8 +20,6 @@ final class DriverJDBCVersion { static final int MAJOR = 4; static final int MINOR = 3; - private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.DriverJDBCVersion"); - static final boolean checkSupportsJDBC43() { return true; } @@ -61,18 +51,4 @@ static SQLServerConnection getSQLServerConnection(String parentInfo) throws SQLS static int getProcessId() { return pid; } - - static void setSocketOptions(Socket tcpSocket, TDSChannel channel) throws IOException { - Set> options = tcpSocket.supportedOptions(); - if (options.contains(ExtendedSocketOptions.TCP_KEEPIDLE) - && options.contains(ExtendedSocketOptions.TCP_KEEPINTERVAL)) { - if (logger.isLoggable(Level.FINER)) { - logger.finer(channel.toString() + ": Setting KeepAlive extended socket options."); - } - tcpSocket.setOption(ExtendedSocketOptions.TCP_KEEPIDLE, 30); // 30 seconds - tcpSocket.setOption(ExtendedSocketOptions.TCP_KEEPINTERVAL, 1); // 1 second - } else if (logger.isLoggable(Level.FINER)) { - logger.finer(channel.toString() + ": KeepAlive extended socket options not supported on this platform."); - } - } }