[OVN][QOS] QoS for distributed floating IP

When floating IP is distributed in OVN, the NAT is done locally
in the same host as the private IP port (same chassis). The QoS
rule match should have a local chassis port to apply the rule;
in this case, the mentioned private IP port UUID will match the
condition "is_chassis_resident(port_id)".

In the case the floating IP is not distributed, the current behaviour
is valid.

Change-Id: Ica5eae22bc989965e8a7e7595bc7be02d9f65640
Closes-Bug: #1915723
This commit is contained in:
Rodolfo Alonso Hernandez 2021-02-15 13:39:26 +00:00
parent 4a021306ad
commit 9079bba00a
2 changed files with 29 additions and 12 deletions

View File

@ -26,6 +26,7 @@ from oslo_log import log as logging
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
LOG = logging.getLogger(__name__)
@ -91,7 +92,7 @@ class OVNClientQosExtension(object):
return qos_rules
@staticmethod
def _ovn_qos_rule_match(direction, port_id, ip_address):
def _ovn_qos_rule_match(direction, port_id, ip_address, resident_port):
if direction == constants.EGRESS_DIRECTION:
in_or_out = 'inport'
src_or_dst = 'src'
@ -100,15 +101,15 @@ class OVNClientQosExtension(object):
src_or_dst = 'dst'
match = '%s == "%s"' % (in_or_out, port_id)
if ip_address:
if ip_address and resident_port:
match += (' && ip4.%s == %s && is_chassis_resident("%s")' %
(src_or_dst, ip_address,
utils.ovn_cr_lrouter_port_name(port_id)))
(src_or_dst, ip_address, resident_port))
return match
def _ovn_qos_rule(self, rules_direction, rules, port_id, network_id,
fip_id=None, ip_address=None, delete=False):
fip_id=None, ip_address=None, resident_port=None,
delete=False):
"""Generate an OVN QoS register based on several Neutron QoS rules
A OVN QoS register can contain "bandwidth" and "action" parameters.
@ -129,6 +130,9 @@ class OVNClientQosExtension(object):
limit.
:param ip_address: (string) IP address, for L3 floating IP bandwidth
limit.
:param resident_port: (string) for L3 floating IP bandwidth, this is
a logical switch port located in the chassis
where the floating IP traffic is NATed.
:param delete: (bool) defines if this rule if going to be a partial
one (without any bandwidth or DSCP information) to be
used only as deletion rule.
@ -142,7 +146,8 @@ class OVNClientQosExtension(object):
direction = (
'from-lport' if rules_direction == constants.EGRESS_DIRECTION else
'to-lport')
match = self._ovn_qos_rule_match(rules_direction, port_id, ip_address)
match = self._ovn_qos_rule_match(rules_direction, port_id, ip_address,
resident_port)
ovn_qos_rule = {'switch': lswitch_name, 'direction': direction,
'priority': OVN_QOS_DEFAULT_RULE_PRIORITY,
@ -286,12 +291,20 @@ class OVNClientQosExtension(object):
if not gw_port_id:
return
if ovn_conf.is_ovn_distributed_floating_ip():
# DVR, floating IP GW is in the same compute node as private port.
resident_port = floatingip['port_id']
else:
# Non-DVR, floating IP GW is located where chassisredirect lrp is.
resident_port = utils.ovn_cr_lrouter_port_name(gw_port_id)
qos_rules = self._qos_rules(admin_context, qos_policy_id)
for direction, rules in qos_rules.items():
ovn_rule = self._ovn_qos_rule(
direction, rules, gw_port_id,
floatingip['floating_network_id'], fip_id=floatingip['id'],
ip_address=floatingip['floating_ip_address'])
ip_address=floatingip['floating_ip_address'],
resident_port=resident_port)
if ovn_rule:
txn.add(self._driver._nb_idl.qos_add(**ovn_rule))

View File

@ -62,6 +62,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
def setUp(self):
cfg.CONF.set_override('extension_drivers', self._extension_drivers,
group='ml2')
cfg.CONF.set_override('enable_distributed_floating_ip', 'False',
group='ovn')
extensions.register_custom_supported_check(qos_api.ALIAS, lambda: True,
plugin_agnostic=True)
super(TestOVNClientQosExtension, self).setUp()
@ -188,7 +190,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
direction = constants.INGRESS_DIRECTION
rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1}
match = self.qos_driver._ovn_qos_rule_match(
direction, 'port_id', ip_address)
direction, 'port_id', ip_address, 'resident_port')
expected = {'burst': 100, 'rate': 200, 'direction': 'to-lport',
'match': match,
'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY,
@ -197,7 +199,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
expected['external_ids'] = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id}
result = self.qos_driver._ovn_qos_rule(
direction, rule, 'port_id', 'network_id', fip_id=fip_id,
ip_address=ip_address)
ip_address=ip_address, resident_port='resident_port')
self.assertEqual(expected, result)
def test__ovn_qos_rule_ingress(self):
@ -210,14 +212,15 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
direction = constants.EGRESS_DIRECTION
rule = {qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1}
match = self.qos_driver._ovn_qos_rule_match(
direction, 'port_id', ip_address)
direction, 'port_id', ip_address, 'resident_port')
expected = {'direction': 'from-lport', 'match': match,
'dscp': 16, 'switch': 'neutron-network_id',
'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY}
if fip_id:
expected['external_ids'] = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id}
result = self.qos_driver._ovn_qos_rule(
direction, rule, 'port_id', 'network_id', fip_id, ip_address)
direction, rule, 'port_id', 'network_id', fip_id=fip_id,
ip_address=ip_address, resident_port='resident_port')
self.assertEqual(expected, result)
rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2,
@ -228,7 +231,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
if fip_id:
expected['external_ids'] = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id}
result = self.qos_driver._ovn_qos_rule(
direction, rule, 'port_id', 'network_id', fip_id, ip_address)
direction, rule, 'port_id', 'network_id', fip_id=fip_id,
ip_address=ip_address, resident_port='resident_port')
self.assertEqual(expected, result)
def test__ovn_qos_rule_egress(self):