Files
neutron/neutron/tests/functional/agent/ovn/extensions/test_qos_hwol.py
Rodolfo Alonso Hernandez 526f238fa4 [OVN] QoS max and min rules should be defined in LSP for phynet ports
The ML2/OVN QoS enforcement for physical network ports (flat, VLAN) is
done in the localnet port of the physical bridge via TC commands.

A physical network will have a physical bridge in each compute node.
This physical bridge will have a NIC interface that will provide
connectivity. On this interface (localnet port), OVN enforces the QoS
mininum and maximum rate rules (**only egress**), using TC commands
on the interface. It creates a HTB qdisc and a class per port with
QoS rules. Each class, that will match the traffic of the port, will
have a "ceil" and "rate" values [1].

OVN uses the information stored in the "Logical_Switch_Port.options"
field, stored as a dictionary, with the keys [2]:
* qos_min_rate
* qos_max_rate
* qos_burst

This patch is:
* Moving the max and min rate configuration from the QoS registers
  to the "Logical_Switch_Port.options" dictionary.
* Logging a warning when a min rate option is defined in a port hosted
  in a tunnelled network (not supported).

This change was documented in [3], in OVN version 23.06.0

[1]https://man7.org/linux/man-pages/man8/tc-htb.8.html
[2]1bec9e3ddd/ovn-nb.xml (L1155-L1168)
[3]87514ac042

Closes-Bug: #2015376
Change-Id: I4581d31ba04c0d2f3f45ee6d07bcaba82fb038d4
2024-12-23 14:48:36 +00:00

198 lines
8.5 KiB
Python

# Copyright (c) 2023 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from neutron_lib import constants
from oslo_utils import uuidutils
from neutron.agent.ovn.agent import ovsdb as agent_ovsdb
from neutron.agent.ovn.extensions import qos_hwol
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.common import utils as n_utils
from neutron.tests import base as test_base
from neutron.tests.functional import base
class OVSInterfaceEventTestCase(base.TestOVNFunctionalBase):
def _cleanup(self):
self.ovs_idl.del_port(self.port_name, bridge=self.br_name).execute(
check_error=False)
self.ovs_idl.del_br(self.br_name).execute(check_error=False)
@test_base.unstable_test('bug 2006603')
def test_port_creation_and_deletion(self):
def check_add_port_called():
try:
mock_agent[qos_hwol.EXT_NAME].add_port.assert_has_calls(
[mock.call(port_iface_id, self.port_name)])
return True
except AssertionError:
return False
def check_remove_egress_called():
try:
mock_agent.qos_hwol_ext.remove_egress.assert_has_calls(
[mock.call(port_iface_id)])
return True
except AssertionError:
return False
port_iface_id = 'port_iface-id'
mock_agent = mock.MagicMock()
events = [qos_hwol.OVSInterfaceEvent(mock_agent)]
self.ovs_idl = agent_ovsdb.MonitorAgentOvsIdl(events=events).start()
self.br_name = ('brtest-' + uuidutils.generate_uuid())[:13]
self.port_name = ('port-' + uuidutils.generate_uuid())[:13]
self.addCleanup(self._cleanup)
with self.ovs_idl.transaction() as txn:
txn.add(self.ovs_idl.add_br(self.br_name))
txn.add(self.ovs_idl.add_port(self.br_name, self.port_name))
txn.add(self.ovs_idl.iface_set_external_id(
self.port_name, 'iface-id', port_iface_id))
txn.add(self.ovs_idl.db_set(
'Interface', self.port_name, ('type', 'internal')))
exc = Exception('Port %s was not added to the bridge %s' %
(self.port_name, self.br_name))
n_utils.wait_until_true(check_add_port_called, timeout=5,
exception=exc)
self.ovs_idl.del_port(self.port_name).execute(check_error=True)
exc = Exception('Port %s was not deleted from the bridge %s' %
(self.port_name, self.br_name))
n_utils.wait_until_true(check_remove_egress_called, timeout=5,
exception=exc)
class QoSBandwidthLimitEventTestCase(base.TestOVNFunctionalBase):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.net = self._make_network(self.fmt, 'n1', True)['network']
res = self._create_subnet(self.fmt, self.net['id'], '10.0.0.0/24')
self.subnet = self.deserialize(self.fmt, res)['subnet']
res = self._create_port(self.fmt, self.net['id'])
self.port = self.deserialize(self.fmt, res)['port']
def test_qos_bw_limit_created_and_updated(self):
def check_update_egress_called(rate):
try:
mock_agent[qos_hwol.EXT_NAME].update_egress.assert_has_calls(
[mock.call(port_id, rate, 0)])
return True
except AssertionError:
return False
mock_agent = mock.MagicMock(nb_idl=self.nb_api)
events = [qos_hwol.QoSBandwidthLimitEvent(mock_agent)]
agent_ovsdb.MonitorAgentOvnNbIdl(qos_hwol.NB_IDL_TABLES,
events).start()
lswitch_name = utils.ovn_name(self.net['id'])
port_id = self.port['id']
ovn_rule = {'switch': lswitch_name,
'priority': 1000,
'direction': 'from-lport',
'match': 'inport == ' + port_id,
'rate': 10000,
'external_ids': {ovn_const.OVN_PORT_EXT_ID_KEY: port_id}}
self.nb_api.qos_add(**ovn_rule).execute(check_error=True)
n_utils.wait_until_true(
lambda: check_update_egress_called(ovn_rule['rate']), timeout=5)
ovn_rule['rate'] = 15000
self.nb_api.qos_add(**ovn_rule, may_exist=True).execute(
check_error=True)
n_utils.wait_until_true(
lambda: check_update_egress_called(ovn_rule['rate']), timeout=5)
class QoSLogicalSwitchPortEventTestCase(base.TestOVNFunctionalBase):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.net = self._make_network(self.fmt, 'n1', True)['network']
res = self._create_subnet(self.fmt, self.net['id'], '10.0.0.0/24')
self.subnet = self.deserialize(self.fmt, res)['subnet']
res = self._create_port(self.fmt, self.net['id'])
self.port = self.deserialize(self.fmt, res)['port']
def test_qos_created_and_updated(self):
def check_update_egress_called(max_kbps, min_kbps):
try:
mock_agent[qos_hwol.EXT_NAME].update_egress.assert_has_calls(
[mock.call(port_id, max_kbps, min_kbps)])
return True
except AssertionError:
return False
mock_agent = mock.MagicMock(nb_idl=self.nb_api)
events = [qos_hwol.QoSLogicalSwitchPortEvent(mock_agent)]
agent_ovsdb.MonitorAgentOvnNbIdl(qos_hwol.NB_IDL_TABLES,
events).start()
port_id = self.port['id']
max_kbps, min_kbps = 9000, 5000
max_bps, min_bps = (max_kbps * constants.SI_BASE,
min_kbps * constants.SI_BASE)
lsp = self.nb_api.lsp_get(port_id).execute(check_error=True)
options = {ovn_const.LSP_OPTIONS_QOS_MAX_RATE: str(max_bps),
ovn_const.LSP_OPTIONS_QOS_MIN_RATE: str(min_bps)}
self.nb_api.update_lswitch_qos_options(lsp, **options).execute(
check_error=True)
n_utils.wait_until_true(
lambda: check_update_egress_called(max_kbps, min_kbps), timeout=5)
class PortBindingChassisCreatedEventTestCase(base.TestOVNFunctionalBase):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.net = self._make_network(self.fmt, 'n1', True)['network']
res = self._create_subnet(self.fmt, self.net['id'], '10.0.0.0/24')
self.subnet = self.deserialize(self.fmt, res)['subnet']
res = self._create_port(self.fmt, self.net['id'])
self.port = self.deserialize(self.fmt, res)['port']
@mock.patch.object(agent_ovsdb, 'get_ovs_port_name')
@mock.patch.object(agent_ovsdb, 'get_port_qos')
def test_port_binding_chassis_create_event(self, mock_get_port_qos,
*args):
def check_update_egress_called(max_kbps, min_kbps):
try:
mock_agent[qos_hwol.EXT_NAME].update_egress.assert_has_calls(
[mock.call(self.port['id'], max_kbps, min_kbps)])
return True
except AssertionError:
return False
max_kbps, min_kbps = 1000, 800
mock_get_port_qos.return_value = max_kbps, min_kbps
mock_agent = mock.MagicMock(nb_idl=self.nb_api)
events = [qos_hwol.PortBindingChassisCreatedEvent(mock_agent)]
chassis_name = self.add_fake_chassis('ovn-host-fake')
mock_agent.chassis = chassis_name
agent_ovsdb.MonitorAgentOvnSbIdl(qos_hwol.SB_IDL_TABLES, events,
chassis=chassis_name).start()
lsp_columns = {}
lsp_name = self.port['id']
ls_name = utils.ovn_name(self.net['id'])
self.nb_api.create_lswitch_port(
lsp_name, ls_name, **lsp_columns).execute(check_error=True)
self.sb_api.lsp_bind(lsp_name, chassis_name).execute(check_error=True)
n_utils.wait_until_true(
lambda: check_update_egress_called(max_kbps, min_kbps), timeout=5)