Merge "Handle ports assigned to routers without routerports" into stable/train

This commit is contained in:
Zuul 2019-10-19 00:49:15 +00:00 committed by Gerrit Code Review
commit 0d462fdc7a
3 changed files with 240 additions and 53 deletions

View File

@ -925,21 +925,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
def _remove_interface_by_port(self, context, router_id,
port_id, subnet_id, owner):
obj = l3_obj.RouterPort.get_object(
context,
port_id=port_id,
router_id=router_id,
port_type=owner
)
if obj:
try:
port = self._core_plugin.get_port(context, obj.port_id)
except n_exc.PortNotFound:
raise l3_exc.RouterInterfaceNotFound(
router_id=router_id, port_id=port_id)
else:
ports = port_obj.Port.get_ports_by_router_and_port(
context, router_id, owner, port_id)
if len(ports) < 1:
raise l3_exc.RouterInterfaceNotFound(
router_id=router_id, port_id=port_id)
port = ports[0]
port_subnet_ids = [fixed_ip['subnet_id']
for fixed_ip in port['fixed_ips']]
if subnet_id and subnet_id not in port_subnet_ids:
@ -952,17 +944,15 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
context, router_id, port_subnet_id)
self._core_plugin.delete_port(context, port['id'],
l3_port_check=False)
return (port, subnets)
return port, subnets
def _remove_interface_by_subnet(self, context,
router_id, subnet_id, owner):
self._confirm_router_interface_not_in_use(
context, router_id, subnet_id)
subnet = self._core_plugin.get_subnet(context, subnet_id)
try:
ports = port_obj.Port.get_ports_by_router(
context, router_id, owner, subnet)
ports = port_obj.Port.get_ports_by_router_and_network(
context, router_id, owner, subnet['network_id'])
for p in ports:
try:
@ -982,16 +972,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
self._core_plugin.delete_port(context, p['id'],
l3_port_check=False)
return (p, [subnet])
except exc.NoResultFound:
pass
raise l3_exc.RouterInterfaceNotFoundForSubnet(
router_id=router_id, subnet_id=subnet_id)
@db_api.retry_if_session_inactive()
def remove_router_interface(self, context, router_id, interface_info):
remove_by_port, remove_by_subnet = (
self._validate_interface_info(interface_info, for_removal=True)
)
remove_by_port, _ = self._validate_interface_info(interface_info,
for_removal=True)
port_id = interface_info.get('port_id')
subnet_id = interface_info.get('subnet_id')
device_owner = self._get_device_owner(context, router_id)
@ -999,9 +986,6 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
port, subnets = self._remove_interface_by_port(context, router_id,
port_id, subnet_id,
device_owner)
# remove_by_subnet is not used here, because the validation logic of
# _validate_interface_info ensures that at least one of remote_by_*
# is True.
else:
port, subnets = self._remove_interface_by_subnet(
context, router_id, subnet_id, device_owner)

View File

@ -15,6 +15,7 @@
import netaddr
from neutron_lib import constants
from neutron_lib.utils import net as net_utils
from oslo_log import log as logging
from oslo_utils import versionutils
from oslo_versionedobjects import fields as obj_fields
@ -28,6 +29,8 @@ from neutron.objects.db import api as obj_db_api
from neutron.objects.qos import binding
from neutron.plugins.ml2 import models as ml2_models
LOG = logging.getLogger(__name__)
class PortBindingBase(base.NeutronDbObject):
@ -508,15 +511,65 @@ class Port(base.NeutronDbObject):
primitive.pop('qos_network_policy_id', None)
@classmethod
def get_ports_by_router(cls, context, router_id, owner, subnet):
rport_qry = context.session.query(models_v2.Port).join(
l3.RouterPort)
ports = rport_qry.filter(
l3.RouterPort.router_id == router_id,
l3.RouterPort.port_type == owner,
models_v2.Port.network_id == subnet['network_id']
)
return [cls._load_object(context, db_obj) for db_obj in ports.all()]
def get_ports_by_router_and_network(cls, context, router_id, owner,
network_id):
"""Returns port objects filtering by router ID, owner and network ID"""
rports_filter = (models_v2.Port.network_id == network_id, )
router_filter = (models_v2.Port.network_id == network_id, )
return cls._get_ports_by_router(context, router_id, owner,
rports_filter, router_filter)
@classmethod
def get_ports_by_router_and_port(cls, context, router_id, owner, port_id):
"""Returns port objects filtering by router ID, owner and port ID"""
rports_filter = (l3.RouterPort.port_id == port_id, )
router_filter = (models_v2.Port.id == port_id, )
return cls._get_ports_by_router(context, router_id, owner,
rports_filter, router_filter)
@classmethod
def _get_ports_by_router(cls, context, router_id, owner, rports_filter,
router_filter):
"""Returns port objects filtering by router id and owner
The method will receive extra filters depending of the caller (filter
by network or filter by port).
The ports are retrieved using:
- The RouterPort registers. Each time a port is assigned to a router,
a new RouterPort register is added to the DB.
- The port owner and device_id information.
Both searches should return the same result. If not, a warning message
is logged and the port list to be returned is completed with the
missing ones.
"""
rports_filter += (l3.RouterPort.router_id == router_id,
l3.RouterPort.port_type == owner)
router_filter += (models_v2.Port.device_id == router_id,
models_v2.Port.device_owner == owner)
ports = context.session.query(models_v2.Port).join(
l3.RouterPort).filter(*rports_filter)
ports_rports = [cls._load_object(context, db_obj)
for db_obj in ports.all()]
ports = context.session.query(models_v2.Port).filter(*router_filter)
ports_router = [cls._load_object(context, db_obj)
for db_obj in ports.all()]
ports_rports_ids = {p.id for p in ports_rports}
ports_router_ids = {p.id for p in ports_router}
missing_port_ids = ports_router_ids - ports_rports_ids
if missing_port_ids:
LOG.warning('The following ports, assigned to router '
'%(router_id)s, do not have a "routerport" register: '
'%(port_ids)s', {'router_id': router_id,
'port_ids': missing_port_ids})
port_objs = [p for p in ports_router if p.id in missing_port_ids]
ports_rports += port_objs
return ports_rports
@classmethod
def get_ports_ids_by_security_groups(cls, context, security_group_ids,

View File

@ -22,6 +22,7 @@ from neutron_lib import constants as n_const
from neutron_lib import context
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import l3 as l3_exc
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.plugins import utils as plugin_utils
from oslo_utils import uuidutils
@ -30,8 +31,12 @@ import testtools
from neutron.db import l3_db
from neutron.db.models import l3 as l3_models
from neutron.objects import base as base_obj
from neutron.objects import network as network_obj
from neutron.objects import ports as port_obj
from neutron.objects import router as l3_obj
from neutron.objects import subnet as subnet_obj
from neutron.tests import base
from neutron.tests.unit.db import test_db_base_plugin_v2
class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
@ -377,3 +382,148 @@ class L3_NAT_db_mixin(base.BaseTestCase):
self.assertRaises(
n_exc.BadRequest,
self.db.add_router_interface, mock.Mock(), router_db.id)
class FakeL3Plugin(l3_db.L3_NAT_dbonly_mixin):
pass
class L3TestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
GET_PORTS_BY_ROUTER_MSG = (
'The following ports, assigned to router %(router_id)s, do not have a '
'"routerport" register: %(port_ids)s')
def setUp(self, *args, **kwargs):
super(L3TestCase, self).setUp(plugin='ml2')
self.core_plugin = directory.get_plugin()
self.ctx = context.get_admin_context()
self.mixin = FakeL3Plugin()
directory.add_plugin(plugin_constants.L3, self.mixin)
self.network = self.create_network()
self.subnets = []
self.subnets.append(self.create_subnet(self.network, '1.1.1.1',
'1.1.1.0/24'))
self.subnets.append(self.create_subnet(self.network, '1.1.2.1',
'1.1.2.0/24'))
router = {'router': {'name': 'foo_router', 'admin_state_up': True,
'tenant_id': 'foo_tenant'}}
self.router = self.create_router(router)
self.ports = []
for subnet in self.subnets:
ipa = str(netaddr.IPNetwork(subnet['subnet']['cidr']).ip + 10)
fixed_ips = [{'subnet_id': subnet['subnet']['id'],
'ip_address': ipa}]
self.ports.append(self.create_port(
self.network['network']['id'], {'fixed_ips': fixed_ips}))
self.addCleanup(self._clean_objs)
def _clean_objs(self):
port_obj.Port.delete_objects(
self.ctx, network_id=self.network['network']['id'])
subnet_obj.Subnet.delete_objects(
self.ctx, network_id=self.network['network']['id'])
network_obj.Network.get_object(
self.ctx, id=self.network['network']['id']).delete()
l3_obj.Router.get_object(self.ctx, id=self.router['id']).delete()
def create_router(self, router):
with self.ctx.session.begin(subtransactions=True):
return self.mixin.create_router(self.ctx, router)
def create_port(self, net_id, port_info):
with self.ctx.session.begin(subtransactions=True):
return self._make_port(self.fmt, net_id, **port_info)
def create_network(self, name=None, **kwargs):
name = name or 'network1'
with self.ctx.session.begin(subtransactions=True):
return self._make_network(self.fmt, name, True, **kwargs)
def create_subnet(self, network, gateway, cidr, **kwargs):
with self.ctx.session.begin(subtransactions=True):
return self._make_subnet(self.fmt, network, gateway, cidr,
**kwargs)
def _add_router_interfaces(self):
return [self.mixin.add_router_interface(
self.ctx, self.router['id'],
interface_info={'port_id': port['port']['id']})
for port in self.ports]
def _check_routerports(self, ri_statuses):
port_ids = []
for idx, ri_status in enumerate(ri_statuses):
rp_obj = l3_obj.RouterPort.get_object(
self.ctx, port_id=self.ports[idx]['port']['id'],
router_id=self.router['id'])
if ri_status:
self.assertEqual(self.ports[idx]['port']['id'], rp_obj.port_id)
port_ids.append(rp_obj.port_id)
else:
self.assertIsNone(rp_obj)
_router_obj = l3_obj.Router.get_object(self.ctx, id=self.router['id'])
router_port_ids = [rp.port_id for rp in
_router_obj.db_obj.attached_ports]
self.assertEqual(sorted(port_ids), sorted(router_port_ids))
@mock.patch.object(port_obj, 'LOG')
def test_remove_router_interface_by_port(self, mock_log):
self._add_router_interfaces()
self._check_routerports((True, True))
interface_info = {'port_id': self.ports[0]['port']['id']}
self.mixin.remove_router_interface(self.ctx, self.router['id'],
interface_info)
mock_log.warning.assert_not_called()
self._check_routerports((False, True))
@mock.patch.object(port_obj, 'LOG')
def test_remove_router_interface_by_port_removed_rport(self, mock_log):
self._add_router_interfaces()
self._check_routerports((True, True))
rp_obj = l3_obj.RouterPort.get_object(
self.ctx, router_id=self.router['id'],
port_id=self.ports[0]['port']['id'])
rp_obj.delete()
interface_info = {'port_id': self.ports[0]['port']['id']}
self.mixin.remove_router_interface(self.ctx, self.router['id'],
interface_info)
msg_vars = {'router_id': self.router['id'],
'port_ids': {self.ports[0]['port']['id']}}
mock_log.warning.assert_called_once_with(self.GET_PORTS_BY_ROUTER_MSG,
msg_vars)
self._check_routerports((False, True))
@mock.patch.object(port_obj, 'LOG')
def test_remove_router_interface_by_subnet(self, mock_log):
self._add_router_interfaces()
self._check_routerports((True, True))
interface_info = {'subnet_id': self.subnets[1]['subnet']['id']}
self.mixin.remove_router_interface(self.ctx, self.router['id'],
interface_info)
mock_log.warning.not_called_once()
self._check_routerports((True, False))
@mock.patch.object(port_obj, 'LOG')
def test_remove_router_interface_by_subnet_removed_rport(self, mock_log):
self._add_router_interfaces()
self._check_routerports((True, True))
rp_obj = l3_obj.RouterPort.get_object(
self.ctx, router_id=self.router['id'],
port_id=self.ports[0]['port']['id'])
rp_obj.delete()
interface_info = {'subnet_id': self.subnets[0]['subnet']['id']}
self.mixin.remove_router_interface(self.ctx, self.router['id'],
interface_info)
msg_vars = {'router_id': self.router['id'],
'port_ids': {self.ports[0]['port']['id']}}
mock_log.warning.assert_called_once_with(self.GET_PORTS_BY_ROUTER_MSG,
msg_vars)
self._check_routerports((False, True))