diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-06-28 00:37:05 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-06-28 00:37:05 +0000 |
commit | 5e2e96aca049cde2717441a5a4ea8027d9eec52a (patch) | |
tree | 1acebc9c2d0657c7f6ec6516b6fda69ec91a4662 | |
parent | e734e2befb7c2e796c24106f3236d010a62e6b9b (diff) | |
parent | 3a6a5b01a72e0e20787f3d6a47f6f0c0262dbbeb (diff) | |
download | oslo-db-5e2e96aca049cde2717441a5a4ea8027d9eec52a.tar.gz |
Merge "Add _wrap_db_error support for postgresql"
-rw-r--r-- | oslo/db/sqlalchemy/session.py | 22 | ||||
-rw-r--r-- | tests/sqlalchemy/test_sqlalchemy.py | 110 |
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): |