summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-11-14 04:11:06 +0000
committerGerrit Code Review <review@openstack.org>2014-11-14 04:11:06 +0000
commit3b6c0417de558225fce2d20ef88054eaeeff3011 (patch)
tree254f4ee42bfcdea2bd013714221fac692ba994d1
parent7dabb52043d27bedc4f55f1f4eec20bc4b8a93a9 (diff)
parentca04d927d9623e2967171a443260cac34b2f962e (diff)
downloadoslo-incubator-3b6c0417de558225fce2d20ef88054eaeeff3011.tar.gz
Merge "Add _wrap_db_error() support to Session.commit()" into stable/icehouse
-rw-r--r--openstack/common/db/sqlalchemy/session.py8
-rw-r--r--tests/unit/db/sqlalchemy/test_sqlalchemy.py48
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():