diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-11-14 04:11:06 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-11-14 04:11:06 +0000 |
commit | 3b6c0417de558225fce2d20ef88054eaeeff3011 (patch) | |
tree | 254f4ee42bfcdea2bd013714221fac692ba994d1 | |
parent | 7dabb52043d27bedc4f55f1f4eec20bc4b8a93a9 (diff) | |
parent | ca04d927d9623e2967171a443260cac34b2f962e (diff) | |
download | oslo-incubator-3b6c0417de558225fce2d20ef88054eaeeff3011.tar.gz |
Merge "Add _wrap_db_error() support to Session.commit()" into stable/icehouse
-rw-r--r-- | openstack/common/db/sqlalchemy/session.py | 8 | ||||
-rw-r--r-- | tests/unit/db/sqlalchemy/test_sqlalchemy.py | 48 |
2 files changed, 56 insertions, 0 deletions
diff --git a/openstack/common/db/sqlalchemy/session.py b/openstack/common/db/sqlalchemy/session.py index 7ec799c3..2f81b690 100644 --- a/openstack/common/db/sqlalchemy/session.py +++ b/openstack/common/db/sqlalchemy/session.py @@ -456,6 +456,10 @@ 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 exception.DBError: + # note(zzzeek) - if _wrap_db_error is applied to nested functions, + # ensure an existing DBError is propagated outwards + raise except Exception as e: LOG.exception(_LE('DB exception wrapped.')) raise exception.DBError(e) @@ -708,6 +712,10 @@ class Session(sqlalchemy.orm.session.Session): def execute(self, *args, **kwargs): return super(Session, self).execute(*args, **kwargs) + @_wrap_db_error + def commit(self, *args, **kwargs): + return super(Session, self).commit(*args, **kwargs) + def get_maker(engine, autocommit=True, expire_on_commit=False): """Return a SQLAlchemy sessionmaker using the given engine.""" diff --git a/tests/unit/db/sqlalchemy/test_sqlalchemy.py b/tests/unit/db/sqlalchemy/test_sqlalchemy.py index 49a11625..54840647 100644 --- a/tests/unit/db/sqlalchemy/test_sqlalchemy.py +++ b/tests/unit/db/sqlalchemy/test_sqlalchemy.py @@ -75,6 +75,54 @@ class SessionErrorWrapperTestCase(test_base.DbTestCase): tbl2.update({'foo': 10}) self.assertRaises(db_exc.DBDuplicateEntry, tbl2.save, _session) + def test_nested_session_wrappers(self): + _session = self.sessionmaker() + + tbl = TmpTable() + tbl.update({'foo': 10}) + tbl.save(_session) + + tbl2 = TmpTable() + tbl2.update({'foo': 10}) + _session.begin() + _session.add(tbl2) + + # commit will call upon flush which raises the exception; + # the wrap around commit needs to not interfere with the + # already wrapped exception coming from flush + self.assertRaises(db_exc.DBDuplicateEntry, _session.commit) + + def test_commit_wrapper(self): + # test a commit that raises at the point of commit, + # not the flush + + _session = self.sessionmaker() + + with _session.begin(): + # patch SQLAlchemy connection._commit_impl to + # raise a deadlock exception during commit only. + deadlock_on_commit = mock.Mock( + side_effect=sqla_exc.OperationalError( + "simulated deadlock", None, None)) + + # patch our own _raise_if_deadlock_error filter to treat + # this OperationalError as a DBDeadlock. + def _raise_if_deadlock_error(operational_error, engine_name): + self.assertIsInstance( + operational_error, sqla_exc.OperationalError) + self.assertTrue( + "simulated deadlock" in str(operational_error)) + raise db_exc.DBDeadlock(operational_error) + + connection = _session.connection() + + with mock.patch.object( + connection, '_commit_impl', deadlock_on_commit): + with mock.patch.object( + session, "_raise_if_deadlock_error", + _raise_if_deadlock_error): + self.assertRaises(db_exc.DBDeadlock, _session.commit) + def test_execute_wrapper(self): _session = self.sessionmaker() with _session.begin(): |