Repair string-based disconnect filters for MySQL, DB2

The two regexp-based filters in exc_filters.py->_is_db_connection_error()
were both incorrectly formed.  The tests for these filters would pass
because the fixture also set the is_disconnect flag to True, which
normally would be set by SQLAlchemy, and therefore the
_raise_operational_errors_directly_filter() or possibly
the _raise_for_remaining_DBAPIError() filters would catch this,
view the is_disconnect flag as True, and promote to a DBConnectionError.
However, the _is_db_connection_error() filters are intended to
work independently of whether this flag is set; so two new tests
are added which unset the flag, and ensure that the exception messages
as given are caught here.

Change-Id: I37ddd669b89669730ae1ff07c7bc7a6ba5705f67
This commit is contained in:
Mike Bayer 2014-12-05 15:52:45 -05:00 committed by Roman Podoliaka
parent 32e5c6096a
commit 0265aa4e01
2 changed files with 29 additions and 8 deletions
oslo/db/sqlalchemy
tests/sqlalchemy

@ -269,9 +269,8 @@ def _raise_operational_errors_directly_filter(operational_error,
raise operational_error
# For the db2, the error code is -30081 since the db2 is still not ready
@filters("mysql", sqla_exc.OperationalError, r".*\((?:2002|2003|2006|2013)")
@filters("ibm_db_sa", sqla_exc.OperationalError, r".*(?:-30081)")
@filters("mysql", sqla_exc.OperationalError, r".*\(.*(?:2002|2003|2006|2013)")
@filters("ibm_db_sa", sqla_exc.OperationalError, r".*(?:30081)")
def _is_db_connection_error(operational_error, match, engine_name,
is_disconnect):
"""Detect the exception as indicating a recoverable error on connect."""

@ -662,7 +662,9 @@ class IntegrationTest(test_base.DbTestCase):
class TestDBDisconnected(TestsExceptionFilter):
@contextlib.contextmanager
def _fixture(self, dialect_name, exception, num_disconnects):
def _fixture(
self,
dialect_name, exception, num_disconnects, is_disconnect=True):
engine = self.engine
compat.engine_connect(engine, session._connect_ping_listener)
@ -683,12 +685,13 @@ class TestDBDisconnected(TestsExceptionFilter):
fake_do_execute),
mock.patch.object(engine.dialect,
"is_disconnect",
mock.Mock(return_value=True))
mock.Mock(return_value=is_disconnect))
):
yield
def _test_ping_listener_disconnected(self, dialect_name, exc_obj):
with self._fixture(dialect_name, exc_obj, 1):
def _test_ping_listener_disconnected(
self, dialect_name, exc_obj, is_disconnect=True):
with self._fixture(dialect_name, exc_obj, 1, is_disconnect):
conn = self.engine.connect()
with conn.begin():
self.assertEqual(conn.scalar(sqla.select([1])), 1)
@ -696,7 +699,7 @@ class TestDBDisconnected(TestsExceptionFilter):
self.assertFalse(conn.invalidated)
self.assertTrue(conn.in_transaction())
with self._fixture(dialect_name, exc_obj, 2):
with self._fixture(dialect_name, exc_obj, 2, is_disconnect):
self.assertRaises(
exception.DBConnectionError,
self.engine.connect
@ -713,6 +716,17 @@ class TestDBDisconnected(TestsExceptionFilter):
self.OperationalError('%d MySQL server has gone away' % code)
)
def test_mysql_ping_listener_disconnected_regex_only(self):
# intentionally set the is_disconnect flag to False
# in the "sqlalchemy" layer to make sure the regexp
# on _is_db_connection_error is catching
for code in [2002, 2003, 2006, 2013]:
self._test_ping_listener_disconnected(
"mysql",
self.OperationalError('%d MySQL server has gone away' % code),
is_disconnect=False
)
def test_db2_ping_listener_disconnected(self):
self._test_ping_listener_disconnected(
"ibm_db_sa",
@ -720,6 +734,14 @@ class TestDBDisconnected(TestsExceptionFilter):
'SQL30081N: DB2 Server connection is no longer active')
)
def test_db2_ping_listener_disconnected_regex_only(self):
self._test_ping_listener_disconnected(
"ibm_db_sa",
self.OperationalError(
'SQL30081N: DB2 Server connection is no longer active'),
is_disconnect=False
)
class TestDBConnectRetry(TestsExceptionFilter):