diff --git a/neutron/db/standard_attr.py b/neutron/db/standard_attr.py index e51eb5adb5a..a6e1c6fdb3d 100644 --- a/neutron/db/standard_attr.py +++ b/neutron/db/standard_attr.py @@ -66,7 +66,8 @@ class StandardAttribute(model_base.BASEV2): __mapper_args__ = { # see http://docs.sqlalchemy.org/en/latest/orm/versioning.html for # details about how this works - "version_id_col": revision_number + "version_id_col": revision_number, + "version_id_generator": False # revision plugin increments manually } def bump_revision(self): diff --git a/neutron/services/revisions/revision_plugin.py b/neutron/services/revisions/revision_plugin.py index 4aa9fa358b0..ef486895b1e 100644 --- a/neutron/services/revisions/revision_plugin.py +++ b/neutron/services/revisions/revision_plugin.py @@ -34,12 +34,16 @@ class RevisionPlugin(service_base.ServicePluginBase): def __init__(self): super(RevisionPlugin, self).__init__() db_api.sqla_listen(se.Session, 'before_flush', self.bump_revisions) + db_api.sqla_listen(se.Session, 'after_commit', + self._clear_rev_bumped_flags) + db_api.sqla_listen(se.Session, 'after_rollback', + self._clear_rev_bumped_flags) def bump_revisions(self, session, context, instances): # bump revision number for any updated objects in the session for obj in session.dirty: if isinstance(obj, standard_attr.HasStandardAttributes): - obj.bump_revision() + self._bump_obj_revision(session, obj) # see if any created/updated/deleted objects bump the revision # of another object @@ -63,7 +67,7 @@ class RevisionPlugin(service_base.ServicePluginBase): self._bump_related_revisions(session, related_obj) # no need to bump revisions on related objects being deleted if related_obj not in session.deleted: - related_obj.bump_revision() + self._bump_obj_revision(session, related_obj) except exc.ObjectDeletedError: # object was in session but another writer deleted it pass @@ -98,3 +102,20 @@ class RevisionPlugin(service_base.ServicePluginBase): "have load_on_pending set to True to " "bump parent revisions on create: %s"), relationship_col) + + def _clear_rev_bumped_flags(self, session): + """This clears all flags on commit/rollback to enable rev bumps.""" + for inst in session: + setattr(inst, '_rev_bumped', False) + + def _bump_obj_revision(self, session, obj): + """Increment object revision in compare and swap fashion. + + Before the increment, this checks and enforces any revision number + constraints. + """ + if getattr(obj, '_rev_bumped', False): + # we've already bumped the revision of this object in this txn + return + obj.bump_revision() + setattr(obj, '_rev_bumped', True) diff --git a/neutron/tests/tempest/api/test_trunk.py b/neutron/tests/tempest/api/test_trunk.py index 3e035a04685..aef82e7e8d3 100644 --- a/neutron/tests/tempest/api/test_trunk.py +++ b/neutron/tests/tempest/api/test_trunk.py @@ -108,18 +108,18 @@ class TrunkTestJSON(TrunkTestJSONBase): @decorators.idempotent_id('4ce46c22-a2b6-4659-bc5a-0ef2463cab32') def test_create_update_trunk(self): trunk = self._create_trunk_with_network_and_parent(None) - self.assertEqual(1, trunk['trunk']['revision_number']) + rev = trunk['trunk']['revision_number'] trunk_id = trunk['trunk']['id'] res = self._show_trunk(trunk_id) self.assertTrue(res['trunk']['admin_state_up']) - self.assertEqual(1, res['trunk']['revision_number']) + self.assertEqual(rev, res['trunk']['revision_number']) self.assertEqual("", res['trunk']['name']) self.assertEqual("", res['trunk']['description']) res = self.client.update_trunk( trunk_id, name='foo', admin_state_up=False) self.assertFalse(res['trunk']['admin_state_up']) self.assertEqual("foo", res['trunk']['name']) - self.assertGreater(res['trunk']['revision_number'], 1) + self.assertGreater(res['trunk']['revision_number'], rev) # enable the trunk so that it can be managed self.client.update_trunk(trunk_id, admin_state_up=True)