summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-06-28 00:37:05 +0000
committerGerrit Code Review <review@openstack.org>2014-06-28 00:37:05 +0000
commit5e2e96aca049cde2717441a5a4ea8027d9eec52a (patch)
tree1acebc9c2d0657c7f6ec6516b6fda69ec91a4662
parente734e2befb7c2e796c24106f3236d010a62e6b9b (diff)
parent3a6a5b01a72e0e20787f3d6a47f6f0c0262dbbeb (diff)
downloadoslo-db-5e2e96aca049cde2717441a5a4ea8027d9eec52a.tar.gz
Merge "Add _wrap_db_error support for postgresql"
-rw-r--r--oslo/db/sqlalchemy/session.py22
-rw-r--r--tests/sqlalchemy/test_sqlalchemy.py110
2 files changed, 131 insertions, 1 deletions
diff --git a/oslo/db/sqlalchemy/session.py b/oslo/db/sqlalchemy/session.py
index 31d1fd0..94c0911 100644
--- a/oslo/db/sqlalchemy/session.py
+++ b/oslo/db/sqlalchemy/session.py
@@ -403,8 +403,12 @@ def _raise_if_duplicate_entry_error(integrity_error, engine_name):
# mysql:
# (OperationalError) (1213, 'Deadlock found when trying to get lock; try '
# 'restarting transaction') <query_str> <query_args>
+#
+# postgresql:
+# (TransactionRollbackError) deadlock detected <deadlock_details>
_DEADLOCK_RE_DB = {
- "mysql": re.compile(r"^.*\(1213, 'Deadlock.*")
+ "mysql": re.compile(r"^.*\(1213, 'Deadlock.*"),
+ "postgresql": re.compile(r"^.*deadlock detected.*")
}
@@ -457,6 +461,22 @@ def _wrap_db_error(f):
# unique constraint, from error message.
_raise_if_duplicate_entry_error(e, self.bind.dialect.name)
raise exception.DBError(e)
+ except sqla_exc.DBAPIError as e:
+ # NOTE(wingwj): This branch is used to catch deadlock exception
+ # under postgresql. The original exception thrown from postgresql
+ # is TransactionRollbackError, it's not included in the sqlalchemy.
+ # Moreover, DBAPIError is the base class of OperationalError
+ # and IntegrityError, so we catch it on the end of the process.
+ #
+ # The issue has also submitted to sqlalchemy on
+ # https://bitbucket.org/zzzeek/sqlalchemy/issue/3075/
+ # support-non-standard-dbapi-exception
+ # (FIXME) This branch should be refactored after the patch
+ # merged in & our requirement of sqlalchemy updated
+ _raise_if_db_connection_lost(e, self.bind)
+ _raise_if_deadlock_error(e, self.bind.dialect.name)
+ LOG.exception(_LE('DBAPIError exception wrapped from %s') % e)
+ raise exception.DBError(e)
except Exception as e:
LOG.exception(_LE('DB exception wrapped.'))
raise exception.DBError(e)
diff --git a/tests/sqlalchemy/test_sqlalchemy.py b/tests/sqlalchemy/test_sqlalchemy.py
index 2036e13..2c8017f 100644
--- a/tests/sqlalchemy/test_sqlalchemy.py
+++ b/tests/sqlalchemy/test_sqlalchemy.py
@@ -260,6 +260,116 @@ class TestDBDisconnected(oslo_test.BaseTestCase):
self._test_ping_listener_disconnected(connection)
+class TestDBDeadlocked(test_base.DbTestCase):
+
+ def test_mysql_deadlock(self):
+ statement = ('SELECT quota_usages.created_at AS '
+ 'quota_usages_created_at, quota_usages.updated_at AS '
+ 'quota_usages_updated_at, quota_usages.deleted_at AS '
+ 'quota_usages_deleted_at, quota_usages.deleted AS '
+ 'quota_usages_deleted, quota_usages.id AS '
+ 'quota_usages_id, quota_usages.project_id AS '
+ 'quota_usages_project_id, quota_usages.user_id AS '
+ 'quota_usages_user_id, quota_usages.resource AS '
+ 'quota_usages_resource, quota_usages.in_use AS '
+ 'quota_usages_in_use, quota_usages.reserved AS '
+ 'quota_usages_reserved, quota_usages.until_refresh AS '
+ 'quota_usages_until_refresh \nFROM quota_usages \n'
+ 'WHERE quota_usages.deleted = %(deleted_1)s AND '
+ 'quota_usages.project_id = %(project_id_1)s AND '
+ '(quota_usages.user_id = %(user_id_1)s OR '
+ 'quota_usages.user_id IS NULL) FOR UPDATE')
+ params = {'project_id_1': u'8891d4478bbf48ad992f050cdf55e9b5',
+ 'user_id_1': u'22b6a9fe91b349639ce39146274a25ba',
+ 'deleted_1': 0}
+ orig = sqla_exc.SQLAlchemyError("(1213, 'Deadlock found when trying "
+ "to get lock; try restarting "
+ "transaction')")
+ deadlock_error = sqla_exc.OperationalError(statement, params, orig)
+ self.assertRaises(db_exc.DBDeadlock,
+ session._raise_if_deadlock_error,
+ deadlock_error,
+ 'mysql')
+
+ def test_postgresql_deadlock(self):
+ statement = ('SELECT quota_usages.created_at AS '
+ 'quota_usages_created_at, quota_usages.updated_at AS '
+ 'quota_usages_updated_at, quota_usages.deleted_at AS '
+ 'quota_usages_deleted_at, quota_usages.deleted AS '
+ 'quota_usages_deleted, quota_usages.id AS '
+ 'quota_usages_id, quota_usages.project_id AS '
+ 'quota_usages_project_id, quota_usages.user_id AS '
+ 'quota_usages_user_id, quota_usages.resource AS '
+ 'quota_usages_resource, quota_usages.in_use AS '
+ 'quota_usages_in_use, quota_usages.reserved AS '
+ 'quota_usages_reserved, quota_usages.until_refresh AS '
+ 'quota_usages_until_refresh \nFROM quota_usages \n'
+ 'WHERE quota_usages.deleted = %(deleted_1)s AND '
+ 'quota_usages.project_id = %(project_id_1)s AND '
+ '(quota_usages.user_id = %(user_id_1)s OR '
+ 'quota_usages.user_id IS NULL) FOR UPDATE')
+ params = {'project_id_1': u'8891d4478bbf48ad992f050cdf55e9b5',
+ 'user_id_1': u'22b6a9fe91b349639ce39146274a25ba',
+ 'deleted_1': 0}
+ orig = sqla_exc.SQLAlchemyError("(TransactionRollbackError) "
+ "deadlock detected")
+ deadlock_error = sqla_exc.DBAPIError(statement, params, orig)
+ self.assertRaises(db_exc.DBDeadlock,
+ session._raise_if_deadlock_error,
+ deadlock_error,
+ 'postgresql')
+
+ def test_mysql_fail_without_deadlock(self):
+ statement = ('SELECT quota_usages.created_at AS '
+ 'quota_usages_created_at, quota_usages.updated_at AS '
+ 'quota_usages_updated_at, quota_usages.deleted_at AS '
+ 'quota_usages_deleted_at, quota_usages.deleted AS '
+ 'quota_usages_deleted, quota_usages.id AS '
+ 'quota_usages_id, quota_usages.project_id AS '
+ 'quota_usages_project_id, quota_usages.user_id AS '
+ 'quota_usages_user_id, quota_usages.resource AS '
+ 'quota_usages_resource, quota_usages.in_use AS '
+ 'quota_usages_in_use, quota_usages.reserved AS '
+ 'quota_usages_reserved, quota_usages.until_refresh AS '
+ 'quota_usages_until_refresh \nFROM quota_usages \n'
+ 'WHERE quota_usages.deleted = %(deleted_1)s AND '
+ 'quota_usages.project_id = %(project_id_1)s AND '
+ '(quota_usages.user_id = %(user_id_1)s OR '
+ 'quota_usages.user_id IS NULL) FOR UPDATE')
+ params = {'project_id_1': u'8891d4478bbf48ad992f050cdf55e9b5',
+ 'user_id_1': u'22b6a9fe91b349639ce39146274a25ba',
+ 'deleted_1': 0}
+ orig = sqla_exc.SQLAlchemyError('Other error occurred.')
+ dbapi_error = sqla_exc.DBAPIError(statement, params, orig)
+ self.assertIsNone(session._raise_if_deadlock_error(dbapi_error,
+ 'mysql'))
+
+ def test_postgresql_fail_without_deadlock(self):
+ statement = ('SELECT quota_usages.created_at AS '
+ 'quota_usages_created_at, quota_usages.updated_at AS '
+ 'quota_usages_updated_at, quota_usages.deleted_at AS '
+ 'quota_usages_deleted_at, quota_usages.deleted AS '
+ 'quota_usages_deleted, quota_usages.id AS '
+ 'quota_usages_id, quota_usages.project_id AS '
+ 'quota_usages_project_id, quota_usages.user_id AS '
+ 'quota_usages_user_id, quota_usages.resource AS '
+ 'quota_usages_resource, quota_usages.in_use AS '
+ 'quota_usages_in_use, quota_usages.reserved AS '
+ 'quota_usages_reserved, quota_usages.until_refresh AS '
+ 'quota_usages_until_refresh \nFROM quota_usages \n'
+ 'WHERE quota_usages.deleted = %(deleted_1)s AND '
+ 'quota_usages.project_id = %(project_id_1)s AND '
+ '(quota_usages.user_id = %(user_id_1)s OR '
+ 'quota_usages.user_id IS NULL) FOR UPDATE')
+ params = {'project_id_1': u'8891d4478bbf48ad992f050cdf55e9b5',
+ 'user_id_1': u'22b6a9fe91b349639ce39146274a25ba',
+ 'deleted_1': 0}
+ orig = sqla_exc.SQLAlchemyError('Other error occurred.')
+ dbapi_error = sqla_exc.DBAPIError(statement, params, orig)
+ self.assertIsNone(session._raise_if_deadlock_error(dbapi_error,
+ 'postgresql'))
+
+
class MySQLModeTestCase(test_base.MySQLOpportunisticTestCase):
def __init__(self, *args, **kwargs):