Skip to content

Commit c246913

Browse files
committed
Ensure redirect-type=bridged not used for geneve networks
As part of [1] the redirect-type=bridged flag was enabled by default. However this have the side effect of also decentralizing N/S traffic for geneve tenant networks, breaking the VM connectivity on them when it must be centralized, i.e., when no FIPs are associated to the VMs. This patch differentiates and only enable that flag when the networks conected through that router gateway port are of VLAN/FLAT type. In addition, to avoid MTU issues for the VLAN networks if there are also geneve networks connected to the same router, we re-take the approach on [2] to ensure the traffic is centralized but not tunneled [1] https://review.opendev.org/c/openstack/neutron/+/875644 [2] https://review.opendev.org/c/openstack/neutron/+/875676 Closes-Bug: #2012712 Conflicts: neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py Change-Id: I25e5ee2cf8daee52221a640faa7ac09679742707 (cherry picked from commit 0ec04dd)
1 parent 9bbb97f commit c246913

File tree

5 files changed

+227
-19
lines changed

5 files changed

+227
-19
lines changed

neutron/common/ovn/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@
372372
LSP_OPTIONS_MCAST_FLOOD = 'mcast_flood'
373373

374374
LRP_OPTIONS_RESIDE_REDIR_CH = 'reside-on-redirect-chassis'
375+
LRP_OPTIONS_REDIRECT_TYPE = 'redirect-type'
376+
BRIDGE_REDIRECT_TYPE = "bridged"
375377

376378
# Port Binding types
377379
PB_TYPE_VIRTUAL = 'virtual'

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from neutron_lib import context as n_context
2828
from neutron_lib.db import api as db_api
2929
from neutron_lib import exceptions as n_exc
30+
from neutron_lib.exceptions import l3 as l3_exc
3031
from oslo_config import cfg
3132
from oslo_log import log
3233
from oslo_utils import timeutils
@@ -816,6 +817,66 @@ def update_port_qos_with_external_ids_reference(self):
816817
txn.add(cmd)
817818
raise periodics.NeverAgain()
818819

820+
# A static spacing value is used here, but this method will only run
821+
# once per lock due to the use of periodics.NeverAgain().
822+
@periodics.periodic(spacing=600, run_immediately=True)
823+
def check_redirect_type_router_gateway_ports(self):
824+
"""Check OVN router gateway ports
825+
Check for the option "redirect-type=bridged" value for
826+
router gateway ports.
827+
"""
828+
if not self.has_lock:
829+
return
830+
context = n_context.get_admin_context()
831+
cmds = []
832+
gw_ports = self._ovn_client._plugin.get_ports(
833+
context, {'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]})
834+
for gw_port in gw_ports:
835+
enable_redirect = False
836+
if ovn_conf.is_ovn_distributed_floating_ip():
837+
try:
838+
r_ports = self._ovn_client._get_router_ports(
839+
context, gw_port['device_id'])
840+
except l3_exc.RouterNotFound:
841+
LOG.debug("No Router %s not found", gw_port['device_id'])
842+
continue
843+
else:
844+
network_ids = {port['network_id'] for port in r_ports}
845+
networks = self._ovn_client._plugin.get_networks(
846+
context, filters={'id': network_ids})
847+
# NOTE(ltomasbo): For VLAN type networks connected through
848+
# the gateway port there is a need to set the redirect-type
849+
# option to bridge to ensure traffic is not centralized
850+
# through the controller.
851+
# If there are no VLAN type networks attached we need to
852+
# still make it centralized.
853+
if networks:
854+
enable_redirect = all(
855+
net.get(pnet.NETWORK_TYPE) in [n_const.TYPE_VLAN,
856+
n_const.TYPE_FLAT]
857+
for net in networks)
858+
859+
lrp_name = utils.ovn_lrouter_port_name(gw_port['id'])
860+
lrp = self._nb_idl.get_lrouter_port(lrp_name)
861+
redirect_value = lrp.options.get(
862+
ovn_const.LRP_OPTIONS_REDIRECT_TYPE)
863+
if enable_redirect:
864+
if redirect_value != ovn_const.BRIDGE_REDIRECT_TYPE:
865+
opt = {ovn_const.LRP_OPTIONS_REDIRECT_TYPE:
866+
ovn_const.BRIDGE_REDIRECT_TYPE}
867+
cmds.append(self._nb_idl.db_set(
868+
'Logical_Router_Port', lrp_name, ('options', opt)))
869+
else:
870+
if redirect_value == ovn_const.BRIDGE_REDIRECT_TYPE:
871+
cmds.append(self._nb_idl.db_remove(
872+
'Logical_Router_Port', lrp_name, 'options',
873+
(ovn_const.LRP_OPTIONS_REDIRECT_TYPE)))
874+
if cmds:
875+
with self._nb_idl.transaction(check_error=True) as txn:
876+
for cmd in cmds:
877+
txn.add(cmd)
878+
raise periodics.NeverAgain()
879+
819880
# A static spacing value is used here, but this method will only run
820881
# once per lock due to the use of periodics.NeverAgain().
821882
@periodics.periodic(spacing=600, run_immediately=True)
@@ -838,9 +899,11 @@ def check_vlan_distributed_ports(self):
838899
router_ports = self._ovn_client._plugin.get_ports(
839900
context, {'network_id': vlan_net_ids,
840901
'device_owner': n_const.ROUTER_PORT_OWNERS})
841-
expected_value = ('false' if ovn_conf.is_ovn_distributed_floating_ip()
842-
else 'true')
902+
843903
for rp in router_ports:
904+
expected_value = (
905+
self._ovn_client._get_reside_redir_for_gateway_port(
906+
rp['device_id']))
844907
lrp_name = utils.ovn_lrouter_port_name(rp['id'])
845908
lrp = self._nb_idl.get_lrouter_port(lrp_name)
846909
if lrp.options.get(

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

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,11 @@ def update_router_routes(self, context, router_id, add, remove,
13171317
nexthop=route['nexthop']))
13181318
self._transaction(commands, txn=txn)
13191319

1320+
def _get_router_gw_ports(self, context, router_id):
1321+
return self._plugin.get_ports(context, filters={
1322+
'device_owner': [const.DEVICE_OWNER_ROUTER_GW],
1323+
'device_id': [router_id]})
1324+
13201325
def _get_router_ports(self, context, router_id, get_gw_port=False):
13211326
# _get_router() will raise a RouterNotFound error if there's no router
13221327
# with the router_id
@@ -1548,6 +1553,31 @@ def _gen_router_port_ext_ids(self, port):
15481553

15491554
return ext_ids
15501555

1556+
def _get_reside_redir_for_gateway_port(self, device_id):
1557+
admin_context = n_context.get_admin_context()
1558+
reside_redir_ch = 'true'
1559+
if ovn_conf.is_ovn_distributed_floating_ip():
1560+
reside_redir_ch = 'false'
1561+
try:
1562+
router_ports = self._get_router_ports(admin_context, device_id)
1563+
except l3_exc.RouterNotFound:
1564+
LOG.debug("No Router %s not found", device_id)
1565+
else:
1566+
network_ids = {port['network_id'] for port in router_ports}
1567+
networks = self._plugin.get_networks(
1568+
admin_context, filters={'id': network_ids})
1569+
1570+
# NOTE(ltomasbo): not all the networks connected to the router
1571+
# are of vlan type, so we won't set the redirect-type=bridged
1572+
# on the router gateway port, therefore we need to centralized
1573+
# the vlan traffic to avoid tunneling
1574+
if networks:
1575+
reside_redir_ch = 'true' if any(
1576+
net.get(pnet.NETWORK_TYPE) not in [const.TYPE_VLAN,
1577+
const.TYPE_FLAT]
1578+
for net in networks) else 'false'
1579+
return reside_redir_ch
1580+
15511581
def _gen_router_port_options(self, port, network=None):
15521582
options = {}
15531583
admin_context = n_context.get_admin_context()
@@ -1562,14 +1592,15 @@ def _gen_router_port_options(self, port, network=None):
15621592
# FIXME(ltomasbo): Once Bugzilla 2162756 is fixed the
15631593
# is_provider_network check should be removed
15641594
if network.get(pnet.NETWORK_TYPE) == const.TYPE_VLAN:
1565-
options[ovn_const.LRP_OPTIONS_RESIDE_REDIR_CH] = (
1566-
'false' if (ovn_conf.is_ovn_distributed_floating_ip() and
1567-
not utils.is_provider_network(network))
1568-
else 'true')
1595+
reside_redir_ch = self._get_reside_redir_for_gateway_port(
1596+
port['device_id'])
1597+
options[ovn_const.LRP_OPTIONS_RESIDE_REDIR_CH] = reside_redir_ch
15691598

15701599
is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
15711600
'device_owner')
1572-
if is_gw_port and ovn_conf.is_ovn_emit_need_to_frag_enabled():
1601+
1602+
if is_gw_port and (ovn_conf.is_ovn_distributed_floating_ip() or
1603+
ovn_conf.is_ovn_emit_need_to_frag_enabled()):
15731604
try:
15741605
router_ports = self._get_router_ports(admin_context,
15751606
port['device_id'])
@@ -1578,12 +1609,32 @@ def _gen_router_port_options(self, port, network=None):
15781609
LOG.debug("Router %s not found", port['device_id'])
15791610
else:
15801611
network_ids = {port['network_id'] for port in router_ports}
1581-
for net in self._plugin.get_networks(admin_context,
1582-
filters={'id': network_ids}):
1583-
if net['mtu'] > network['mtu']:
1584-
options[ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION] = str(
1585-
network['mtu'])
1586-
break
1612+
networks = self._plugin.get_networks(
1613+
admin_context, filters={'id': network_ids})
1614+
if ovn_conf.is_ovn_emit_need_to_frag_enabled():
1615+
for net in networks:
1616+
if net['mtu'] > network['mtu']:
1617+
options[
1618+
ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION] = str(
1619+
network['mtu'])
1620+
break
1621+
if ovn_conf.is_ovn_distributed_floating_ip():
1622+
# NOTE(ltomasbo): For VLAN type networks connected through
1623+
# the gateway port there is a need to set the redirect-type
1624+
# option to bridge to ensure traffic is not centralized
1625+
# through the controller.
1626+
# If there are no VLAN type networks attached we need to
1627+
# still make it centralized.
1628+
enable_redirect = False
1629+
if networks:
1630+
enable_redirect = all(
1631+
net.get(pnet.NETWORK_TYPE) in [const.TYPE_VLAN,
1632+
const.TYPE_FLAT]
1633+
for net in networks)
1634+
if enable_redirect:
1635+
options[ovn_const.LRP_OPTIONS_REDIRECT_TYPE] = (
1636+
ovn_const.BRIDGE_REDIRECT_TYPE)
1637+
15871638
return options
15881639

15891640
def _create_lrouter_port(self, context, router, port, txn=None):
@@ -1664,6 +1715,12 @@ def create_router_port(self, context, router_id, router_interface):
16641715
if utils.is_snat_enabled(router) and cidr:
16651716
self.update_nat_rules(router, networks=[cidr],
16661717
enable_snat=True, txn=txn)
1718+
if ovn_conf.is_ovn_distributed_floating_ip():
1719+
router_gw_ports = self._get_router_gw_ports(context,
1720+
router_id)
1721+
for router_port in router_gw_ports:
1722+
self._update_lrouter_port(context, router_port,
1723+
txn=txn)
16671724

16681725
db_rev.bump_revision(context, port, ovn_const.TYPE_ROUTER_PORTS)
16691726

@@ -1804,6 +1861,11 @@ def delete_router_port(self, context, port_id, router_id=None,
18041861
self.update_nat_rules(
18051862
router, networks=[cidr], enable_snat=False, txn=txn)
18061863

1864+
if ovn_conf.is_ovn_distributed_floating_ip():
1865+
router_gw_ports = self._get_router_gw_ports(context, router_id)
1866+
for router_port in router_gw_ports:
1867+
self._update_lrouter_port(context, router_port, txn=txn)
1868+
18071869
# NOTE(mangelajo): If the port doesn't exist anymore, we
18081870
# delete the router port as the last operation and update the
18091871
# revision database to ensure consistency

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

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from unittest import mock
1717

1818
from futurist import periodics
19+
from neutron_lib import constants as n_const
1920
from neutron_lib import context
2021
from neutron_lib.db import api as db_api
2122
from oslo_config import cfg
@@ -622,16 +623,76 @@ def test_update_port_qos_with_external_ids_reference(self):
622623
('external_ids', external_ids))]
623624
nb_idl.db_set.assert_has_calls(expected_calls)
624625

626+
def _test_check_redirect_type_router_gateway_ports(self, networks,
627+
redirect_value):
628+
self.fake_ovn_client._plugin.get_ports.return_value = [{
629+
'device_owner': n_const.DEVICE_OWNER_ROUTER_GW,
630+
'id': 'fake-id',
631+
'device_id': 'fake-device-id'}]
632+
self.fake_ovn_client._get_router_ports.return_value = []
633+
self.fake_ovn_client._plugin.get_networks.return_value = networks
634+
635+
lrp_redirect = fakes.FakeOvsdbRow.create_one_ovsdb_row(
636+
attrs={
637+
'options': {constants.LRP_OPTIONS_REDIRECT_TYPE: "bridged"}})
638+
lrp_no_redirect = fakes.FakeOvsdbRow.create_one_ovsdb_row(
639+
attrs={
640+
'options': {}})
641+
642+
# set the opossite so that the value is changed
643+
if redirect_value:
644+
self.fake_ovn_client._nb_idl.get_lrouter_port.return_value = (
645+
lrp_no_redirect)
646+
else:
647+
self.fake_ovn_client._nb_idl.get_lrouter_port.return_value = (
648+
lrp_redirect)
649+
650+
self.assertRaises(
651+
periodics.NeverAgain,
652+
self.periodic.check_redirect_type_router_gateway_ports)
653+
654+
if redirect_value:
655+
expected_calls = [
656+
mock.call.db_set('Logical_Router_Port',
657+
mock.ANY,
658+
('options', {'redirect-type': 'bridged'}))
659+
]
660+
self.fake_ovn_client._nb_idl.db_set.assert_has_calls(
661+
expected_calls)
662+
else:
663+
expected_calls = [
664+
mock.call.db_remove('Logical_Router_Port', mock.ANY,
665+
'options', 'redirect-type')
666+
]
667+
self.fake_ovn_client._nb_idl.db_remove.assert_has_calls(
668+
expected_calls)
669+
670+
def test_check_redirect_type_router_gateway_ports_enable_redirect(self):
671+
cfg.CONF.set_override('enable_distributed_floating_ip', 'True',
672+
group='ovn')
673+
networks = [{'network_id': 'foo',
674+
'provider:network_type': n_const.TYPE_VLAN}]
675+
self._test_check_redirect_type_router_gateway_ports(networks, True)
676+
677+
def test_check_redirect_type_router_gateway_ports_disable_redirect(self):
678+
cfg.CONF.set_override('enable_distributed_floating_ip', 'True',
679+
group='ovn')
680+
networks = [{'network_id': 'foo',
681+
'provider:network_type': n_const.TYPE_GENEVE}]
682+
self._test_check_redirect_type_router_gateway_ports(networks, False)
683+
625684
def _test_check_vlan_distributed_ports(self, opt_value=None):
626685
fake_net0 = {'id': 'net0'}
627686
fake_net1 = {'id': 'net1'}
628-
fake_port0 = {'id': 'port0'}
629-
fake_port1 = {'id': 'port1'}
687+
fake_port0 = {'id': 'port0', 'device_id': 'device0'}
688+
fake_port1 = {'id': 'port1', 'device_id': 'device1'}
630689

631690
self.fake_ovn_client._plugin.get_networks.return_value = [
632691
fake_net0, fake_net1]
633692
self.fake_ovn_client._plugin.get_ports.return_value = [
634693
fake_port0, fake_port1]
694+
(self.fake_ovn_client._get_reside_redir_for_gateway_port
695+
.return_value) = 'true'
635696

636697
fake_lrp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
637698
attrs={
@@ -645,8 +706,6 @@ def _test_check_vlan_distributed_ports(self, opt_value=None):
645706
self.periodic.check_vlan_distributed_ports)
646707

647708
def test_check_vlan_distributed_ports_expected_value(self):
648-
cfg.CONF.set_override('enable_distributed_floating_ip', 'False',
649-
group='ovn')
650709
self._test_check_vlan_distributed_ports(opt_value='true')
651710

652711
# If the "reside-on-redirect-chassis" option value do match
@@ -655,8 +714,6 @@ def test_check_vlan_distributed_ports_expected_value(self):
655714
self.fake_ovn_client._nb_idl.db_set.called)
656715

657716
def test_check_vlan_distributed_ports_non_expected_value(self):
658-
cfg.CONF.set_override('enable_distributed_floating_ip', 'False',
659-
group='ovn')
660717
self._test_check_vlan_distributed_ports(opt_value='false')
661718

662719
# If the "reside-on-redirect-chassis" option value does not match
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
issues:
3+
- |
4+
The `redirect-type=bridged` option is only used if all the tenant networks
5+
connected to the router are of type VLAN or FLAT. In this case their
6+
traffic will be distributed. However, if there is a mix of VLAN/FLAT and
7+
geneve networks connected to the same router, the redirect-type option is
8+
not set, and therefore the traffic for the VLAN/FLAT networks will also be
9+
centralized but not tunneled.
10+
fixes:
11+
- |
12+
[`bug 2003455 <https://bugs.launchpad.net/neutron/+bug/2003455>`_]
13+
As part of a previous commit
14+
(https://review.opendev.org/c/openstack/neutron/+/875644) the
15+
`redirect-type=bridged` option was set in all the router gateway ports
16+
(cr-lrp ovn ports). However this was breaking the N/S traffic for geneve
17+
tenant networks connected to the provider networks through those routers
18+
with the redirect-type option enabled. To fix this we ensure that the
19+
redirect-type option is only set if all the networks connected to the
20+
router are of VLAN or FLAT type, otherwise we fall back to the default
21+
option. This also means that if there is a mix of VLAN and geneve tenant
22+
networks connected to the same router, the VLAN traffic will be centralized
23+
(but not tunneled). If the traffic for the VLAN/FLAT needs to be
24+
distributed, then it should use a different router.

0 commit comments

Comments
 (0)