Make revision bump robust to concurrent removals

If there expired objects in the session that are concurrently
deleted by another sesssion, the revision code will get an
ObjectDeletedError when it goes to check if the expired object
is a related object to the object it is handling. So we need
to catch this error an continue on since there is nothing it
needs to do.

Change-Id: Ie6034f36c34099f2646840cbdd569a4398534df5
Closes-Bug: #1611627
This commit is contained in:
Kevin Benton 2016-08-05 16:25:29 -07:00
parent f80bd6d121
commit 9e8bcf45a5
2 changed files with 31 additions and 11 deletions

View File

@ -13,6 +13,7 @@
from oslo_log import log as logging
from sqlalchemy import event
from sqlalchemy.orm import exc
from sqlalchemy.orm import session as se
from neutron._i18n import _, _LW
@ -53,17 +54,21 @@ class RevisionPlugin(service_base.ServicePluginBase):
def _bump_related_revisions(self, session, obj):
for revises_col in getattr(obj, 'revises_on_change', ()):
related_obj = self._find_related_obj(session, obj, revises_col)
if not related_obj:
LOG.warning(_LW("Could not find related %(col)s for resource "
"%(obj)s to bump revision."),
{'obj': obj, 'col': revises_col})
continue
# if related object revises others, bump those as well
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()
try:
related_obj = self._find_related_obj(session, obj, revises_col)
if not related_obj:
LOG.warning(_LW("Could not find related %(col)s for "
"resource %(obj)s to bump revision."),
{'obj': obj, 'col': revises_col})
continue
# if related object revises others, bump those as well
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()
except exc.ObjectDeletedError:
# object was in session but another writer deleted it
pass
def get_plugin_type(self):
return "revision_plugin"

View File

@ -16,6 +16,7 @@
import netaddr
from neutron import context as nctx
from neutron.db import models_v2
from neutron import manager
from neutron.tests.unit.plugins.ml2 import test_plugin
@ -34,6 +35,20 @@ class TestRevisionPlugin(test_plugin.Ml2PluginV2TestCase):
get_service_plugins()['L3_ROUTER_NAT'])
self.ctx = nctx.get_admin_context()
def test_handle_expired_object(self):
rp = manager.NeutronManager.get_service_plugins()['revision_plugin']
with self.port():
with self.ctx.session.begin():
ipal_obj = self.ctx.session.query(models_v2.IPAllocation).one()
# load port into our session
port_obj = self.ctx.session.query(models_v2.Port).one()
# simulate concurrent delete in another session
nctx.get_admin_context().session.query(models_v2.Port).delete()
# expire the port so the revision bumping code will trigger a
# lookup on its attributes and encounter an ObjectDeletedError
self.ctx.session.expire(port_obj)
rp._bump_related_revisions(self.ctx.session, ipal_obj)
def test_port_name_update_revises(self):
with self.port() as port:
rev = port['port']['revision']