Make it possible to opt out of nested transactions

Nested transactions work for simple things, but intermediate
results from transaction commands like create/add that are used
in subsequent nested transactions won't exist until the the end of
the whole nested transaction, causing failures. Since this can be
surprising, make it possible to easily opt out on a per-transaction
or per-API basis.

Change-Id: I4f7997d5dc11a190e188c16e8eac9fe5df8b0d0d
This commit is contained in:
Terry Wilson 2019-03-13 16:32:18 -05:00
parent 1ae5385b67
commit 4eabf17683
4 changed files with 37 additions and 7 deletions

View File

@ -71,8 +71,9 @@ class Transaction(object):
@six.add_metaclass(abc.ABCMeta)
class API(object):
def __init__(self):
def __init__(self, nested_transactions=True):
# Mapping between a (green)thread and its transaction.
self._nested_txns = nested_transactions
self._nested_txns_map = {}
@abc.abstractmethod
@ -88,17 +89,23 @@ class API(object):
"""
@contextlib.contextmanager
def transaction(self, check_error=False, log_errors=True, **kwargs):
def transaction(self, check_error=False, log_errors=True, nested=True,
**kwargs):
"""Create a transaction context.
:param check_error: Allow the transaction to raise an exception?
:type check_error: bool
:param log_errors: Log an error if the transaction fails?
:type log_errors: bool
:param nested: Allow nested transactions be merged into one txn
:type nested: bool
:returns: Either a new transaction or an existing one.
:rtype: :class:`Transaction`
"""
cur_thread_id = thread.get_ident()
# ojbect() is unique, so if we are not nested, this will always result
# in a KeyError on lookup and so a unique Transaction
nested = nested and self._nested_txns
cur_thread_id = thread.get_ident() if nested else object()
try:
yield self._nested_txns_map[cur_thread_id]

View File

@ -26,8 +26,8 @@ class Backend(object):
lookup_table = {}
ovsdb_connection = None
def __init__(self, connection):
super(Backend, self).__init__()
def __init__(self, connection, **kwargs):
super(Backend, self).__init__(**kwargs)
self.start_connection(connection)
@classmethod

View File

@ -33,3 +33,10 @@ class TransactionTestCase(base.TestCase):
transaction = impl_idl.OvsVsctlTransaction(mock.sentinel,
mock.Mock(), 0)
transaction.post_commit(mock.Mock())
class TestOvsdbIdl(base.TestCase):
def test_nested_txns(self):
conn = mock.MagicMock()
api = impl_idl.OvsdbIdl(conn, nested_transactions=False)
self.assertFalse(api._nested_txns)

View File

@ -65,7 +65,9 @@ class FakeTransaction(object):
class TestingAPI(api.API):
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
return FakeTransaction()
txn = FakeTransaction()
mock.patch.object(txn, 'commit').start()
return txn
TestingAPI.__abstractmethods__ = set()
@ -75,7 +77,6 @@ class TransactionTestCase(base.TestCase):
def setUp(self):
super(TransactionTestCase, self).setUp()
self.api = TestingAPI()
mock.patch.object(FakeTransaction, 'commit').start()
self.useFixture(GreenThreadingFixture())
def test_transaction_nested(self):
@ -84,6 +85,21 @@ class TransactionTestCase(base.TestCase):
self.assertIs(txn1, txn2)
txn1.commit.assert_called_once_with()
def test_transaction_nested_false(self):
with self.api.transaction(nested=False) as txn1:
with self.api.transaction() as txn2:
self.assertIsNot(txn1, txn2)
txn1.commit.assert_called_once_with()
txn2.commit.assert_called_once_with()
def test_api_level_transaction_nested_fales(self):
api = TestingAPI(nested_transactions=False)
with api.transaction() as txn1:
with api.transaction() as txn2:
self.assertIsNot(txn1, txn2)
txn1.commit.assert_called_once_with()
txn2.commit.assert_called_once_with()
def test_transaction_no_nested_transaction_after_error(self):
class TestException(Exception):
pass