/* * Copyright 2010 Proofpoint, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.airlift.dbpool; import io.airlift.dbpool.MockConnectionPoolDataSource.MockConnection; import io.airlift.units.Duration; import org.testng.annotations.Test; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import static io.airlift.testing.Assertions.assertGreaterThan; import static io.airlift.testing.Assertions.assertInstanceOf; import static io.airlift.units.Duration.nanosSince; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; public class ManagedDataSourceTest { @Test public void testBasic() throws Exception { ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); assertEquals(dataSource.getConnectionsActive(), 0); assertEquals(dataSource.getStats().getCheckout().getAllTime().getCount(), 0.0); assertEquals(dataSource.getStats().getCreate().getAllTime().getCount(), 0.0); assertEquals(dataSource.getStats().getHeld().getAllTime().getCount(), 0.0); assertEquals(dataSource.getStats().getConnectionErrorCount(), 0); Connection connection = dataSource.getConnection(); assertNotNull(connection); assertTrue(connection instanceof MockConnection); assertEquals(dataSource.getConnectionsActive(), 1); assertEquals(dataSource.getStats().getCheckout().getAllTime().getCount(), 1.0); assertEquals(dataSource.getStats().getCreate().getAllTime().getCount(), 1.0); assertEquals(dataSource.getStats().getHeld().getAllTime().getCount(), 0.0); assertEquals(dataSource.getStats().getConnectionErrorCount(), 0); connection.close(); assertEquals(dataSource.getConnectionsActive(), 0); assertEquals(dataSource.getStats().getCheckout().getAllTime().getCount(), 1.0); assertEquals(dataSource.getStats().getCreate().getAllTime().getCount(), 1.0); assertEquals(dataSource.getStats().getHeld().getAllTime().getCount(), 1.0); assertEquals(dataSource.getStats().getConnectionErrorCount(), 0); } @Test public void testMaxConnectionWaitMillis() throws Exception { // datasource server to 1 connection and only wait 1 ms for a connection ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); assertEquals(dataSource.getMaxConnectionWaitMillis(), 10); // checkout a connection Connection connection = dataSource.getConnection(); assertEquals(dataSource.getConnectionsActive(), 1); // try to get another one which will timeout long start = System.nanoTime(); try { dataSource.getConnection(); fail("Expected SQLException from timeout"); } catch (SQLException expected) { } Duration duration = nanosSince(start); assertGreaterThan(duration, new Duration(10, MILLISECONDS)); assertEquals(dataSource.getConnectionsActive(), 1); // try with a different timeout dataSource.setMaxConnectionWaitMillis(new Duration(50, MILLISECONDS)); assertEquals(dataSource.getMaxConnectionWaitMillis(), 50); start = System.nanoTime(); try { dataSource.getConnection(); fail("Expected SQLException from timeout"); } catch (SQLException expected) { } duration = nanosSince(start); assertGreaterThan(duration, new Duration(50, MILLISECONDS)); assertEquals(dataSource.getConnectionsActive(), 1); // verify proper handling of illegal values try { dataSource.setMaxConnectionWaitMillis(null); fail("NullPointerException IllegalArgumentException"); } catch (NullPointerException e) { } assertEquals(dataSource.getMaxConnectionWaitMillis(), 50); // verify proper handling of illegal values try { dataSource.setMaxConnectionWaitMillis(new Duration(0, MILLISECONDS)); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { } assertEquals(dataSource.getMaxConnectionWaitMillis(), 50); connection.close(); assertEquals(dataSource.getConnectionsActive(), 0); } /** * Verify adjustment of connection count limits. * <ol> * <li>Test initial limit</li> * <li>Test limit increase</li> * <li>Test decrease below current checkout</li> * <li>Verify handling of illegal values</li> * </ol> */ @Test public void testMaxConnections() throws Exception { // datasource server to 1 connection and only wait 1 ms for a connection ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(1, MILLISECONDS)); assertEquals(dataSource.getMaxConnections(), 1); // checkout a connection Queue<Connection> connections = new LinkedList<Connection>(); connections.add(dataSource.getConnection()); assertEquals(dataSource.getConnectionsActive(), 1); // verify that we can't another connection try { dataSource.getConnection(); fail("Expected SQLException from timeout"); } catch (SQLException expected) { } assertEquals(dataSource.getConnectionsActive(), 1); // increase the max connection count to 3 and acquire two extra ones dataSource.setMaxConnections(3); assertEquals(dataSource.getMaxConnections(), 3); connections.add(dataSource.getConnection()); connections.add(dataSource.getConnection()); // verify that we can't get another connection try { dataSource.getConnection(); fail("Expected SQLException from timeout"); } catch (SQLException expected) { } assertEquals(dataSource.getConnectionsActive(), 3); // reduce the max connection count to 2 dataSource.setMaxConnections(2); assertEquals(dataSource.getMaxConnections(), 2); assertEquals(dataSource.getConnectionsActive(), 3); // first verify that we still can't get more connections try { dataSource.getConnection(); fail("Expected SQLException from timeout"); } catch (SQLException expected) { } assertEquals(dataSource.getConnectionsActive(), 3); // now release one and verify we still can't get another one connections.remove().close(); assertEquals(dataSource.getConnectionsActive(), 2); try { dataSource.getConnection(); fail("Expected SQLException from timeout"); } catch (SQLException expected) { } assertEquals(dataSource.getConnectionsActive(), 2); // finally close another one and verify we can reopen it connections.remove().close(); connections.add(dataSource.getConnection()); assertEquals(dataSource.getConnectionsActive(), 2); // verify proper handling of illegal values try { dataSource.setMaxConnectionWaitMillis(null); fail("Expected NullPointerException"); } catch (NullPointerException e) { } assertEquals(dataSource.getMaxConnections(), 2); try { dataSource.setMaxConnectionWaitMillis(new Duration(0, MILLISECONDS)); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { } assertEquals(dataSource.getMaxConnections(), 2); // clean up for (Connection connection : connections) { connection.close(); } assertEquals(dataSource.getConnectionsActive(), 0); } @Test public void testAcquirePermitInterrupted() throws Exception { // datasource server to 1 connection and only wait 1 ms for a connection final ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(5000, MILLISECONDS)); assertEquals(dataSource.getMaxConnectionWaitMillis(), 5000); // checkout a connection Connection connection = dataSource.getConnection(); assertEquals(dataSource.getConnectionsActive(), 1); // checkout in another thread which we are going to interrupt final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch endLatch = new CountDownLatch(1); final AtomicBoolean wasInterrupted = new AtomicBoolean(); final AtomicReference<SQLException> exception = new AtomicReference<SQLException>(); Thread createThread = new Thread() { @Override public void run() { startLatch.countDown(); try { dataSource.getConnection(); } catch (SQLException e) { exception.set(e); } finally { wasInterrupted.set(isInterrupted()); endLatch.countDown(); } } }; createThread.start(); // wait for thread to actually start startLatch.await(); // interrupt the createThread createThread.interrupt(); // wait for the thread to end endLatch.await(); // verify that the thread is still in the interrupted state assertTrue(wasInterrupted.get(), "createThread.isInterrupted()"); SQLException sqlException = exception.get(); assertNotNull(sqlException); assertInstanceOf(sqlException.getCause(), InterruptedException.class); connection.close(); assertEquals(dataSource.getConnectionsActive(), 0); } @Test public void testIdempotentClose() throws Exception { ManagedDataSource dataSource = new MockManagedDataSource(10, new Duration(10, MILLISECONDS)); List<MockConnection> connections = new ArrayList<MockConnection>(); for (int i = 0; i < 10; i++) { MockConnection connection = (MockConnection) dataSource.getConnection(); assertNotNull(connection); connections.add(connection); } assertEquals(dataSource.getConnectionsActive(), 10); // close connections in a random order Collections.shuffle(connections); int closedCount = 0; for (MockConnection connection : connections) { closedCount++; for (int j = 0; j < 7; j++) { connection.close(); assertEquals(dataSource.getConnectionsActive(), 10 - closedCount); } } } @Test public void testConnectionException() throws Exception { ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); MockConnection connection = (MockConnection) dataSource.getConnection(); assertNotNull(connection); assertEquals(dataSource.getConnectionsActive(), 1); connection.errorOccurred(); assertEquals(dataSource.getConnectionsActive(), 0); } @Test public void testCreateException() { MockConnectionPoolDataSource mockConnectionPoolDataSource = new MockConnectionPoolDataSource(); ManagedDataSource dataSource = new MockManagedDataSource(mockConnectionPoolDataSource, 1, new Duration(10, MILLISECONDS)); mockConnectionPoolDataSource.createException = new SQLException(); assertEquals(dataSource.getConnectionsActive(), 0); try { dataSource.getConnection(); fail("expected SQLException"); } catch (SQLException e) { assertSame(e, mockConnectionPoolDataSource.createException); } assertEquals(dataSource.getConnectionsActive(), 0); } @Test public void testCloseException() throws SQLException { MockConnectionPoolDataSource mockConnectionPoolDataSource = new MockConnectionPoolDataSource(); ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); mockConnectionPoolDataSource.closeException = new SQLException(); assertEquals(dataSource.getConnectionsActive(), 0); MockConnection connection = (MockConnection) dataSource.getConnection(); assertNotNull(connection); assertEquals(dataSource.getConnectionsActive(), 1); connection.close(); assertEquals(dataSource.getConnectionsActive(), 0); connection = (MockConnection) dataSource.getConnection(); assertNotNull(connection); assertEquals(dataSource.getConnectionsActive(), 1); connection.close(); assertEquals(dataSource.getConnectionsActive(), 0); connection.errorOccurred(); assertEquals(dataSource.getConnectionsActive(), 0); } @Test public void testIdempotentCloseAndException() throws SQLException { MockConnectionPoolDataSource mockConnectionPoolDataSource = new MockConnectionPoolDataSource(); ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); mockConnectionPoolDataSource.closeException = new SQLException(); assertEquals(dataSource.getConnectionsActive(), 0); MockConnection connection = (MockConnection) dataSource.getConnection(); assertNotNull(connection); assertEquals(dataSource.getConnectionsActive(), 1); for (int i = 0; i < 10; i++) { connection.close(); assertEquals(dataSource.getConnectionsActive(), 0); connection.errorOccurred(); assertEquals(dataSource.getConnectionsActive(), 0); } } @Test public void testLogWriterIsNeverSet() throws SQLException { MockConnectionPoolDataSource mockConnectionPoolDataSource = new MockConnectionPoolDataSource(); PrintWriter expectedLogWriter = new PrintWriter(new StringWriter()); mockConnectionPoolDataSource.logWriter = expectedLogWriter; ManagedDataSource dataSource = new MockManagedDataSource(mockConnectionPoolDataSource, 1, new Duration(10, MILLISECONDS)); // data source log writer should start with null assertNull(dataSource.getLogWriter()); // set the writer PrintWriter newWriter = new PrintWriter(new StringWriter()); dataSource.setLogWriter(newWriter); // data source log writer should still be null assertNull(dataSource.getLogWriter()); // core data source should remain unaffected assertSame(mockConnectionPoolDataSource.logWriter, expectedLogWriter); } @Test public void testLoginTimeoutIsNeverSet() throws SQLException { MockConnectionPoolDataSource mockConnectionPoolDataSource = new MockConnectionPoolDataSource(); mockConnectionPoolDataSource.loginTimeout = 42; ManagedDataSource dataSource = new MockManagedDataSource(mockConnectionPoolDataSource, 1, new Duration(5, SECONDS)); // login timeout should always be max connection wait assertEquals(dataSource.getLoginTimeout(), 5); // set to a new value int newTimeout = 12345; dataSource.setLoginTimeout(newTimeout); // data source timeout should still be max connection wait assertEquals(dataSource.getLoginTimeout(), 5); // core data source should remain unaffected assertEquals(mockConnectionPoolDataSource.loginTimeout, 42); } @Test public void testWrapper() throws SQLException { ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); assertTrue(dataSource.isWrapperFor(ManagedDataSource.class)); assertTrue(dataSource.isWrapperFor(DataSource.class)); assertTrue(dataSource.isWrapperFor(Object.class)); assertFalse(dataSource.isWrapperFor(ConnectionPoolDataSource.class)); assertFalse(dataSource.isWrapperFor(Integer.class)); try { dataSource.isWrapperFor(null); fail("Expected SQLException"); } catch (SQLException expected) { } try { dataSource.unwrap(null); fail("Expected SQLException"); } catch (SQLException expected) { } assertSame(dataSource.unwrap(ManagedDataSource.class), dataSource); assertSame(dataSource.unwrap(DataSource.class), dataSource); assertSame(dataSource.unwrap(Object.class), dataSource); try { dataSource.unwrap(ConnectionPoolDataSource.class); fail("Expected SQLException"); } catch (SQLException expected) { } try { dataSource.unwrap(Integer.class); fail("Expected SQLException"); } catch (SQLException expected) { } } @Test public void testGetConnectionUsernamePassword() throws SQLException { ManagedDataSource dataSource = new MockManagedDataSource(1, new Duration(10, MILLISECONDS)); try { dataSource.getConnection("username", "password"); fail("Expected SQLException"); } catch (UnsupportedOperationException expected) { } } }