Delete gw port on exceptions

Clean up related core plugin ports on routers when there is a failure
creating the record for the router port.

The two partial bugs will be fixed after
I476d3e03c8ee763cc4be6d679fe9f501eb3a19b5 has merged.

Closes-Bug: #1600344
Partial-Bug: #1535225
Partial-Bug: #1535226
Change-Id: I8dd832f35e20d1ee090ebab921f0deea533b6fc8
This commit is contained in:
Kevin Benton 2016-06-22 18:23:19 -07:00
parent d78450e1fe
commit fbd3578d64
5 changed files with 104 additions and 35 deletions

View File

@ -368,17 +368,18 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
if not gw_port['fixed_ips']:
LOG.debug('No IPs available for external network %s',
network_id)
with context.session.begin(subtransactions=True):
router.gw_port = self._core_plugin._get_port(context.elevated(),
gw_port['id'])
router_port = RouterPort(
router_id=router.id,
port_id=gw_port['id'],
port_type=DEVICE_OWNER_ROUTER_GW
)
context.session.add(router)
context.session.add(router_port)
with p_utils.delete_port_on_error(self._core_plugin,
context.elevated(), gw_port['id']):
with context.session.begin(subtransactions=True):
router.gw_port = self._core_plugin._get_port(
context.elevated(), gw_port['id'])
router_port = RouterPort(
router_id=router.id,
port_id=gw_port['id'],
port_type=DEVICE_OWNER_ROUTER_GW
)
context.session.add(router)
context.session.add(router_port)
def _validate_gw_info(self, context, gw_port, info, ext_ips):
network_id = info['network_id'] if info else None
@ -746,6 +747,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
# This should be True unless adding an IPv6 prefix to an existing port
new_port = True
cleanup_port = False
if add_by_port:
port, subnets = self._add_interface_by_port(
@ -755,15 +757,19 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
else:
port, subnets, new_port = self._add_interface_by_subnet(
context, router, interface_info['subnet_id'], device_owner)
cleanup_port = new_port # only cleanup port we created
if new_port:
with context.session.begin(subtransactions=True):
router_port = RouterPort(
port_id=port['id'],
router_id=router.id,
port_type=device_owner
)
context.session.add(router_port)
with p_utils.delete_port_on_error(self._core_plugin,
context, port['id']) as delmgr:
delmgr.delete_on_error = cleanup_port
with context.session.begin(subtransactions=True):
router_port = RouterPort(
port_id=port['id'],
router_id=router.id,
port_type=device_owner
)
context.session.add(router_port)
gw_ips = []
gw_network_id = None

View File

@ -307,6 +307,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
# This should be True unless adding an IPv6 prefix to an existing port
new_port = True
cleanup_port = False
if add_by_port:
port, subnets = self._add_interface_by_port(
@ -314,31 +315,27 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
elif add_by_sub:
port, subnets, new_port = self._add_interface_by_subnet(
context, router, interface_info['subnet_id'], device_owner)
cleanup_port = new_port
subnet = subnets[0]
if new_port:
if router.extra_attributes.distributed and router.gw_port:
try:
with p_utils.delete_port_on_error(self._core_plugin,
context, port['id']) as delmgr:
if router.extra_attributes.distributed and router.gw_port:
admin_context = context.elevated()
self._add_csnat_router_interface_port(
admin_context, router, port['network_id'],
port['fixed_ips'][-1]['subnet_id'])
except Exception:
with excutils.save_and_reraise_exception():
# we need to preserve the original state prior
# the request by rolling back the port creation
# that led to new_port=True
self._core_plugin.delete_port(
admin_context, port['id'], l3_port_check=False)
with context.session.begin(subtransactions=True):
router_port = l3_db.RouterPort(
port_id=port['id'],
router_id=router.id,
port_type=device_owner
)
context.session.add(router_port)
delmgr.delete_on_error = cleanup_port
with context.session.begin(subtransactions=True):
router_port = l3_db.RouterPort(
port_id=port['id'],
router_id=router.id,
port_type=device_owner
)
context.session.add(router_port)
# NOTE: For IPv6 additional subnets added to the same
# network we need to update the CSNAT port with respective

View File

@ -16,6 +16,7 @@
Common utilities and helper functions for OpenStack Networking Plugins.
"""
import contextlib
import hashlib
from neutron_lib import constants as n_const
@ -23,9 +24,10 @@ from neutron_lib import exceptions
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import encodeutils
from oslo_utils import excutils
import webob.exc
from neutron._i18n import _, _LI
from neutron._i18n import _, _LE, _LI
from neutron.api.v2 import attributes
from neutron.callbacks import events
from neutron.callbacks import registry
@ -195,6 +197,26 @@ def create_port(core_plugin, context, port, check_allow_post=True):
return core_plugin.create_port(context, {'port': port_data})
class _DelManager(object):
def __init__(self):
self.delete_on_error = True
@contextlib.contextmanager
def delete_port_on_error(core_plugin, context, port_id):
mgr = _DelManager()
try:
yield mgr
except Exception:
with excutils.save_and_reraise_exception():
try:
if mgr.delete_on_error:
core_plugin.delete_port(context, port_id,
l3_port_check=False)
except Exception:
LOG.exception(_LE("Failed to cleanup port: %s"), port_id)
def get_interface_name(name, prefix='', max_len=n_const.DEVICE_NAME_MAX_LEN):
"""Construct an interface name based on the prefix and name.

View File

@ -23,6 +23,7 @@ from oslo_utils import uuidutils
import testscenarios
from webob import exc
from neutron import context as nctx
from neutron.db import api as db_api
from neutron.db import external_net_db
from neutron.db import l3_db
@ -30,6 +31,7 @@ from neutron.db import l3_gwmode_db
from neutron.db import models_v2
from neutron.extensions import l3
from neutron.extensions import l3_ext_gw_mode
from neutron import manager
from neutron.tests import base
from neutron.tests.unit.db import test_db_base_plugin_v2
from neutron.tests.unit.extensions import test_l3
@ -390,6 +392,20 @@ class ExtGwModeIntTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
expected_code=expected_code,
neutron_context=neutron_context)
def test_router_gateway_set_fail_after_port_create(self):
with self.router() as r, self.subnet() as s:
ext_net_id = s['subnet']['network_id']
self._set_net_external(ext_net_id)
plugin = manager.NeutronManager.get_plugin()
with mock.patch.object(plugin, '_get_port',
side_effect=ValueError()):
self._set_router_external_gateway(r['router']['id'],
ext_net_id,
expected_code=500)
ports = [p for p in plugin.get_ports(nctx.get_admin_context())
if p['device_owner'] == l3_db.DEVICE_OWNER_ROUTER_GW]
self.assertFalse(ports)
def test_router_gateway_set_retry(self):
with self.router() as r, self.subnet() as s:
ext_net_id = s['subnet']['network_id']

View File

@ -16,6 +16,7 @@ import hashlib
import mock
from neutron_lib import constants
import testtools
from neutron.plugins.common import utils
from neutron.tests import base
@ -70,3 +71,30 @@ class TestUtils(base.BaseTestCase):
self.assertEqual(12, len(utils.get_interface_name(LONG_NAME1,
prefix="pre-",
max_len=12)))
def test_delete_port_on_error(self):
core_plugin, context = mock.Mock(), mock.Mock()
port_id = 'pid'
with testtools.ExpectedException(ValueError):
with utils.delete_port_on_error(core_plugin, context, port_id):
raise ValueError()
core_plugin.delete_port.assert_called_once_with(context, port_id,
l3_port_check=False)
def test_delete_port_on_error_no_delete(self):
core_plugin, context = mock.Mock(), mock.Mock()
with testtools.ExpectedException(ValueError):
with utils.delete_port_on_error(core_plugin, context, 1) as cm:
cm.delete_on_error = False
raise ValueError()
self.assertFalse(core_plugin.delete_port.called)
def test_delete_port_on_error_fail_port_delete(self):
core_plugin, context = mock.Mock(), mock.Mock()
core_plugin.delete_port.side_effect = TypeError()
port_id = 'pid'
with testtools.ExpectedException(ValueError):
with utils.delete_port_on_error(core_plugin, context, port_id):
raise ValueError()
core_plugin.delete_port.assert_called_once_with(context, port_id,
l3_port_check=False)