Fix DB integrity issues when using postgres

The IntegrityError faced on network deletion that
spooks Tempest tests is most likely caused by the
fact that the ml2 plugin does a dirty read. By
adding a 'select for update' we should be able to
address the issue once and for all.

Also, there's a chance that the dhcp port gets
reallocated while we are deleting the network. To
this aim, catch the integrity error and attempt
to recover from it.

This patch also removes the handling of errors
on concurrent reads that can no longer occur when
deleting the network.

(Hopefully) Closes-bug: #1239637

PS: This fix may be relevant for bugs #1243726 and #1246737

Change-Id: Iefae7fc8a903cc35d8bf80fc4cee533b7f64032c
This commit is contained in:
armando-migliaccio 2013-10-31 16:53:31 -07:00
parent 3c3e6eef90
commit 54d79acaab
2 changed files with 55 additions and 45 deletions

View File

@ -1416,7 +1416,11 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
self.delete_port(context, port['id'])
def _delete_port(self, context, id):
port = self._get_port(context, id)
query = (context.session.query(models_v2.Port).
enable_eagerloads(False).filter_by(id=id))
if not context.is_admin:
query = query.filter_by(tenant_id=context.tenant_id)
port = query.with_lockmode('update').one()
allocated_qry = context.session.query(
models_v2.IPAllocation).with_lockmode('update')

View File

@ -14,7 +14,7 @@
# under the License.
from oslo.config import cfg
from sqlalchemy import orm
from sqlalchemy import exc as sql_exc
from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
@ -36,6 +36,7 @@ from neutron.extensions import multiprovidernet as mpnet
from neutron.extensions import portbindings
from neutron.extensions import providernet as provider
from neutron import manager
from neutron.openstack.common import db as os_db
from neutron.openstack.common import excutils
from neutron.openstack.common import importutils
from neutron.openstack.common import log
@ -378,52 +379,60 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
LOG.debug(_("Deleting network %s"), id)
session = context.session
filter = {'network_id': [id]}
while True:
with session.begin(subtransactions=True):
# Get ports to auto-delete.
ports = (self._get_ports_query(context, filters=filter).
all())
LOG.debug(_("Ports to auto-delete: %s"), ports)
only_auto_del = all(p.device_owner
in db_base_plugin_v2.
AUTO_DELETE_PORT_OWNERS
for p in ports)
if not only_auto_del:
LOG.debug(_("Tenant-owned ports exist"))
raise exc.NetworkInUse(net_id=id)
try:
with session.begin(subtransactions=True):
# Get ports to auto-delete.
ports = (session.query(models_v2.Port).
enable_eagerloads(False).
filter_by(network_id=id).
with_lockmode('update').all())
LOG.debug(_("Ports to auto-delete: %s"), ports)
only_auto_del = all(p.device_owner
in db_base_plugin_v2.
AUTO_DELETE_PORT_OWNERS
for p in ports)
if not only_auto_del:
LOG.debug(_("Tenant-owned ports exist"))
raise exc.NetworkInUse(net_id=id)
# Get subnets to auto-delete.
subnets = (session.query(models_v2.Subnet).
filter_by(network_id=id).
all())
LOG.debug(_("Subnets to auto-delete: %s"), subnets)
# Get subnets to auto-delete.
subnets = (session.query(models_v2.Subnet).
enable_eagerloads(False).
filter_by(network_id=id).
with_lockmode('update').all())
LOG.debug(_("Subnets to auto-delete: %s"), subnets)
if not (ports or subnets):
network = self.get_network(context, id)
mech_context = driver_context.NetworkContext(self,
context,
network)
self.mechanism_manager.delete_network_precommit(
mech_context)
if not (ports or subnets):
network = self.get_network(context, id)
mech_context = driver_context.NetworkContext(self,
context,
network)
self.mechanism_manager.delete_network_precommit(
mech_context)
LOG.debug(_("Deleting network record"))
record = self._get_network(context, id)
session.delete(record)
record = self._get_network(context, id)
LOG.debug(_("Deleting network record %s"), record)
session.delete(record)
for segment in mech_context.network_segments:
self.type_manager.release_segment(session, segment)
for segment in mech_context.network_segments:
self.type_manager.release_segment(session, segment)
# The segment records are deleted via cascade from the
# network record, so explicit removal is not necessary.
LOG.debug(_("Committing transaction"))
break
# The segment records are deleted via cascade from the
# network record, so explicit removal is not necessary.
LOG.debug(_("Committing transaction"))
break
except os_db.exception.DBError as e:
if isinstance(e.inner_exception, sql_exc.IntegrityError):
msg = _("A concurrent port creation has occurred")
LOG.exception(msg)
continue
else:
raise
for port in ports:
try:
self.delete_port(context, port.id)
except exc.PortNotFound:
LOG.debug(_("Port %s concurrently deleted"), port.id)
except Exception:
LOG.exception(_("Exception auto-deleting port %s"),
port.id)
@ -432,8 +441,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
for subnet in subnets:
try:
self.delete_subnet(context, subnet.id)
except exc.SubnetNotFound:
LOG.debug(_("Subnet %s concurrently deleted"), subnet.id)
except Exception:
LOG.exception(_("Exception auto-deleting subnet %s"),
subnet.id)
@ -493,11 +500,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
session = context.session
while True:
with session.begin(subtransactions=True):
subnet = self.get_subnet(context, id)
# Get ports to auto-delete.
allocated = (session.query(models_v2.IPAllocation).
options(orm.joinedload('ports')).
filter_by(subnet_id=id).
all())
join(models_v2.Port).
filter_by(network_id=subnet['network_id']).
with_lockmode('update').all())
LOG.debug(_("Ports to auto-delete: %s"), allocated)
only_auto_del = all(not a.port_id or
a.ports.device_owner in db_base_plugin_v2.
@ -508,7 +517,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
raise exc.SubnetInUse(subnet_id=id)
if not allocated:
subnet = self.get_subnet(context, id)
mech_context = driver_context.SubnetContext(self, context,
subnet)
self.mechanism_manager.delete_subnet_precommit(
@ -524,8 +532,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
for a in allocated:
try:
self.delete_port(context, a.port_id)
except exc.PortNotFound:
LOG.debug(_("Port %s concurrently deleted"), a.port_id)
except Exception:
LOG.exception(_("Exception auto-deleting port %s"),
a.port_id)