diff --git a/framework/db/pom.xml b/framework/db/pom.xml index e28628e5e9f..b24b7420a37 100644 --- a/framework/db/pom.xml +++ b/framework/db/pom.xml @@ -35,6 +35,10 @@ commons-dbcp commons-dbcp + + commons-io + commons-io + commons-pool commons-pool diff --git a/framework/db/src/com/cloud/utils/db/DbUtil.java b/framework/db/src/com/cloud/utils/db/DbUtil.java index 25700933080..2a613d2225a 100755 --- a/framework/db/src/com/cloud/utils/db/DbUtil.java +++ b/framework/db/src/com/cloud/utils/db/DbUtil.java @@ -208,13 +208,14 @@ public class DbUtil { } PreparedStatement pstmt = null; + ResultSet rs = null; try { pstmt = conn.prepareStatement("SELECT COALESCE(GET_LOCK(?, ?),0)"); pstmt.setString(1, name); pstmt.setInt(2, timeoutSeconds); - ResultSet rs = pstmt.executeQuery(); + rs = pstmt.executeQuery(); if (rs != null && rs.first()) { if(rs.getInt(1) > 0) { return true; @@ -228,13 +229,8 @@ public class DbUtil { } catch (Throwable e) { s_logger.error("GET_LOCK() throws exception ", e); } finally { - if (pstmt != null) { - try { - pstmt.close(); - } catch (Throwable e) { - s_logger.error("What the heck? ", e); - } - } + closeStatement(pstmt); + closeResultSet(rs); } removeConnectionForGlobalLocks(name); @@ -259,10 +255,11 @@ public class DbUtil { } PreparedStatement pstmt = null; + ResultSet rs = null; try { pstmt = conn.prepareStatement("SELECT COALESCE(RELEASE_LOCK(?), 0)"); pstmt.setString(1, name); - ResultSet rs = pstmt.executeQuery(); + rs = pstmt.executeQuery(); if(rs != null && rs.first()) return rs.getInt(1) > 0; s_logger.error("RELEASE_LOCK() returns unexpected result : " + rs.getInt(1)); @@ -271,13 +268,9 @@ public class DbUtil { } catch (Throwable e) { s_logger.error("RELEASE_LOCK() throws exception ", e); } finally { - try { - if (pstmt != null) { - pstmt.close(); - } - conn.close(); - } catch(SQLException e) { - } + closeResultSet(rs); + closeStatement(pstmt); + closeConnection(conn); } return false; } @@ -305,7 +298,7 @@ public class DbUtil { resultSet.close(); } - } catch (Exception e) { + } catch (SQLException e) { s_logger.warn("Ignored exception while closing result set.",e); } @@ -319,7 +312,7 @@ public class DbUtil { statement.close(); } - } catch (Exception e) { + } catch (SQLException e) { s_logger.warn("Ignored exception while closing statement.",e); } @@ -333,7 +326,7 @@ public class DbUtil { connection.close(); } - } catch (Exception e) { + } catch (SQLException e) { s_logger.warn("Ignored exception while close connection.",e); } diff --git a/framework/db/test/com/cloud/utils/DbUtilTest.java b/framework/db/test/com/cloud/utils/DbUtilTest.java index e9a72870f5f..4839c23c867 100644 --- a/framework/db/test/com/cloud/utils/DbUtilTest.java +++ b/framework/db/test/com/cloud/utils/DbUtilTest.java @@ -16,16 +16,58 @@ // under the License. package com.cloud.utils; -import javax.persistence.Column; +import java.io.IOException; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Table; +import javax.sql.DataSource; + +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; import com.cloud.utils.db.DbUtil; +import com.cloud.utils.db.TransactionLegacy; +@RunWith(MockitoJUnitRunner.class) public class DbUtilTest { + @Mock + Connection connection; + + @Mock + PreparedStatement preparedStatement; + + @Mock + Statement statement; + + @Mock + ResultSet resultSet; + + @Mock + DataSource dataSource; + + DataSource backup; + + Map connectionMapBackup = null; + + Map connectionMap = null; + + @Table(name = "test_table") static class Testbean { String noAnnotation; @Column() @@ -78,4 +120,152 @@ public class DbUtilTest { .getDeclaredField("instanceField"))); } + class Bar { + + } + + @Test + public void getTableName() { + Assert.assertEquals("test_table", DbUtil.getTableName(Testbean.class)); + Assert.assertEquals("Bar", DbUtil.getTableName(Bar.class)); + } + + @SuppressWarnings("unchecked") + @Before + public void setup() throws SecurityException, NoSuchFieldException, + IllegalArgumentException, IllegalAccessException { + Field globalLocks = DbUtil.class + .getDeclaredField("s_connectionForGlobalLocks"); + globalLocks.setAccessible(true); + connectionMapBackup = (Map) globalLocks.get(null); + connectionMap = new HashMap(); + globalLocks.set(null, connectionMap); + + Field dsField = TransactionLegacy.class.getDeclaredField("s_ds"); + dsField.setAccessible(true); + backup = (DataSource) dsField.get(null); + dsField.set(null, dataSource); + } + + @After + public void cleanup() throws SecurityException, NoSuchFieldException, + IllegalArgumentException, IllegalAccessException { + Field globalLocks = DbUtil.class + .getDeclaredField("s_connectionForGlobalLocks"); + globalLocks.setAccessible(true); + globalLocks.set(null, connectionMapBackup); + + Field dsField = TransactionLegacy.class.getDeclaredField("s_ds"); + dsField.setAccessible(true); + dsField.set(null, backup); + } + + @Test + public void getGlobalLock() throws SQLException { + Mockito.when(dataSource.getConnection()).thenReturn(connection); + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(preparedStatement); + Mockito.when(preparedStatement.executeQuery()).thenReturn(resultSet); + Mockito.when(resultSet.first()).thenReturn(true); + Mockito.when(resultSet.getInt(1)).thenReturn(1); + Assert.assertTrue(DbUtil.getGlobalLock("TEST", 600)); + + Mockito.verify(connection).prepareStatement(Mockito.anyString()); + Mockito.verify(preparedStatement).close(); + Mockito.verify(resultSet).close(); + } + + @Test + public void getGlobalLockTimeout() throws SQLException { + Mockito.when(dataSource.getConnection()).thenReturn(connection); + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(preparedStatement); + Mockito.when(preparedStatement.executeQuery()).thenReturn(resultSet); + Mockito.when(resultSet.first()).thenReturn(true); + Mockito.when(resultSet.getInt(1)).thenReturn(0); + Assert.assertFalse(DbUtil.getGlobalLock("TEST", 600)); + + Mockito.verify(connection).prepareStatement(Mockito.anyString()); + Mockito.verify(preparedStatement).close(); + Mockito.verify(resultSet).close(); + Mockito.verify(connection).close(); + + // if any error happens, the connection map must be cleared + Assert.assertTrue(connectionMap.isEmpty()); + } + + @Test + public void closeNull() { + DbUtil.closeStatement((Statement) null); + DbUtil.closeConnection((Connection) null); + DbUtil.closeResultSet((ResultSet) null); + // no exception should be thrown + } + + @Test + public void closeConnection() throws IOException, SQLException { + DbUtil.closeConnection(connection); + Mockito.verify(connection).close(); + } + + @Test + public void closeConnectionFail() throws IOException, SQLException { + Mockito.doThrow(new SQLException("it is all right")).when(connection) + .close(); + DbUtil.closeConnection(connection); + Mockito.verify(connection).close(); + } + + @Test + public void closeStatement() throws IOException, SQLException { + DbUtil.closeStatement(statement); + Mockito.verify(statement).close(); + } + + @Test + public void closeStatementFail() throws IOException, SQLException { + Mockito.doThrow(new SQLException("it is all right")).when(statement) + .close(); + DbUtil.closeStatement(statement); + Mockito.verify(statement).close(); + } + + @Test + public void closeResultSet() throws IOException, SQLException { + DbUtil.closeResultSet(resultSet); + Mockito.verify(resultSet).close(); + } + + @Test + public void closeResultSetFail() throws IOException, SQLException { + Mockito.doThrow(new SQLException("it is all right")).when(resultSet) + .close(); + DbUtil.closeResultSet(resultSet); + Mockito.verify(resultSet).close(); + } + + @Test + @Ignore + //can not be performed since assertion embedded in this branch of execution + public void releaseGlobalLockNotexisting() throws SQLException { + Assert.assertFalse(DbUtil.releaseGlobalLock("notexisting")); + Mockito.verify(dataSource, Mockito.never()).getConnection(); + } + + @Test + public void releaseGlobalLock() throws SQLException { + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(preparedStatement); + Mockito.when(preparedStatement.executeQuery()).thenReturn(resultSet); + Mockito.when(resultSet.first()).thenReturn(true); + Mockito.when(resultSet.getInt(1)).thenReturn(1); + connectionMap.put("testLock", connection); + Assert.assertTrue(DbUtil.releaseGlobalLock("testLock")); + + Mockito.verify(resultSet).close(); + Mockito.verify(preparedStatement).close(); + Mockito.verify(connection).close(); + Assert.assertFalse(connectionMap.containsKey("testLock")); + } + }