From 82bd3352f0f078bf17c739a0e49e2569d5e2c76b Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Tue, 21 May 2024 15:42:49 -0700 Subject: [PATCH 1/5] Updates for running tests with managed identity (#2416) --- .../jdbc/AlwaysEncrypted/AESetup.java | 6 +- .../CallableStatementTest.java | 2 +- .../jdbc/SQLServerConnectionTest.java | 37 ++++- .../sqlserver/jdbc/TestResource.java | 6 +- .../microsoft/sqlserver/jdbc/TestUtils.java | 34 ++++- .../jdbc/connection/PoolingTest.java | 4 + .../jdbc/connection/TimeoutTest.java | 139 ++++++++++++------ .../DatabaseMetaDataTest.java | 11 +- .../sqlserver/jdbc/fedauth/FedauthWithAE.java | 2 +- .../sqlserver/jdbc/fips/FipsTest.java | 18 ++- .../jdbc/unit/SQLServerErrorTest.java | 5 + .../unit/statement/BatchExecutionTest.java | 6 + .../unit/statement/PreparedStatementTest.java | 4 +- .../sqlserver/testframework/AbstractTest.java | 28 ++-- 14 files changed, 231 insertions(+), 71 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java index 541f2da16f..1a12194bbe 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -332,7 +332,7 @@ protected static void createTable(String tableName, String cekName, String table TestUtils.dropTableIfExists(tableName, stmt); sql = String.format(createSql, tableName, sql); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } @@ -366,7 +366,7 @@ protected static void createPrecisionTable(String tableName, String table[][], S } sql = String.format(createSql, tableName, sql); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } @@ -394,7 +394,7 @@ protected static void createScaleTable(String tableName, String table[][], Strin sql = String.format(createSql, tableName, sql); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 95531d6981..d259b35a55 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -2201,7 +2201,7 @@ protected static void createDateTableCallableStatement(String cekName) throws SQ SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(DATE_TABLE_AE, stmt); stmt.execute(sql); - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } catch (SQLException e) { fail(e.getMessage()); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index eabbbb574d..70e3a318df 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -481,7 +481,13 @@ public void testConnectCountInLoginAndCorrectRetryCount() { assertTrue(con == null, TestResource.getResource("R_shouldNotConnect")); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")), e.getMessage()); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && (e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase()) + || e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_MInotAvailable").toLowerCase()))), + e.getMessage()); long totalTime = System.currentTimeMillis() - timerStart; // Maximum is unknown, but is needs to be less than longLoginTimeout or else this is an issue. @@ -756,13 +762,22 @@ public void testIncorrectDatabase() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")), e.getMessage()); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } @Test public void testIncorrectUserName() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long timerStart = 0; long timerEnd = 0; final long milsecs = threshHoldForNoRetryInMilliseconds; @@ -780,13 +795,22 @@ public void testIncorrectUserName() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_loginFailed"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_loginFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } @Test public void testIncorrectPassword() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long timerStart = 0; long timerEnd = 0; final long milsecs = threshHoldForNoRetryInMilliseconds; @@ -804,7 +828,12 @@ public void testIncorrectPassword() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_loginFailed"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_loginFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index e14bb671ae..fe817797c5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -212,5 +212,9 @@ protected Object[][] getContents() { {"R_failedFedauth", "Failed to acquire fedauth token: "}, {"R_noLoginModulesConfiguredForJdbcDriver", "javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"}, - {"R_unexpectedThreadCount", "Thread count is higher than expected."}}; + {"R_unexpectedThreadCount", "Thread count is higher than expected."}, + {"R_expectedClassDoesNotMatchActualClass", + "Expected column class {0} does not match actual column class {1} for column {2}."}, + {"R_loginFailedMI", "Login failed for user ''"}, + {"R_MInotAvailable", "Managed Identity authentication is not available"},}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java index af5d54e190..f15fbfaf2d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java @@ -523,7 +523,31 @@ public static void dropDatabaseIfExists(String databaseName, String connectionSt */ public static void dropSchemaIfExists(String schemaName, Statement stmt) throws SQLException { stmt.execute("if EXISTS (SELECT * FROM sys.schemas where name = '" + escapeSingleQuotes(schemaName) - + "') drop schema " + AbstractSQLGenerator.escapeIdentifier(schemaName)); + + "') DROP SCHEMA" + AbstractSQLGenerator.escapeIdentifier(schemaName)); + } + + /** + * mimic "DROP USER..." + * + * @param userName + * @param stmt + * @throws SQLException + */ + public static void dropUserIfExists(String userName, Statement stmt) throws SQLException { + stmt.execute("IF EXISTS (SELECT * FROM sys.sysusers where name = '" + escapeSingleQuotes(userName) + + "') DROP USER " + AbstractSQLGenerator.escapeIdentifier(userName)); + } + + /** + * mimic "DROP LOGIN..." + * + * @param userName + * @param stmt + * @throws SQLException + */ + public static void dropLoginIfExists(String userName, Statement stmt) throws SQLException { + stmt.execute("IF EXISTS (SELECT * FROM sys.sysusers where name = '" + escapeSingleQuotes(userName) + + "') DROP LOGIN " + AbstractSQLGenerator.escapeIdentifier(userName)); } /** @@ -1099,4 +1123,12 @@ public static String getConnectionID( SQLServerConnection conn = (SQLServerConnection) physicalConnection.get(pc); return (String) traceID.get(conn); } + + public static void freeProcCache(Statement stmt) { + try { + stmt.execute("DBCC FREEPROCCACHE"); + } catch (Exception e) { + // ignore error - some tests fails due to permission issues from managed identity, this does not seem to affect tests + } + } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java index d99d846ef5..57a11a7258 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java @@ -152,6 +152,10 @@ public void testConnectionPoolClose() throws SQLException { @Test public void testConnectionPoolClientConnectionId() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + SQLServerXADataSource ds = new SQLServerXADataSource(); ds.setURL(connectionString); PooledConnection pc = null; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index af2bda8af0..f8d9d9da50 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -59,11 +59,13 @@ public void testDefaultLoginTimeout() { } catch (Exception e) { timerEnd = System.currentTimeMillis(); - assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } @@ -82,11 +84,13 @@ public void testURLLoginTimeout() { } catch (Exception e) { timerEnd = System.currentTimeMillis(); - assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } @@ -106,11 +110,13 @@ public void testDMLoginTimeoutApplied() { } catch (Exception e) { timerEnd = System.currentTimeMillis(); - assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } @@ -132,12 +138,13 @@ public void testDMLoginTimeoutNotApplied() { timerEnd = System.currentTimeMillis(); assertTrue( - (e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage() - .contains(TestResource - .getResource("R_connectTimedOut")) - : false), + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } verifyTimeout(timerEnd - timerStart, timeout); @@ -146,6 +153,36 @@ public void testDMLoginTimeoutNotApplied() { } } + // Test connect retry set to 0 (disabled) + @Test + public void () { + long totalTime = 0; + long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long timeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval + + // non existent server with long loginTimeout, should return fast if no retries at all + try (Connection con = PrepUtil.getConnection( + "jdbc:sqlserver://" + randomServer + ";transparentNetworkIPResolution=false;loginTimeout=" + timeout + + ";connectRetryCount=0;connectInterval=" + interval)) { + fail(TestResource.getResource("R_shouldNotConnect")); + } catch (Exception e) { + totalTime = System.currentTimeMillis() - timerStart; + + assertTrue( + e.getMessage().matches(TestUtils.formatErrorMsg("R_tcpipConnectionFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + e.getMessage()); + } + + // if there was a retry then it would take at least 1 interval long, so if < interval means there were no retries + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(interval), + "total time: " + totalTime + " interval: " + TimeUnit.SECONDS.toMillis(interval)); + } + // Test connect retry for non-existent server with loginTimeout @Test public void testConnectRetryBadServer() { @@ -160,11 +197,13 @@ public void testConnectRetryBadServer() { } catch (Exception e) { timerEnd = System.currentTimeMillis(); - assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } @@ -174,7 +213,11 @@ public void testConnectRetryBadServer() { // Test connect retry for database error @Test public void testConnectRetryServerError() { - long timerEnd = 0; + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + + long totalTime = 0; long timerStart = System.currentTimeMillis(); // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry @@ -188,11 +231,13 @@ public void testConnectRetryServerError() { } catch (Exception e) { timerEnd = System.currentTimeMillis(); - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } @@ -203,7 +248,11 @@ public void testConnectRetryServerError() { // Test connect retry for database error using Datasource @Test public void testConnectRetryServerErrorDS() { - long timerEnd = 0; + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + + long totalTime = 0; long timerStart = System.currentTimeMillis(); // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry @@ -219,11 +268,13 @@ public void testConnectRetryServerErrorDS() { try (Connection con = PrepUtil.getConnection(connectStr)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); timerEnd = System.currentTimeMillis(); } @@ -248,11 +299,13 @@ public void testConnectRetryTimeout() { } catch (Exception e) { timerEnd = System.currentTimeMillis(); - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java index ff2f2fc73a..3534e9d537 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -158,6 +158,10 @@ public void testGetURL() throws SQLException { */ @Test public void testDBUserLogin() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + try (Connection conn = getConnection()) { DatabaseMetaData databaseMetaData = conn.getMetaData(); String connectionString = getConnectionString(); @@ -181,7 +185,8 @@ public void testDBUserLogin() throws SQLException { assertNotNull(userName, TestResource.getResource("R_userNameNull")); assertTrue(userName.equalsIgnoreCase(userFromConnectionString), - TestResource.getResource("R_userNameNotMatch")); + TestResource.getResource("R_userNameNotMatch") + "userName: " + userName + "from connectio string: " + + userFromConnectionString); } catch (Exception e) { fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage()); } @@ -904,6 +909,10 @@ public static void terminate() throws SQLException { try (Statement stmt = connection.createStatement()) { TestUtils.dropTableIfExists(tableName, stmt); TestUtils.dropFunctionIfExists(functionName, stmt); + + TestUtils.dropTableWithSchemaIfExists(tableNameWithSchema, stmt); + TestUtils.dropProcedureWithSchemaIfExists(sprocWithSchema, stmt); + TestUtils.dropSchemaIfExists(schema, stmt); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java index bfd09d3b92..1e0112f028 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java @@ -307,7 +307,7 @@ private void createCMK(String cmkName, String keyStoreName, String keyPath, Stat private void callDbccFreeProcCache() throws SQLException { try (Connection connection = DriverManager.getConnection(adPasswordConnectionStr); Statement stmt = connection.createStatement()) { - stmt.execute("DBCC FREEPROCCACHE"); + TestUtils.freeProcCache(stmt); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java index 10853f2f6f..2e80407d3a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java @@ -50,7 +50,7 @@ public void fipsTrustServerCertificateTest() throws Exception { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidTrustCert")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } @@ -62,13 +62,18 @@ public void fipsTrustServerCertificateTest() throws Exception { */ @Test public void fipsEncryptTest() throws Exception { + // test doesn't apply to managed identity as encrypt is set to on by default + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null && !(auth.equalsIgnoreCase("ActiveDirectoryManagedIdentity") + || auth.equalsIgnoreCase("ActiveDirectoryMSI"))); + Properties props = buildConnectionProperties(); props.setProperty(Constants.ENCRYPT, Boolean.FALSE.toString()); try (Connection con = PrepUtil.getConnection(connectionString, props)) { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidEncrypt")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } @@ -118,6 +123,11 @@ public void fipsDataSourcePropertyTest() throws Exception { */ @Test public void fipsDatSourceEncrypt() { + // test doesn't apply to managed identity as encrypt is set to on by default + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null && !(auth.equalsIgnoreCase("ActiveDirectoryManagedIdentity") + || auth.equalsIgnoreCase("ActiveDirectoryMSI"))); + SQLServerDataSource ds = new SQLServerDataSource(); setDataSourceProperties(ds); ds.setEncrypt(Constants.FALSE); @@ -126,7 +136,7 @@ public void fipsDatSourceEncrypt() { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidEncrypt")); + TestResource.getResource("R_invalidEncrypt") + ": " + e.getMessage()); } } @@ -146,7 +156,7 @@ public void fipsDataSourceTrustServerCertificateTest() throws Exception { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidTrustCert")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java index b4ca10c046..7c7dc377e7 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java @@ -42,6 +42,11 @@ public static void setupTests() throws Exception { @Test @Tag(Constants.xAzureSQLDW) public void testLoginFailedError() { + // test to remove password only valid for password auth + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + SQLServerDataSource ds = new SQLServerDataSource(); ds.setURL(connectionString); ds.setLoginTimeout(loginTimeOutInSeconds); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java index 0dace62b22..47b771dc34 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java @@ -188,6 +188,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep pstmt.setTimestamp(1, timestamp, gmtCal); pstmt.addBatch(); pstmt.executeBatch(); + } catch (Exception e) { + fail(e.getMessage()); } // Insert Timestamp using bulkcopy for batch insert @@ -200,6 +202,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep pstmt.setTimestamp(1, timestamp, gmtCal); pstmt.addBatch(); pstmt.executeBatch(); + } catch (Exception e) { + fail(e.getMessage()); } // Compare Timestamp values inserted, should be the same @@ -225,6 +229,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep assertEquals(ts0, ts1, failureMsg); assertEquals(t0, t1, failureMsg); assertEquals(d0, d1, failureMsg); + } catch (Exception e) { + fail(e.getMessage()); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java index 79862dfcd4..b21372c965 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java @@ -258,7 +258,9 @@ public void testBatchedUnprepare() throws SQLException { con.setStatementPoolingCacheSize(0); // Clean-up proc cache - this.executeSQL(con, "DBCC FREEPROCCACHE;"); + try (Statement stmt = con.createStatement()) { + TestUtils.freeProcCache(stmt); + } String lookupUniqueifier = UUID.randomUUID().toString(); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index e1a321c2f5..a353aba114 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -5,6 +5,8 @@ package com.microsoft.sqlserver.testframework; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -304,19 +306,23 @@ protected static void setupConnectionString() { } protected static void setConnection() throws Exception { - setupConnectionString(); + try { + setupConnectionString(); - Assertions.assertNotNull(connectionString, TestResource.getResource("R_ConnectionStringNull")); - Class.forName(Constants.MSSQL_JDBC_PACKAGE + ".SQLServerDriver"); - if (!SQLServerDriver.isRegistered()) { - SQLServerDriver.register(); - } - if (null == connection || connection.isClosed()) { - connection = getConnection(); - } - isSqlAzureOrAzureDW(connection); + Assertions.assertNotNull(connectionString, TestResource.getResource("R_ConnectionStringNull")); + Class.forName(Constants.MSSQL_JDBC_PACKAGE + ".SQLServerDriver"); + if (!SQLServerDriver.isRegistered()) { + SQLServerDriver.register(); + } + if (null == connection || connection.isClosed()) { + connection = getConnection(); + } + isSqlAzureOrAzureDW(connection); - checkSqlOS(connection); + checkSqlOS(connection); + } catch (Exception e) { + fail("setConnection failed, connectionString=" + connectionString + "\nException: " + e.getMessage()); + } } /** From f3b66e4424bfa175a75f95eacb23067433ecbcbc Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 22 May 2024 16:50:25 -0700 Subject: [PATCH 2/5] Updated tests for removal of secrets --- .../jdbc/connection/TimeoutTest.java | 250 +++++++----------- .../DatabaseMetaDataTest.java | 4 - 2 files changed, 102 insertions(+), 152 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index f8d9d9da50..00716150d3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.fail; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -36,71 +35,85 @@ @RunWith(JUnitPlatform.class) -@Tag("slow") public class TimeoutTest extends AbstractTest { static String randomServer = RandomUtil.getIdentifier("Server"); static String waitForDelaySPName = RandomUtil.getIdentifier("waitForDelaySP"); static final int waitForDelaySeconds = 10; - static final int defaultTimeout = 15; // loginTimeout default value + static final int defaultTimeout = 30; // loginTimeout default value @BeforeAll public static void setupTests() throws Exception { setConnection(); } + /* + * TODO: + * The tests below uses a simple interval counting logic to determine whether there was at least 1 retry. + * Given the interval is long enough, then 1 retry should take at least 1 interval long, so if it took < 1 interval, then it assumes there were no retry. However, this only works if TNIR or failover is not enabled since those cases should retry but no wait interval in between. So this interval counting can not detect these cases. + * Note a better and more reliable way would be to check attemptNumber using reflection to determine the number of retries. + */ + + // test default loginTimeout used if not specified in connection string @Test public void testDefaultLoginTimeout() { - long timerEnd = 0; - + long totalTime = 0; long timerStart = System.currentTimeMillis(); - // Try a non existing server and see if the default timeout is 15 seconds - try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + "connectRetryCount=0")) { + + // non existing server and default values to see if took default timeout + try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, defaultTimeout); + // time should be < default loginTimeout + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(defaultTimeout), + "total time: " + totalTime + " default loginTimout: " + TimeUnit.SECONDS.toMillis(defaultTimeout)); } + // test setting loginTimeout value @Test public void testURLLoginTimeout() { - long timerEnd = 0; - int timeout = 10; + long totalTime = 0; + int timeout = 15; long timerStart = System.currentTimeMillis(); + // non existing server and set loginTimeout try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + ";logintimeout=" + timeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, timeout); + // time should be < set loginTimeout + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), + "total time: " + totalTime + " loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); } + // test setting timeout in DM @Test public void testDMLoginTimeoutApplied() { - long timerEnd = 0; - int timeout = 10; + long totalTime = 0; + int timeout = 15; DriverManager.setLoginTimeout(timeout); long timerStart = System.currentTimeMillis(); @@ -108,25 +121,28 @@ public void testDMLoginTimeoutApplied() { try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, timeout); + // time should be < DM timeout + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), + "total time: " + totalTime + " DM loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); } + // test that setting in connection string overrides value set in DM @Test public void testDMLoginTimeoutNotApplied() { - long timerEnd = 0; - int timeout = 10; + long totalTime = 0; + int timeout = 15; try { DriverManager.setLoginTimeout(timeout * 3); // 30 seconds long timerStart = System.currentTimeMillis(); @@ -135,19 +151,22 @@ public void testDMLoginTimeoutNotApplied() { .getConnection("jdbc:sqlserver://" + randomServer + ";loginTimeout=" + timeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null - && e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, timeout); + + // time should be < connection string loginTimeout + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), + "total time: " + totalTime + " loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); } finally { DriverManager.setLoginTimeout(0); // Default to 0 again } @@ -155,7 +174,7 @@ public void testDMLoginTimeoutNotApplied() { // Test connect retry set to 0 (disabled) @Test - public void () { + public void testConnectRetryDisable() { long totalTime = 0; long timerStart = System.currentTimeMillis(); int interval = defaultTimeout; // long interval so we can tell if there was a retry @@ -172,9 +191,9 @@ public void () { assertTrue( e.getMessage().matches(TestUtils.formatErrorMsg("R_tcpipConnectionFailed")) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } @@ -186,28 +205,29 @@ public void () { // Test connect retry for non-existent server with loginTimeout @Test public void testConnectRetryBadServer() { - long timerEnd = 0; + long totalTime = 0; long timerStart = System.currentTimeMillis(); - int loginTimeout = 15; + int timeout = 15; // non existent server with very short loginTimeout, no retry will happen as not a transient error - try (Connection con = PrepUtil - .getConnection("jdbc:sqlserver://" + randomServer + ";loginTimeout=" + loginTimeout)) { + try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + ";loginTimeout=" + timeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, loginTimeout); + // time should be < loginTimeout set + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), + "total time: " + totalTime + " loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); } // Test connect retry for database error @@ -219,30 +239,33 @@ public void testConnectRetryServerError() { long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long timeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry - int connectRetryCount = new Random().nextInt(256); - int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" + connectRetryCount - + ";connectRetryInterval=" + connectRetryInterval)) { + + ";loginTimeout=" + timeout + ";connectRetryCount=" + 1 + ";connectRetryInterval=" + interval + + ";transparentNetworkIPResolution=false")) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - // connect + all retries should always be <= loginTimeout - verifyTimeout(timerEnd - timerStart, defaultTimeout); + // 1 retry should be at least 1 interval long but < 2 intervals + assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, + "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), + "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(interval)); } // Test connect retry for database error using Datasource @@ -254,15 +277,14 @@ public void testConnectRetryServerErrorDS() { long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long loginTimeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry - int connectRetryCount = new Random().nextInt(256); - int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; - + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time SQLServerDataSource ds = new SQLServerDataSource(); String connectStr = TestUtils.addOrOverrideProperty(connectionString, "database", - RandomUtil.getIdentifier("database")) + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" - + connectRetryCount + ";connectRetryInterval=" + connectRetryInterval; + RandomUtil.getIdentifier("database")) + ";logintimeout=" + loginTimeout + ";connectRetryCount=1" + + ";connectRetryInterval=" + interval; updateDataSource(connectStr, ds); try (Connection con = PrepUtil.getConnection(connectStr)) { @@ -272,44 +294,50 @@ public void testConnectRetryServerErrorDS() { (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; } - // connect + all retries should always be <= loginTimeout - verifyTimeout(timerEnd - timerStart, defaultTimeout); + // 1 retry should be at least 1 interval long but < 2 intervals + assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, + "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), + "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(2 * interval)); } // Test connect retry for database error with loginTimeout @Test public void testConnectRetryTimeout() { - long timerEnd = 0; + long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry int loginTimeout = 2; - // non existent database with very short loginTimeout so there is no time to do all retries + // non existent database with very short loginTimeout so there is no time to do any retry try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" - + (new Random().nextInt(defaultTimeout - 1) + 1) + ";loginTimeout=" + loginTimeout)) { + + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" + interval + + ";loginTimeout=" + loginTimeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; assertTrue( (e.getMessage().toLowerCase() .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, loginTimeout); + // if there was a retry then it would take at least 1 interval long, so if < interval means there were no retries + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(interval), + "total time: " + totalTime + " interval: " + TimeUnit.SECONDS.toMillis(interval)); } // Test for detecting Azure server for connection retries @@ -342,83 +370,9 @@ public void testAzureEndpointRetry() { } } - @Test - public void testFailoverInstanceResolution() throws SQLException { - long timerEnd = 0; - long timerStart = System.currentTimeMillis(); - - // Try a non existing server and see if the default timeout is 15 seconds - try (Connection con = PrepUtil - .getConnection("jdbc:sqlserver://" + randomServer + ";databaseName=FailoverDB_abc;failoverPartner=" - + randomServer + "\\foo;user=sa;password=" + RandomUtil.getIdentifier("password"))) { - fail(TestResource.getResource("R_shouldNotConnect")); - } catch (Exception e) { - timerEnd = System.currentTimeMillis(); - - assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), - e.getMessage()); - } - - verifyTimeout(timerEnd - timerStart, defaultTimeout * 2); - } - - @Test - public void testFOInstanceResolution2() throws SQLException { - long timerEnd = 0; - - long timerStart = System.currentTimeMillis(); - try (Connection con = PrepUtil - .getConnection("jdbc:sqlserver://" + randomServer + "\\fooggg;databaseName=FailoverDB;failoverPartner=" - + randomServer + "\\foo;user=sa;password=" + RandomUtil.getIdentifier("password"))) { - fail(TestResource.getResource("R_shouldNotConnect")); - } catch (Exception e) { - timerEnd = System.currentTimeMillis(); - } - - verifyTimeout(timerEnd - timerStart, defaultTimeout); - } - - /** - * Tests that failover is correctly used after a socket timeout, by confirming total time includes socketTimeout - * for both primary and failover server. - */ - @Test - public void testFailoverInstanceResolutionWithSocketTimeout() { - long timerEnd; - long timerStart = System.currentTimeMillis(); - - try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer - + ";databaseName=FailoverDB;failoverPartner=" + randomServer + "\\foo;user=sa;password=" - + RandomUtil.getIdentifier("password") + ";socketTimeout=" + waitForDelaySeconds)) { - fail(TestResource.getResource("R_shouldNotConnect")); - } catch (Exception e) { - timerEnd = System.currentTimeMillis(); - if (!(e instanceof SQLException)) { - fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage()); - } - - // Driver should correctly attempt to connect to db, experience a socketTimeout, attempt to connect to - // failover, and then have another socketTimeout. So, expected total time is 2 x socketTimeout. - long totalTime = timerEnd - timerStart; - long totalExpectedTime = waitForDelaySeconds * 1000L * 2; // We expect 2 * socketTimeout - assertTrue(totalTime >= totalExpectedTime, TestResource.getResource("R_executionNotLong") + "totalTime: " - + totalTime + " expectedTime: " + totalExpectedTime); - } - } - - private void verifyTimeout(long timeDiff, int timeout) { - // Verify that login timeout does not take longer than seconds. - assertTrue(timeDiff < TimeUnit.SECONDS.toMillis(timeout * 2), - "timeout: " + TimeUnit.SECONDS.toMillis(timeout) + " timediff: " + timeDiff); - } - /** * When query timeout occurs, the connection is still usable. - * + * * @throws Exception */ @Test @@ -455,7 +409,7 @@ public void testQueryTimeout() throws Exception { /** * Tests sanity of connection property. - * + * * @throws Exception */ @Test @@ -492,7 +446,7 @@ public void testCancelQueryTimeout() throws Exception { /** * Tests sanity of connection property. - * + * * @throws Exception */ @Test @@ -530,7 +484,7 @@ public void testCancelQueryTimeoutOnStatement() throws Exception { /** * When socketTimeout occurs, the connection will be marked as closed. - * + * * @throws Exception */ @Test diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java index 3534e9d537..4e5214e526 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -909,10 +909,6 @@ public static void terminate() throws SQLException { try (Statement stmt = connection.createStatement()) { TestUtils.dropTableIfExists(tableName, stmt); TestUtils.dropFunctionIfExists(functionName, stmt); - - TestUtils.dropTableWithSchemaIfExists(tableNameWithSchema, stmt); - TestUtils.dropProcedureWithSchemaIfExists(sprocWithSchema, stmt); - TestUtils.dropSchemaIfExists(schema, stmt); } } } From 59d1057337a6189d91ef05f31b6cc2dc0801ca77 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 22 May 2024 19:41:57 -0700 Subject: [PATCH 3/5] Reverted TimeoutTests --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 4 - .../jdbc/connection/TimeoutTest.java | 353 +++++++++--------- 2 files changed, 173 insertions(+), 184 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 1597d0cd30..6e09bd7566 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -6912,10 +6912,6 @@ final boolean readPacket() throws SQLServerException { // if messageType is RPC or QUERY, then increment Counter's state if (tdsChannel.getWriter().checkIfTdsMessageTypeIsBatchOrRPC() && null != command) { - if (logger.isLoggable(Level.FINER)) { - logger.warning(toString() + ": increasing state of counter for TDS Command: " + command.toString()); - } - if (null == command.getCounter()) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); Object[] msgArgs1 = {"TDS command counter"}; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index 00716150d3..43742b821e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -35,85 +36,67 @@ @RunWith(JUnitPlatform.class) +@Tag("slow") public class TimeoutTest extends AbstractTest { static String randomServer = RandomUtil.getIdentifier("Server"); static String waitForDelaySPName = RandomUtil.getIdentifier("waitForDelaySP"); static final int waitForDelaySeconds = 10; - static final int defaultTimeout = 30; // loginTimeout default value + static final int defaultTimeout = 15; // loginTimeout default value @BeforeAll public static void setupTests() throws Exception { setConnection(); } - /* - * TODO: - * The tests below uses a simple interval counting logic to determine whether there was at least 1 retry. - * Given the interval is long enough, then 1 retry should take at least 1 interval long, so if it took < 1 interval, then it assumes there were no retry. However, this only works if TNIR or failover is not enabled since those cases should retry but no wait interval in between. So this interval counting can not detect these cases. - * Note a better and more reliable way would be to check attemptNumber using reflection to determine the number of retries. - */ - - // test default loginTimeout used if not specified in connection string @Test public void testDefaultLoginTimeout() { - long totalTime = 0; - long timerStart = System.currentTimeMillis(); + long timerEnd = 0; - // non existing server and default values to see if took default timeout - try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer)) { + long timerStart = System.currentTimeMillis(); + // Try a non existing server and see if the default timeout is 15 seconds + try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + "connectRetryCount=0")) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); } - // time should be < default loginTimeout - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(defaultTimeout), - "total time: " + totalTime + " default loginTimout: " + TimeUnit.SECONDS.toMillis(defaultTimeout)); + verifyTimeout(timerEnd - timerStart, defaultTimeout); } - // test setting loginTimeout value @Test public void testURLLoginTimeout() { - long totalTime = 0; - int timeout = 15; + long timerEnd = 0; + int timeout = 10; long timerStart = System.currentTimeMillis(); - // non existing server and set loginTimeout try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + ";logintimeout=" + timeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); } - // time should be < set loginTimeout - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), - "total time: " + totalTime + " loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); + verifyTimeout(timerEnd - timerStart, timeout); } - // test setting timeout in DM @Test public void testDMLoginTimeoutApplied() { - long totalTime = 0; - int timeout = 15; + long timerEnd = 0; + int timeout = 10; DriverManager.setLoginTimeout(timeout); long timerStart = System.currentTimeMillis(); @@ -121,28 +104,23 @@ public void testDMLoginTimeoutApplied() { try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); } - // time should be < DM timeout - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), - "total time: " + totalTime + " DM loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); + verifyTimeout(timerEnd - timerStart, timeout); } - // test that setting in connection string overrides value set in DM @Test public void testDMLoginTimeoutNotApplied() { - long totalTime = 0; - int timeout = 15; + long timerEnd = 0; + int timeout = 10; try { DriverManager.setLoginTimeout(timeout * 3); // 30 seconds long timerStart = System.currentTimeMillis(); @@ -151,193 +129,134 @@ public void testDMLoginTimeoutNotApplied() { .getConnection("jdbc:sqlserver://" + randomServer + ";loginTimeout=" + timeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; + timerEnd = System.currentTimeMillis(); assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null - && e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + (e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage() + .contains(TestResource + .getResource("R_connectTimedOut")) + : false), e.getMessage()); } - - // time should be < connection string loginTimeout - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), - "total time: " + totalTime + " loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); + verifyTimeout(timerEnd - timerStart, timeout); } finally { DriverManager.setLoginTimeout(0); // Default to 0 again } } - // Test connect retry set to 0 (disabled) - @Test - public void testConnectRetryDisable() { - long totalTime = 0; - long timerStart = System.currentTimeMillis(); - int interval = defaultTimeout; // long interval so we can tell if there was a retry - long timeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - - // non existent server with long loginTimeout, should return fast if no retries at all - try (Connection con = PrepUtil.getConnection( - "jdbc:sqlserver://" + randomServer + ";transparentNetworkIPResolution=false;loginTimeout=" + timeout - + ";connectRetryCount=0;connectInterval=" + interval)) { - fail(TestResource.getResource("R_shouldNotConnect")); - } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - e.getMessage().matches(TestUtils.formatErrorMsg("R_tcpipConnectionFailed")) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), - e.getMessage()); - } - - // if there was a retry then it would take at least 1 interval long, so if < interval means there were no retries - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(interval), - "total time: " + totalTime + " interval: " + TimeUnit.SECONDS.toMillis(interval)); - } - // Test connect retry for non-existent server with loginTimeout @Test public void testConnectRetryBadServer() { - long totalTime = 0; + long timerEnd = 0; long timerStart = System.currentTimeMillis(); - int timeout = 15; + int loginTimeout = 15; // non existent server with very short loginTimeout, no retry will happen as not a transient error - try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + ";loginTimeout=" + timeout)) { + try (Connection con = PrepUtil + .getConnection("jdbc:sqlserver://" + randomServer + ";loginTimeout=" + loginTimeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_tcpipConnectionToHost").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); } - // time should be < loginTimeout set - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(timeout), - "total time: " + totalTime + " loginTimeout: " + TimeUnit.SECONDS.toMillis(timeout)); + verifyTimeout(timerEnd - timerStart, loginTimeout); } // Test connect retry for database error @Test public void testConnectRetryServerError() { - String auth = TestUtils.getProperty(connectionString, "authentication"); - org.junit.Assume.assumeTrue(auth != null - && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); - - long totalTime = 0; + long timerEnd = 0; long timerStart = System.currentTimeMillis(); - int interval = defaultTimeout; // long interval so we can tell if there was a retry - long timeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry + int connectRetryCount = new Random().nextInt(256); + int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + ";loginTimeout=" + timeout + ";connectRetryCount=" + 1 + ";connectRetryInterval=" + interval - + ";transparentNetworkIPResolution=false")) { + + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" + connectRetryCount + + ";connectRetryInterval=" + connectRetryInterval)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); } - // 1 retry should be at least 1 interval long but < 2 intervals - assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, - "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), - "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(interval)); + // connect + all retries should always be <= loginTimeout + verifyTimeout(timerEnd - timerStart, defaultTimeout); } // Test connect retry for database error using Datasource @Test public void testConnectRetryServerErrorDS() { - String auth = TestUtils.getProperty(connectionString, "authentication"); - org.junit.Assume.assumeTrue(auth != null - && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); - - long totalTime = 0; + long timerEnd = 0; long timerStart = System.currentTimeMillis(); - int interval = defaultTimeout; // long interval so we can tell if there was a retry - long loginTimeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry + int connectRetryCount = new Random().nextInt(256); + int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; + SQLServerDataSource ds = new SQLServerDataSource(); String connectStr = TestUtils.addOrOverrideProperty(connectionString, "database", - RandomUtil.getIdentifier("database")) + ";logintimeout=" + loginTimeout + ";connectRetryCount=1" - + ";connectRetryInterval=" + interval; + RandomUtil.getIdentifier("database")) + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" + + connectRetryCount + ";connectRetryInterval=" + connectRetryInterval; updateDataSource(connectStr, ds); try (Connection con = PrepUtil.getConnection(connectStr)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); - totalTime = System.currentTimeMillis() - timerStart; + timerEnd = System.currentTimeMillis(); } - // 1 retry should be at least 1 interval long but < 2 intervals - assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, - "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), - "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(2 * interval)); + // connect + all retries should always be <= loginTimeout + verifyTimeout(timerEnd - timerStart, defaultTimeout); } // Test connect retry for database error with loginTimeout @Test public void testConnectRetryTimeout() { - long totalTime = 0; + long timerEnd = 0; long timerStart = System.currentTimeMillis(); - int interval = defaultTimeout; // long interval so we can tell if there was a retry int loginTimeout = 2; - // non existent database with very short loginTimeout so there is no time to do any retry + // non existent database with very short loginTimeout so there is no time to do all retries try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" + interval - + ";loginTimeout=" + loginTimeout)) { + + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" + + (new Random().nextInt(defaultTimeout - 1) + 1) + ";loginTimeout=" + loginTimeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - totalTime = System.currentTimeMillis() - timerStart; - - assertTrue( - (e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) - || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() - .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) - || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() - .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), e.getMessage()); } - // if there was a retry then it would take at least 1 interval long, so if < interval means there were no retries - assertTrue(totalTime < TimeUnit.SECONDS.toMillis(interval), - "total time: " + totalTime + " interval: " + TimeUnit.SECONDS.toMillis(interval)); + verifyTimeout(timerEnd - timerStart, loginTimeout); } // Test for detecting Azure server for connection retries @@ -370,6 +289,80 @@ public void testAzureEndpointRetry() { } } + @Test + public void testFailoverInstanceResolution() throws SQLException { + long timerEnd = 0; + long timerStart = System.currentTimeMillis(); + + // Try a non existing server and see if the default timeout is 15 seconds + try (Connection con = PrepUtil + .getConnection("jdbc:sqlserver://" + randomServer + ";databaseName=FailoverDB_abc;failoverPartner=" + + randomServer + "\\foo;user=sa;password=" + RandomUtil.getIdentifier("password"))) { + fail(TestResource.getResource("R_shouldNotConnect")); + } catch (Exception e) { + timerEnd = System.currentTimeMillis(); + + assertTrue((e.getMessage().contains(TestResource.getResource("R_tcpipConnectionToHost"))) + || ((isSqlAzure() || isSqlAzureDW()) + ? e.getMessage().contains( + TestResource.getResource("R_connectTimedOut")) + : false), + e.getMessage()); + } + + verifyTimeout(timerEnd - timerStart, defaultTimeout * 2); + } + + @Test + public void testFOInstanceResolution2() throws SQLException { + long timerEnd = 0; + + long timerStart = System.currentTimeMillis(); + try (Connection con = PrepUtil + .getConnection("jdbc:sqlserver://" + randomServer + "\\fooggg;databaseName=FailoverDB;failoverPartner=" + + randomServer + "\\foo;user=sa;password=" + RandomUtil.getIdentifier("password"))) { + fail(TestResource.getResource("R_shouldNotConnect")); + } catch (Exception e) { + timerEnd = System.currentTimeMillis(); + } + + verifyTimeout(timerEnd - timerStart, defaultTimeout); + } + + /** + * Tests that failover is correctly used after a socket timeout, by confirming total time includes socketTimeout + * for both primary and failover server. + */ + @Test + public void testFailoverInstanceResolutionWithSocketTimeout() { + long timerEnd; + long timerStart = System.currentTimeMillis(); + + try (Connection con = PrepUtil.getConnection("jdbc:sqlserver://" + randomServer + + ";databaseName=FailoverDB;failoverPartner=" + randomServer + "\\foo;user=sa;password=" + + RandomUtil.getIdentifier("password") + ";socketTimeout=" + waitForDelaySeconds)) { + fail(TestResource.getResource("R_shouldNotConnect")); + } catch (Exception e) { + timerEnd = System.currentTimeMillis(); + if (!(e instanceof SQLException)) { + fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage()); + } + + // Driver should correctly attempt to connect to db, experience a socketTimeout, attempt to connect to + // failover, and then have another socketTimeout. So, expected total time is 2 x socketTimeout. + long totalTime = timerEnd - timerStart; + long totalExpectedTime = waitForDelaySeconds * 1000L * 2; // We expect 2 * socketTimeout + assertTrue(totalTime >= totalExpectedTime, TestResource.getResource("R_executionNotLong") + "totalTime: " + + totalTime + " expectedTime: " + totalExpectedTime); + } + } + + private void verifyTimeout(long timeDiff, int timeout) { + // Verify that login timeout does not take longer than seconds. + assertTrue(timeDiff < TimeUnit.SECONDS.toMillis(timeout * 2), + "timeout: " + TimeUnit.SECONDS.toMillis(timeout) + " timediff: " + timeDiff); + } + /** * When query timeout occurs, the connection is still usable. * From fe57bcd17b5a3c5eb5a2ef6f18726051da30fff7 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 22 May 2024 20:54:35 -0700 Subject: [PATCH 4/5] AE config port --- .../sqlserver/jdbc/AlwaysEncrypted/AESetup.java | 7 +++++++ .../sqlserver/jdbc/resiliency/ReflectiveTests.java | 2 +- .../microsoft/sqlserver/testframework/AbstractTest.java | 9 ++++----- .../com/microsoft/sqlserver/testframework/Constants.java | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java index 1a12194bbe..3fbf0d4d81 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -186,6 +186,13 @@ static void setAEConnectionString(String serverName, String url, String protocol if (enclaveServer.length > 1) { System.out.println("Testing enclave: " + enclaveProperties); } + + // remove the password in connection string + // this is necessary as updateDataSource will only use 1st occurrence + String password = getConfiguredProperty("enclaveServerPassword"); + AETestConnectionString = TestUtils.removeProperty(AETestConnectionString, Constants.PASSWORD); + AETestConnectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, Constants.PASSWORD, + password); } else { AETestConnectionString = connectionString + ";sendTimeAsDateTime=false;columnEncryptionSetting=enabled;"; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java index 92d7c852cd..9884902290 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java @@ -88,7 +88,7 @@ public void testDefaultRetry() throws SQLException { // ensure count is not set to something else as this test assumes exactly just 1 retry // this is only true for non-Azure as retry counts gets auto changed for Azure servers - timeoutVariations(m, 6000, Optional.empty()); + timeoutVariations(m, 6500, Optional.empty()); } /* diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index a353aba114..29e5b36ad7 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -279,11 +279,7 @@ protected static void setupConnectionString() { connectionStringNTLM = TestUtils.addOrOverrideProperty(connectionStringNTLM, "user", user); } - if (null != password) { - connectionStringNTLM = TestUtils.addOrOverrideProperty(connectionStringNTLM, "password", password); - } - - if (null != user && null != password) { + if (null != user) { connectionStringNTLM = TestUtils.addOrOverrideProperty(connectionStringNTLM, "authenticationScheme", "NTLM"); connectionStringNTLM = TestUtils.addOrOverrideProperty(connectionStringNTLM, "integratedSecurity", "true"); @@ -356,6 +352,9 @@ protected static ISQLServerDataSource updateDataSource(String connectionString, case Constants.INTEGRATED_SECURITY: ds.setIntegratedSecurity(Boolean.parseBoolean(value)); break; + case Constants.SERVER_NAME: + ds.setServerName(value); + break; case Constants.USER: case Constants.USER_NAME: ds.setUser(value); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java index e4338fc92e..7ffc68b450 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java @@ -140,6 +140,7 @@ private Constants() {} public static final String DATABASE = "DATABASE"; public static final String DATABASE_NAME = "DATABASENAME"; public static final String COLUMN_ENCRYPTION_SETTING = "COLUMNENCRYPTIONSETTING"; + public static final String SERVER_NAME = "SERVERNAME"; public static final String DISABLE_STATEMENT_POOLING = "DISABLESTATEMENTPOOLING"; public static final String STATEMENT_POOLING_CACHE_SIZE = "STATEMENTPOOLINGCACHESIZE"; public static final String AUTHENTICATION = "AUTHENTICATION"; From 2a3b795df1e831e733533eb86399452b46aaa31a Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 22 May 2024 21:17:19 -0700 Subject: [PATCH 5/5] TimeoutTests update --- .../jdbc/connection/TimeoutTest.java | 106 +++++++++++------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index 43742b821e..13064bf7f8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -174,89 +174,111 @@ public void testConnectRetryBadServer() { // Test connect retry for database error @Test public void testConnectRetryServerError() { - long timerEnd = 0; + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + + long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long timeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry - int connectRetryCount = new Random().nextInt(256); - int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" + connectRetryCount - + ";connectRetryInterval=" + connectRetryInterval)) { + + ";loginTimeout=" + timeout + ";connectRetryCount=" + 1 + ";connectRetryInterval=" + interval + + ";transparentNetworkIPResolution=false")) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); - - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + totalTime = System.currentTimeMillis() - timerStart; + + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - // connect + all retries should always be <= loginTimeout - verifyTimeout(timerEnd - timerStart, defaultTimeout); + // 1 retry should be at least 1 interval long but < 2 intervals + assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, + "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), + "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(interval)); } // Test connect retry for database error using Datasource @Test public void testConnectRetryServerErrorDS() { - long timerEnd = 0; - long timerStart = System.currentTimeMillis(); + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry - int connectRetryCount = new Random().nextInt(256); - int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; + long totalTime = 0; + long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long loginTimeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time SQLServerDataSource ds = new SQLServerDataSource(); String connectStr = TestUtils.addOrOverrideProperty(connectionString, "database", - RandomUtil.getIdentifier("database")) + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" - + connectRetryCount + ";connectRetryInterval=" + connectRetryInterval; + RandomUtil.getIdentifier("database")) + ";logintimeout=" + loginTimeout + ";connectRetryCount=1" + + ";connectRetryInterval=" + interval; updateDataSource(connectStr, ds); try (Connection con = PrepUtil.getConnection(connectStr)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; } - // connect + all retries should always be <= loginTimeout - verifyTimeout(timerEnd - timerStart, defaultTimeout); + // 1 retry should be at least 1 interval long but < 2 intervals + assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, + "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), + "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(2 * interval)); } // Test connect retry for database error with loginTimeout @Test public void testConnectRetryTimeout() { - long timerEnd = 0; + long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry int loginTimeout = 2; - // non existent database with very short loginTimeout so there is no time to do all retries + // non existent database with very short loginTimeout so there is no time to do any retry try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" - + (new Random().nextInt(defaultTimeout - 1) + 1) + ";loginTimeout=" + loginTimeout)) { + + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" + interval + + ";loginTimeout=" + loginTimeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - timerEnd = System.currentTimeMillis(); - - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + totalTime = System.currentTimeMillis() - timerStart; + + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); } - verifyTimeout(timerEnd - timerStart, loginTimeout); + // if there was a retry then it would take at least 1 interval long, so if < interval means there were no retries + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(interval), + "total time: " + totalTime + " interval: " + TimeUnit.SECONDS.toMillis(interval)); } // Test for detecting Azure server for connection retries