Skip to content

Commit 63ba2c9

Browse files
committed
Use the OVN policer for max-bw only QoS policies
As reported in the related bug, when [1] was implemented, that introduced a regression when a port located in a physical network, with maximum bandwidth *only* QoS rules and egress direction, was bound to a compute node with a bond interface on the physical network bridge. The TC commands have no effect on the interfaces of the bond. This patch restores the backend enforcement for this scenario (physical networks, egress direction, maximum bandwidth rule only) to the OVN policer, using the OVN ``QoS`` registers, instead of relying on the physical interface bandwidth enforcement, using TC commands. [1]https://review.opendev.org/c/openstack/neutron/+/934418 Closes-Bug: #2115952 Signed-off-by: Rodolfo Alonso Hernandez <[email protected]> Change-Id: I7bf4dc396a044d0d67d2d2f6e68140c063f3e3d8 (cherry picked from commit 4ebd69f)
1 parent 62606a0 commit 63ba2c9

File tree

4 files changed

+132
-33
lines changed

4 files changed

+132
-33
lines changed

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,25 @@ def _add_port_qos_rules(self, txn, port_id, network_id, network_type,
348348
_qos_rules = (copy.deepcopy(qos_rules) if qos_rules else
349349
self._qos_rules(admin_context, qos_policy_id))
350350
for direction, rules in _qos_rules.items():
351+
min_bw = rules.get(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH)
352+
# NOTE(ralonsoh): the QoS rules are defined in the LSP.options
353+
# dictionary if (1) direction=egress, (2) the network is physical
354+
# and (3) there are min-bw rules. Otherwise, the OVN QoS registers
355+
# are used (OVN BW policer).
351356
if (network_type in TYPE_PHYSICAL and
352357
direction == constants.EGRESS_DIRECTION):
353-
ovn_rule_lsp = self._ovn_lsp_rule(rules)
354-
self._update_lsp_qos_options(txn, lsp, port_id, ovn_rule_lsp)
355-
# In this particular case, the QoS rules should be defined in
356-
# LSP.options. Only DSCP rule will create a QoS entry.
357-
rules.pop(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, None)
358-
rules.pop(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, None)
358+
if min_bw:
359+
ovn_rule_lsp = self._ovn_lsp_rule(rules)
360+
self._update_lsp_qos_options(txn, lsp, port_id,
361+
ovn_rule_lsp)
362+
# In this particular case, the QoS rules should be defined
363+
# in LSP.options. Only DSCP rule will create a QoS entry.
364+
rules.pop(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, None)
365+
rules.pop(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, None)
366+
else:
367+
# Clear the LSP.options QoS rules.
368+
self._update_lsp_qos_options(txn, lsp, port_id,
369+
self._ovn_lsp_rule({}))
359370

360371
ovn_rule_qos = self._ovn_qos_rule(direction, rules, port_id,
361372
network_id)

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@
6969
qos_constants.RULE_TYPE_MINIMUM_BANDWIDTH: QOS_RULE_MINBW_1}
7070
}
7171

72+
QOS_RULES_5 = {
73+
constants.EGRESS_DIRECTION: {
74+
qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1,
75+
qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1,
76+
qos_constants.RULE_TYPE_MINIMUM_BANDWIDTH: QOS_RULE_MINBW_1},
77+
}
78+
7279

7380
class _TestOVNClientQosExtensionBase(base.TestOVNFunctionalBase):
7481
def setUp(self, maintenance_worker=False):
@@ -78,7 +85,9 @@ def setUp(self, maintenance_worker=False):
7885
def _check_rules_qos(self, rules, port_id, network_id, network_type,
7986
fip_id=None, ip_address=None, expected_ext_ids=None):
8087
qos_rules = copy.deepcopy(rules)
81-
if network_type in (constants.TYPE_VLAN, constants.TYPE_FLAT):
88+
min_bw = qos_rules.get(constants.EGRESS_DIRECTION, {}).get(
89+
qos_constants.RULE_TYPE_MINIMUM_BANDWIDTH)
90+
if network_type in constants.TYPE_PHYSICAL and min_bw:
8291
# Remove the egress max-rate and min-rate rules, these are defined
8392
# in the LSP.options field for a physical network.
8493
try:
@@ -135,12 +144,13 @@ def _check_rules_qos(self, rules, port_id, network_id, network_type,
135144
self.assertEqual(bandwidth, rule.bandwidth)
136145

137146
def _check_rules_lsp(self, rules, port_id, network_type):
138-
if network_type not in (constants.TYPE_VLAN, constants.TYPE_FLAT):
147+
egress_rules = rules.get(constants.EGRESS_DIRECTION, {})
148+
min_bw = egress_rules.get(qos_constants.RULE_TYPE_MINIMUM_BANDWIDTH)
149+
if not (network_type in constants.TYPE_PHYSICAL and min_bw):
139150
return
140151

141152
# If there are no egress rules, it is checked that there are no
142153
# QoS parameters in the LSP.options dictionary.
143-
egress_rules = rules.get(constants.EGRESS_DIRECTION, {})
144154
qos_rule_lsp = self.qos_driver._ovn_lsp_rule(egress_rules)
145155
lsp = self.qos_driver.nb_idl.lsp_get(port_id).execute(
146156
check_error=True)
@@ -267,6 +277,62 @@ def test_update_policy(self):
267277
constants.TYPE_VLAN)
268278
self._check_rules_lsp(_qos_rules, port['id'], constants.TYPE_VLAN)
269279

280+
def test_set_and_update_physical_network_qos(self):
281+
# The goal of this test is to check how the OVN QoS registers and
282+
# LSP.options are set and deleted, depending on the QoS policy rules.
283+
# Check LP#2115952 for more information.
284+
port = uuidutils.generate_uuid()
285+
self._add_logical_switch_port(port)
286+
287+
def _apply_rules(qos_rules):
288+
with self.nb_api.transaction(check_error=True) as txn:
289+
_qos_rules = copy.deepcopy(qos_rules)
290+
for direction in constants.VALID_DIRECTIONS:
291+
_qos_rules[direction] = _qos_rules.get(direction, {})
292+
self.mock_qos_rules.return_value = copy.deepcopy(_qos_rules)
293+
self.qos_driver._update_port_qos_rules(
294+
txn, port, self.network_1, constants.TYPE_VLAN, 'qos1',
295+
None)
296+
297+
# Loop this test twice, to check that all the QoS registers and
298+
# parameters are correctly created, set or removed, regardless of the
299+
# previous state.
300+
for _ in range(2):
301+
# Apply QOS_RULES_5: egress with max-bw, min-bw and DSCP rules.
302+
# * Check the OVN QoS rule created has only DSCP information.
303+
# * Check the LSP.options have the correct fields.
304+
_apply_rules(QOS_RULES_5)
305+
lsp = self.qos_driver.nb_idl.lsp_get(port).execute(
306+
check_error=True)
307+
for _param in ('qos_max_rate', 'qos_burst', 'qos_min_rate'):
308+
self.assertIn(_param, lsp.options)
309+
ls = self.qos_driver.nb_idl.lookup(
310+
'Logical_Switch', ovn_utils.ovn_name(self.network_1))
311+
self.assertEqual(1, len(ls.qos_rules))
312+
rule = ls.qos_rules[0]
313+
self.assertIn(port, rule.match)
314+
self.assertEqual({'dscp': QOS_RULE_DSCP_1['dscp_mark']},
315+
rule.action)
316+
self.assertEqual({}, rule.bandwidth)
317+
318+
# Apply QOS_RULES_3: egress with max-bw only rule.
319+
# * Check the OVN QoS rule created has only max-bw information.
320+
# * Check the LSP.options has no QoS information.
321+
_apply_rules(QOS_RULES_3)
322+
lsp = self.qos_driver.nb_idl.lsp_get(port).execute(
323+
check_error=True)
324+
for _param in ('qos_max_rate', 'qos_burst', 'qos_min_rate'):
325+
self.assertNotIn(_param, lsp.options)
326+
ls = self.qos_driver.nb_idl.lookup(
327+
'Logical_Switch', ovn_utils.ovn_name(self.network_1))
328+
self.assertEqual(1, len(ls.qos_rules))
329+
rule = ls.qos_rules[0]
330+
self.assertIn(port, rule.match)
331+
self.assertEqual({}, rule.action)
332+
self.assertEqual({'burst': QOS_RULE_BW_1['max_burst_kbps'],
333+
'rate': QOS_RULE_BW_1['max_kbps']},
334+
rule.bandwidth)
335+
270336

271337
class TestOVNClientQosExtensionEndToEnd(_TestOVNClientQosExtensionBase):
272338

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_client.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,13 @@ def test_create_router_port_multiple_routers(self):
386386
# GW network MTU=1400
387387
self.assertEqual(1400, int(lrp.options['gateway_mtu']))
388388

389-
def test_update_port_with_qos(self):
390-
def _check_bw(port_id, max_kbps=None, max_burst_kbps=None):
389+
def test_update_port_with_qos_in_lsp_options(self):
390+
# The goal of this test is to check that a Neutron port that has a
391+
# QoS policy defined in the LSP.options (physical network, egress
392+
# rules, max+min BW rules) is correctly updated and the LSP.options
393+
# are defined when the Neutron port is updated (see LP#2106231).
394+
def _check_bw(port_id, max_kbps=None, max_burst_kbps=None,
395+
min_kbps=None):
391396
lsp = self.nb_api.lookup('Logical_Switch_Port', port_id)
392397
if max_kbps:
393398
self.assertEqual(
@@ -403,37 +408,47 @@ def _check_bw(port_id, max_kbps=None, max_burst_kbps=None):
403408
else:
404409
self.assertNotIn(ovn_const.LSP_OPTIONS_QOS_BURST,
405410
lsp.options)
411+
if min_kbps:
412+
self.assertEqual(
413+
'{}'.format(min_kbps * 1000),
414+
lsp.options[ovn_const.LSP_OPTIONS_QOS_MIN_RATE])
415+
else:
416+
self.assertNotIn(ovn_const.LSP_OPTIONS_QOS_MIN_RATE,
417+
lsp.options)
406418

407419
res = self._create_qos_policy(self.fmt, is_admin=True)
408420
qos = self.deserialize(self.fmt, res)['policy']
409-
max_kbps, max_burst_kbps = 1000, 800
421+
max_kbps, max_burst_kbps, min_kbps = 1000, 800, 600
422+
410423
self._create_qos_rule(self.fmt, qos['id'],
411424
qos_const.RULE_TYPE_BANDWIDTH_LIMIT,
412425
max_kbps=max_kbps, max_burst_kbps=max_burst_kbps,
413426
is_admin=True)
427+
self._create_qos_rule(self.fmt, qos['id'],
428+
qos_const.RULE_TYPE_MINIMUM_BANDWIDTH,
429+
min_kbps=min_kbps, is_admin=True)
414430
net_args = {provider_net.NETWORK_TYPE: 'flat',
415431
provider_net.PHYSICAL_NETWORK: 'datacentre'}
416432
with self.network(uuidutils.generate_uuid(),
417433
arg_list=tuple(net_args.keys()), as_admin=True,
418434
**net_args) as net:
419-
with self.subnet(net) as subnet:
420-
with self.port(subnet) as port:
421-
port_data = port['port']
422-
# Check no QoS options.
423-
_check_bw(port_data['id'])
424-
425-
# Add QoS policy.
426-
req = self.new_update_request(
427-
'ports',
428-
{'port': {'qos_policy_id': qos['id']}},
429-
port_data['id'])
430-
req.get_response(self.api)
431-
_check_bw(port_data['id'], max_kbps, max_burst_kbps)
432-
433-
# Update port.
434-
req = self.new_update_request(
435-
'ports',
436-
{'port': {'name': uuidutils.generate_uuid()}},
437-
port_data['id'])
438-
req.get_response(self.api)
439-
_check_bw(port_data['id'], max_kbps, max_burst_kbps)
435+
with self.subnet(net) as subnet, self.port(subnet) as port:
436+
port_data = port['port']
437+
# Check no QoS options.
438+
_check_bw(port_data['id'])
439+
440+
# Add QoS policy.
441+
req = self.new_update_request(
442+
'ports',
443+
{'port': {'qos_policy_id': qos['id']}},
444+
port_data['id'])
445+
req.get_response(self.api)
446+
_check_bw(port_data['id'], max_kbps, max_burst_kbps, min_kbps)
447+
448+
# Update port.
449+
req = self.new_update_request(
450+
'ports',
451+
{'port': {'name': uuidutils.generate_uuid()}},
452+
port_data['id'])
453+
req.get_response(self.api)
454+
_check_bw(port_data['id'], max_kbps, max_burst_kbps, min_kbps)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
When using the ML2/OVN mechanism driver, the QoS policies with maximum
5+
bandwidth rules only are always enforced using the internal OVN policers,
6+
regardless of the direction and network type. It is not relevant if the
7+
QoS policy has or not DSCP rules.

0 commit comments

Comments
 (0)