Skip to content

Commit bd6595d

Browse files
committed
Log warning about port forwardings that won't work properly
This is follow up patch to [1] in which was added warning about incompatible configuration of the vlan/flat networks allowed as tenant networks, distributed routing and port forwardings. In this new patch similar warning is logged every time when port forwarding is created using router which have actually connected vlan or flat networks as "internal networks" (external gateway network is fine) and when distributed routing is enabled in the Neutron config. This patch additionally adds "neutron:is_ext_gw" flag to the Logical_Router_Port's external_ids. With that it's easier to check if network is used as gateway network (no checks needed) or not (checks are perfomed and warning may be logged). [1] https://review.opendev.org/c/openstack/neutron/+/892542 Conflicts: neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py neutron/tests/unit/services/ovn_l3/test_plugin.py Related-Bug: #2028846 Change-Id: I101128bdb421ec83df5cdcb0d486cbafbbca2ce5 (cherry picked from commit a355d2a) (cherry picked from commit 3eaa326)
1 parent c40f637 commit bd6595d

File tree

7 files changed

+200
-4
lines changed

7 files changed

+200
-4
lines changed

neutron/common/ovn/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,15 @@ def is_gateway_chassis_invalid(chassis_name, gw_chassis,
679679

680680

681681
def is_provider_network(network):
682+
"""Check if given network is provider network
683+
:param network: (str, dict) it can be given as network object or as string
684+
with network ID only. In the latter case, network object
685+
will be loaded from the database
686+
"""
687+
if isinstance(network, str):
688+
ctx = n_context.get_admin_context()
689+
plugin = directory.get_plugin()
690+
network = plugin.get_network(ctx, network)
682691
return network.get(provider_net.PHYSICAL_NETWORK, False)
683692

684693

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,41 @@ def create_router_extra_attributes_registers(self):
971971

972972
raise periodics.NeverAgain()
973973

974+
# TODO(slaweq): Remove this in the E cycle (C+2 as it will be next SLURP)
975+
@periodics.periodic(spacing=600, run_immediately=True)
976+
def add_gw_port_info_to_logical_router_port(self):
977+
"""Add info if LRP is connecting internal subnet or ext gateway."""
978+
cmds = []
979+
context = n_context.get_admin_context()
980+
for router in self._ovn_client._l3_plugin.get_routers(context):
981+
ext_gw_networks = [
982+
ext_gw['network_id'] for ext_gw in router['external_gateways']]
983+
rtr_name = 'neutron-{}'.format(router['id'])
984+
ovn_lr = self._nb_idl.get_lrouter(rtr_name)
985+
for lrp in ovn_lr.ports:
986+
if ovn_const.OVN_ROUTER_IS_EXT_GW in lrp.external_ids:
987+
continue
988+
ovn_network_name = lrp.external_ids.get(
989+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY)
990+
if not ovn_network_name:
991+
continue
992+
network_id = ovn_network_name.replace('neutron-', '')
993+
if not network_id:
994+
continue
995+
is_ext_gw = str(network_id in ext_gw_networks)
996+
external_ids = lrp.external_ids
997+
external_ids[ovn_const.OVN_ROUTER_IS_EXT_GW] = is_ext_gw
998+
cmds.append(
999+
self._nb_idl.update_lrouter_port(
1000+
name=lrp.name,
1001+
external_ids=external_ids))
1002+
1003+
if cmds:
1004+
with self._nb_idl.transaction(check_error=True) as txn:
1005+
for cmd in cmds:
1006+
txn.add(cmd)
1007+
raise periodics.NeverAgain()
1008+
9741009
@periodics.periodic(spacing=600, run_immediately=True)
9751010
def check_router_default_route_empty_dst_ip(self):
9761011
"""Check routers with default route with empty dst-ip (LP: #2002993).

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,10 @@ def _gen_router_port_ext_ids(self, port):
15371537
ovn_const.OVN_SUBNET_EXT_IDS_KEY:
15381538
' '.join(utils.get_port_subnet_ids(port)),
15391539
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
1540-
utils.ovn_name(port['network_id'])}
1540+
utils.ovn_name(port['network_id']),
1541+
ovn_const.OVN_ROUTER_IS_EXT_GW:
1542+
str(const.DEVICE_OWNER_ROUTER_GW == port.get('device_owner')),
1543+
}
15411544

15421545
router_id = port.get('device_id')
15431546
if router_id:

neutron/services/portforwarding/drivers/ovn/driver.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
from neutron_lib.plugins import constants as plugin_constants
1818
from neutron_lib.plugins import directory
1919
from oslo_log import log
20+
from oslo_utils import strutils
2021
from ovsdbapp.backend.ovs_idl import idlutils
2122
from ovsdbapp import constants as ovsdbapp_const
2223

2324
from neutron.common.ovn import constants as ovn_const
2425
from neutron.common.ovn import exceptions as ovn_exc
2526
from neutron.common.ovn import utils as ovn_utils
27+
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
2628
from neutron.db import ovn_revision_numbers_db as db_rev
2729
from neutron import manager
2830
from neutron.objects import port_forwarding as port_forwarding_obj
@@ -129,7 +131,41 @@ def _port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj,
129131
"Switch %s failed as it is not found",
130132
lb_name, ls_name)
131133

134+
def _validate_router_networks(self, nb_ovn, router_id):
135+
if not ovn_conf.is_ovn_distributed_floating_ip():
136+
return
137+
rtr_name = 'neutron-{}'.format(router_id)
138+
ovn_lr = nb_ovn.get_lrouter(rtr_name)
139+
if not ovn_lr:
140+
return
141+
for lrouter_port in ovn_lr.ports:
142+
is_ext_gw = strutils.bool_from_string(
143+
lrouter_port.external_ids.get(ovn_const.OVN_ROUTER_IS_EXT_GW))
144+
if is_ext_gw:
145+
# NOTE(slaweq): This is external gateway port of the router and
146+
# this not needs to be checked
147+
continue
148+
ovn_network_name = lrouter_port.external_ids.get(
149+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY)
150+
if not ovn_network_name:
151+
continue
152+
network_id = ovn_network_name.replace('neutron-', '')
153+
if not network_id:
154+
continue
155+
if ovn_utils.is_provider_network(network_id):
156+
LOG.warning("Port forwarding configured in the router "
157+
"%(router_id)s will not work properly as "
158+
"distributed floating IPs are enabled "
159+
"and at least one provider network "
160+
"(%(network_id)s) is connected to that router. "
161+
"See bug https://launchpad.net/bugs/2028846 for "
162+
"more details.", {
163+
'router_id': router_id,
164+
'network_id': network_id})
165+
return
166+
132167
def port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj):
168+
self._validate_router_networks(nb_ovn, pf_obj.router_id)
133169
pf_objs = pf_obj.unroll_port_ranges()
134170
is_range = len(pf_objs) > 1
135171
for pf_obj in pf_objs:

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,70 @@ def test_update_port_virtual_type(self, *args):
937937
('type', constants.LSP_TYPE_VIRTUAL))]
938938
nb_idl.db_set.assert_has_calls(expected_calls)
939939

940+
def test_add_gw_port_info_to_logical_router_port(self):
941+
nb_idl = self.fake_ovn_client._nb_idl
942+
ext_net_id = uuidutils.generate_uuid()
943+
internal_net_id = uuidutils.generate_uuid()
944+
routers_db = [{
945+
'id': uuidutils.generate_uuid(),
946+
'external_gateways': [{'network_id': ext_net_id}]}]
947+
ext_gw_lrp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
948+
attrs={'external_ids': {
949+
constants.OVN_NETWORK_NAME_EXT_ID_KEY:
950+
'neutron-{}'.format(ext_net_id)}})
951+
internal_net_lrp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
952+
attrs={'external_ids': {
953+
constants.OVN_NETWORK_NAME_EXT_ID_KEY:
954+
'neutron-{}'.format(internal_net_id)}})
955+
fake_router = fakes.FakeOvsdbRow.create_one_ovsdb_row(
956+
attrs={
957+
'ports': [ext_gw_lrp, internal_net_lrp]})
958+
959+
expected_new_ext_gw_lrp_ids = ext_gw_lrp.external_ids
960+
expected_new_ext_gw_lrp_ids[constants.OVN_ROUTER_IS_EXT_GW] = 'True'
961+
expected_new_internal_lrp_ids = internal_net_lrp.external_ids
962+
expected_new_internal_lrp_ids[constants.OVN_ROUTER_IS_EXT_GW] = 'False'
963+
964+
self.fake_ovn_client._l3_plugin.get_routers.return_value = routers_db
965+
nb_idl.get_lrouter.return_value = fake_router
966+
self.assertRaises(
967+
periodics.NeverAgain,
968+
self.periodic.add_gw_port_info_to_logical_router_port)
969+
nb_idl.update_lrouter_port.assert_has_calls([
970+
mock.call(name=ext_gw_lrp.name,
971+
external_ids=expected_new_ext_gw_lrp_ids),
972+
mock.call(name=internal_net_lrp.name,
973+
external_ids=expected_new_internal_lrp_ids)],
974+
any_order=True)
975+
976+
def test_add_gw_port_info_to_logical_router_port_no_action_needed(self):
977+
nb_idl = self.fake_ovn_client._nb_idl
978+
ext_net_id = uuidutils.generate_uuid()
979+
internal_net_id = uuidutils.generate_uuid()
980+
routers_db = [{
981+
'id': uuidutils.generate_uuid(),
982+
'external_gateways': [{'network_id': ext_net_id}]}]
983+
ext_gw_lrp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
984+
attrs={'external_ids': {
985+
constants.OVN_NETWORK_NAME_EXT_ID_KEY:
986+
'neutron-{}'.format(ext_net_id),
987+
constants.OVN_ROUTER_IS_EXT_GW: 'True'}})
988+
internal_net_lrp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
989+
attrs={'external_ids': {
990+
constants.OVN_NETWORK_NAME_EXT_ID_KEY:
991+
'neutron-{}'.format(internal_net_id),
992+
constants.OVN_ROUTER_IS_EXT_GW: 'False'}})
993+
fake_router = fakes.FakeOvsdbRow.create_one_ovsdb_row(
994+
attrs={
995+
'ports': [ext_gw_lrp, internal_net_lrp]})
996+
997+
self.fake_ovn_client._l3_plugin.get_routers.return_value = routers_db
998+
nb_idl.get_lrouter.return_value = fake_router
999+
self.assertRaises(
1000+
periodics.NeverAgain,
1001+
self.periodic.add_gw_port_info_to_logical_router_port)
1002+
nb_idl.update_lrouter_port.assert_not_called()
1003+
9401004
def test_check_router_default_route_empty_dst_ip(self):
9411005
nb_idl = self.fake_ovn_client._nb_idl
9421006
route0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(

neutron/tests/unit/services/ovn_l3/test_plugin.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def setUp(self):
9393
ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'subnet-id',
9494
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
9595
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
96-
utils.ovn_name(self.fake_network['id'])}}
96+
utils.ovn_name(self.fake_network['id']),
97+
ovn_const.OVN_ROUTER_IS_EXT_GW: 'False'}}
9798
self.fake_router_ports = [self.fake_router_port]
9899
self.fake_subnet = {'id': 'subnet-id',
99100
'ip_version': 4,
@@ -149,7 +150,8 @@ def setUp(self):
149150
ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'ext-subnet-id',
150151
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
151152
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
152-
utils.ovn_name(self.fake_network['id'])},
153+
utils.ovn_name(self.fake_network['id']),
154+
ovn_const.OVN_ROUTER_IS_EXT_GW: 'True'},
153155
'gateway_chassis': ['hv1'],
154156
'options': {}}
155157
self.fake_floating_ip_attrs = {'floating_ip_address': '192.168.0.10',
@@ -404,7 +406,8 @@ def test_remove_router_interface_update_lrouter_port(self):
404406
ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'subnet-id',
405407
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
406408
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
407-
utils.ovn_name(self.fake_network['id'])})
409+
utils.ovn_name(self.fake_network['id']),
410+
ovn_const.OVN_ROUTER_IS_EXT_GW: 'False'})
408411

409412
def test_remove_router_interface_router_not_found(self):
410413
router_id = 'router-id'
@@ -1631,6 +1634,8 @@ def test_add_router_interface_need_to_frag_enabled(
16311634
fake_router_port_assert['options'] = {
16321635
ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION:
16331636
str(prov_net['mtu'])}
1637+
fake_router_port_assert['external_ids'][
1638+
ovn_const.OVN_ROUTER_IS_EXT_GW] = 'True'
16341639

16351640
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
16361641
**fake_router_port_assert)
@@ -1678,6 +1683,8 @@ def test_add_router_interface_need_to_frag_enabled_then_remove(
16781683
fake_router_port_assert = self.fake_router_port_assert
16791684
fake_router_port_assert['gateway_chassis'] = mock.ANY
16801685
fake_router_port_assert['options'] = {}
1686+
fake_router_port_assert['external_ids'][
1687+
ovn_const.OVN_ROUTER_IS_EXT_GW] = 'True'
16811688

16821689
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
16831690
**fake_router_port_assert)

neutron/tests/unit/services/portforwarding/drivers/ovn/test_driver.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,48 @@ def test_port_forwarding_deleted(self, m_info):
450450
self.l3_plugin._nb_ovn.lb_del.assert_called_once_with(
451451
exp_lb_name, exp_vip, if_exists=mock.ANY)
452452

453+
@mock.patch.object(port_forwarding.LOG, 'warning')
454+
def test__validate_router_networks_provider_networks(self, mock_warning):
455+
lr_ports = [
456+
mock.MagicMock(external_ids={
457+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-ext-net-id',
458+
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'True'}),
459+
mock.MagicMock(external_ids={
460+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-net-id',
461+
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'False'})]
462+
lr_mock = mock.MagicMock(ports=lr_ports)
463+
ovn_nb_mock = mock.Mock()
464+
ovn_nb_mock.get_lrouter.return_value = lr_mock
465+
with mock.patch.object(
466+
ovn_conf, 'is_ovn_distributed_floating_ip',
467+
return_value=True), mock.patch.object(
468+
ovn_utils, 'is_provider_network',
469+
return_value=True):
470+
self.handler._validate_router_networks(
471+
ovn_nb_mock, 'router-id')
472+
self.assertEqual(1, mock_warning.call_count)
473+
474+
@mock.patch.object(port_forwarding.LOG, 'warning')
475+
def test__validate_router_networks_tunnel_networks(self, mock_warning):
476+
lr_ports = [
477+
mock.MagicMock(external_ids={
478+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-ext-net-id',
479+
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'True'}),
480+
mock.MagicMock(external_ids={
481+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-net-id',
482+
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'False'})]
483+
lr_mock = mock.MagicMock(ports=lr_ports)
484+
ovn_nb_mock = mock.Mock()
485+
ovn_nb_mock.get_lrouter.return_value = lr_mock
486+
with mock.patch.object(
487+
ovn_conf, 'is_ovn_distributed_floating_ip',
488+
return_value=True), mock.patch.object(
489+
ovn_utils, 'is_provider_network',
490+
return_value=False):
491+
self.handler._validate_router_networks(
492+
ovn_nb_mock, 'router-id')
493+
mock_warning.assert_not_called()
494+
453495

454496
class TestOVNPortForwarding(TestOVNPortForwardingBase):
455497
def setUp(self):

0 commit comments

Comments
 (0)