Skip to content

Commit

Permalink
Fix auto-deletion of ports when deleting subnets in ML2
Browse files Browse the repository at this point in the history
When a subnet is deleted, certain ports referencing it are
auto-deleted. The implementation of NeutronDBPluginV2.delete_subnet()
does this at the DB level, so ML2's mechanism drivers were not being
called.

Ml2Plugin.delete_subnet() is changed to not use the base class's
method, and to auto-delete ports by calling its own delete_port()
method outside of the transaction. A loop avoids race conditions with
ports being asynchronously added to the subnet.

The logic in Ml2Plugin.delete_network() is also fixed to properly
handle auto-deleting ports and subnets, and debug logging is added to
the various delete methods.

Closes-Bug: 1234195
Partial-Bug: 1235486
Change-Id: I6d74f89d39ea8afe6915f1d2f9afdf66c0076f5a
(cherry picked from commit ed78b56)
  • Loading branch information
Bob Kukura authored and markmcclain committed Oct 11, 2013
1 parent 1cb74d6 commit bfe1dca
Showing 1 changed file with 91 additions and 23 deletions.
114 changes: 91 additions & 23 deletions neutron/plugins/ml2/plugin.py
Expand Up @@ -14,6 +14,7 @@
# under the License.

from oslo.config import cfg
from sqlalchemy import orm

from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
Expand Down Expand Up @@ -315,8 +316,8 @@ def create_network(self, context, network):
self.mechanism_manager.create_network_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_("mechanism_manager.create_network failed, "
"deleting network '%s'"), result['id'])
LOG.error(_("mechanism_manager.create_network_postcommit "
"failed, deleting network '%s'"), result['id'])
self.delete_network(context, result['id'])
return result

Expand Down Expand Up @@ -375,54 +376,76 @@ def delete_network(self, context, id):
# drivers from being called. This approach should be revisited
# when the API layer is reworked during icehouse.

LOG.debug(_("Deleting network %s"), id)
session = context.session
filter = {'network_id': [id]}
while True:
with session.begin(subtransactions=True):
filter = {'network_id': [id]}

# Get ports to auto-delete.
ports = self.get_ports(context, filters=filter)
only_auto_del = all(p['device_owner']
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)

# Get subnets to auto-delete.
subnets = self.get_subnets(context, filters=filter)
subnets = (session.query(models_v2.Subnet).
filter_by(network_id=id).
all())
LOG.debug(_("Subnets to auto-delete: %s"), subnets)

if not ports or 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)

LOG.debug(_("Deleting network record"))
record = self._get_network(context, id)
context.session.delete(record)
session.delete(record)

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

for port in ports:
self.delete_port(context, port['id'])
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)
raise

for subnet in subnets:
self.delete_subnet(context, subnet['id'])
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)
raise

try:
self.mechanism_manager.delete_network_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
# TODO(apech) - One or more mechanism driver failed to
# delete the network. Ideally we'd notify the caller of
# the fact that an error occurred.
pass
LOG.error(_("mechanism_manager.delete_network_postcommit failed"))
self.notifier.network_delete(context, id)

def create_subnet(self, context, subnet):
Expand All @@ -436,8 +459,8 @@ def create_subnet(self, context, subnet):
self.mechanism_manager.create_subnet_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_("mechanism_manager.create_subnet failed, "
"deleting subnet '%s'"), result['id'])
LOG.error(_("mechanism_manager.create_subnet_postcommit "
"failed, deleting subnet '%s'"), result['id'])
self.delete_subnet(context, result['id'])
return result

Expand All @@ -459,19 +482,62 @@ def update_subnet(self, context, id, subnet):
return updated_subnet

def delete_subnet(self, context, id):
# REVISIT(rkukura) The super(Ml2Plugin, self).delete_subnet()
# function is not used because it auto-deletes ports from the
# DB without invoking the derived class's delete_port(),
# preventing mechanism drivers from being called. This
# approach should be revisited when the API layer is reworked
# during icehouse.

LOG.debug(_("Deleting subnet %s"), id)
session = context.session
with session.begin(subtransactions=True):
subnet = self.get_subnet(context, id)
mech_context = driver_context.SubnetContext(self, context, subnet)
self.mechanism_manager.delete_subnet_precommit(mech_context)
super(Ml2Plugin, self).delete_subnet(context, id)
while True:
with session.begin(subtransactions=True):
# Get ports to auto-delete.
allocated = (session.query(models_v2.IPAllocation).
options(orm.joinedload('ports')).
filter_by(subnet_id=id).
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.
AUTO_DELETE_PORT_OWNERS
for a in allocated)
if not only_auto_del:
LOG.debug(_("Tenant-owned ports exist"))
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(
mech_context)

LOG.debug(_("Deleting subnet record"))
record = self._get_subnet(context, id)
session.delete(record)

LOG.debug(_("Committing transaction"))
break

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)
raise

try:
self.mechanism_manager.delete_subnet_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
# TODO(apech) - One or more mechanism driver failed to
# delete the subnet. Ideally we'd notify the caller of
# the fact that an error occurred.
pass
LOG.error(_("mechanism_manager.delete_subnet_postcommit failed"))

def create_port(self, context, port):
attrs = port['port']
Expand Down Expand Up @@ -500,8 +566,8 @@ def create_port(self, context, port):
self.mechanism_manager.create_port_postcommit(mech_context)
except ml2_exc.MechanismDriverError:
with excutils.save_and_reraise_exception():
LOG.error(_("mechanism_manager.create_port failed, "
"deleting port '%s'"), result['id'])
LOG.error(_("mechanism_manager.create_port_postcommit "
"failed, deleting port '%s'"), result['id'])
self.delete_port(context, result['id'])
self.notify_security_groups_member_updated(context, result)
return result
Expand Down Expand Up @@ -555,6 +621,7 @@ def update_port(self, context, id, port):
return updated_port

def delete_port(self, context, id, l3_port_check=True):
LOG.debug(_("Deleting port %s"), id)
l3plugin = manager.NeutronManager.get_service_plugins().get(
service_constants.L3_ROUTER_NAT)
if l3plugin and l3_port_check:
Expand All @@ -571,6 +638,7 @@ def delete_port(self, context, id, l3_port_check=True):
self.mechanism_manager.delete_port_precommit(mech_context)
self._delete_port_binding(mech_context)
self._delete_port_security_group_bindings(context, id)
LOG.debug(_("Calling base delete_port"))
super(Ml2Plugin, self).delete_port(context, id)

try:
Expand All @@ -579,7 +647,7 @@ def delete_port(self, context, id, l3_port_check=True):
# TODO(apech) - One or more mechanism driver failed to
# delete the port. Ideally we'd notify the caller of the
# fact that an error occurred.
pass
LOG.error(_("mechanism_manager.delete_port_postcommit failed"))
self.notify_security_groups_member_updated(context, port)

def update_port_status(self, context, port_id, status):
Expand Down

0 comments on commit bfe1dca

Please sign in to comment.