From d9d796848304196edd064b9d590de3b3bffd99bf Mon Sep 17 00:00:00 2001 From: khramshinr Date: Thu, 24 Oct 2024 18:48:20 +0600 Subject: [PATCH] T6790: QoS: Improve CAKE Policy - Fixed handling of flow isolation parameters. - Corrected support for `nat` and `nonat` in flow isolation. - Extended RTT values to cover the full range supported by `tc`. - Make migration script 2-to-3 qos --- .../include/version/qos-version.xml.i | 2 +- interface-definitions/qos.xml.in | 111 ++++++++---------- python/vyos/qos/cake.py | 47 ++++---- smoketest/scripts/cli/test_qos.py | 63 ++++++++++ src/migration-scripts/qos/2-to-3 | 34 ++++++ 5 files changed, 174 insertions(+), 83 deletions(-) create mode 100644 src/migration-scripts/qos/2-to-3 diff --git a/interface-definitions/include/version/qos-version.xml.i b/interface-definitions/include/version/qos-version.xml.i index c67e61e915..127f771a9f 100644 --- a/interface-definitions/include/version/qos-version.xml.i +++ b/interface-definitions/include/version/qos-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in index 907fd5e4ce..c6ecb742e3 100644 --- a/interface-definitions/qos.xml.in +++ b/interface-definitions/qos.xml.in @@ -85,78 +85,67 @@ #include #include - + Flow isolation settings + + blind src-host dst-host host flow dual-src-host dual-dst-host triple-isolate + + + blind + Disables flow isolation, all traffic passes through a single queue + + + src-host + Flows are defined only by source address + + + dst-host + Flows are defined only by destination address + + + host + Flows are defined by source-destination host pairs + + + flow + Flows are defined by the entire 5-tuple + + + dual-src-host + Flows are defined by the 5-tuple, fairness is applied first over source addresses, then over individual flows + + + dual-dst-host + Flows are defined by the 5-tuple, fairness is applied first over destination addresses, then over individual flows + + + triple-isolate + Flows are defined by the 5-tuple, fairness is applied over source and destination addresses and also over individual flows (default) + + + (blind|src-host|dst-host|host|flow|dual-src-host|dual-dst-host|triple-isolate) + - - - - Disables flow isolation, all traffic passes through a single queue - - - - - - Flows are defined only by source address - - - - - - Flows are defined only by destination address - - - - - - Flows are defined by source-destination host pairs - - - - - - Flows are defined by the entire 5-tuple - - - - - - Flows are defined by the 5-tuple, fairness is applied first over source addresses, then over individual flows - - - - - - Flows are defined by the 5-tuple, fairness is applied first over destination addresses, then over individual flows - - - - - - Flows are defined by the 5-tuple, fairness is applied over source and destination addresses and also over individual flows (default) - - - - - - Perform NAT lookup before applying flow-isolation rules - - - - - + triple-isolate + + + + Perform NAT lookup before applying flow-isolation rules + + + Round-Trip-Time for Active Queue Management (AQM) - u32:1-3600000 + u32:1-1000000000 RTT in ms - + - RTT must be in range 1 to 3600000 milli-seconds + RTT must be in range 1 to 1000000000 milli-seconds 100 diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py index 1ee7d0fc3c..ca5a269174 100644 --- a/python/vyos/qos/cake.py +++ b/python/vyos/qos/cake.py @@ -15,10 +15,25 @@ from vyos.qos.base import QoSBase + class CAKE(QoSBase): + """ + https://man7.org/linux/man-pages/man8/tc-cake.8.html + """ + _direction = ['egress'] - # https://man7.org/linux/man-pages/man8/tc-cake.8.html + flow_isolation_map = { + 'blind': 'flowblind', + 'src-host': 'srchost', + 'dst-host': 'dsthost', + 'dual-dst-host': 'dual-dsthost', + 'dual-src-host': 'dual-srchost', + 'triple-isolate': 'triple-isolate', + 'flow': 'flows', + 'host': 'hosts', + } + def update(self, config, direction): tmp = f'tc qdisc add dev {self._interface} root handle 1: cake {direction}' if 'bandwidth' in config: @@ -30,26 +45,16 @@ def update(self, config, direction): tmp += f' rtt {rtt}ms' if 'flow_isolation' in config: - if 'blind' in config['flow_isolation']: - tmp += f' flowblind' - if 'dst_host' in config['flow_isolation']: - tmp += f' dsthost' - if 'dual_dst_host' in config['flow_isolation']: - tmp += f' dual-dsthost' - if 'dual_src_host' in config['flow_isolation']: - tmp += f' dual-srchost' - if 'triple_isolate' in config['flow_isolation']: - tmp += f' triple-isolate' - if 'flow' in config['flow_isolation']: - tmp += f' flows' - if 'host' in config['flow_isolation']: - tmp += f' hosts' - if 'nat' in config['flow_isolation']: - tmp += f' nat' - if 'src_host' in config['flow_isolation']: - tmp += f' srchost ' - else: - tmp += f' nonat' + isolation_value = self.flow_isolation_map.get(config['flow_isolation']) + + if isolation_value is not None: + tmp += f' {isolation_value}' + else: + raise ValueError( + f'Invalid flow isolation parameter: {config["flow_isolation"]}' + ) + + tmp += ' nat' if 'flow_isolation_nat' in config else ' nonat' self._cmd(tmp) diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py index 77d384024b..eb6653b31e 100755 --- a/smoketest/scripts/cli/test_qos.py +++ b/smoketest/scripts/cli/test_qos.py @@ -22,6 +22,7 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.qos import CAKE from vyos.utils.process import cmd base_path = ['qos'] @@ -871,6 +872,68 @@ def test_16_wrong_traffic_match_group(self): self.cli_set(['qos', 'traffic-match-group', '3', 'match-group', 'unexpected']) self.cli_commit() + def test_17_cake_updates(self): + bandwidth = 1000000 + rtt = 200 + interface = self._interfaces[0] + policy_name = f'qos-policy-{interface}' + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set( + base_path + ['policy', 'cake', policy_name, 'bandwidth', str(bandwidth)] + ) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'rtt', str(rtt)]) + + # commit changes + self.cli_commit() + + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('cake', tmp['kind']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth * 125), tmp['options']['bandwidth']) + # RTT internally is in us + self.assertEqual(int(rtt * 1000), tmp['options']['rtt']) + self.assertEqual('triple-isolate', tmp['options']['flowmode']) + self.assertFalse(tmp['options']['ingress']) + self.assertFalse(tmp['options']['nat']) + self.assertTrue(tmp['options']['raw']) + + nat = True + for flow_isolation in [ + 'blind', + 'src-host', + 'dst-host', + 'dual-dst-host', + 'dual-src-host', + 'triple-isolate', + 'flow', + 'host', + ]: + self.cli_set( + base_path + + ['policy', 'cake', policy_name, 'flow-isolation', flow_isolation] + ) + + if nat: + self.cli_set( + base_path + ['policy', 'cake', policy_name, 'flow-isolation-nat'] + ) + else: + self.cli_delete( + base_path + ['policy', 'cake', policy_name, 'flow-isolation-nat'] + ) + + self.cli_commit() + + tmp = get_tc_qdisc_json(interface) + self.assertEqual( + CAKE.flow_isolation_map.get(flow_isolation), tmp['options']['flowmode'] + ) + + self.assertEqual(nat, tmp['options']['nat']) + nat = not nat + def test_20_round_robin_policy_default(self): interface = self._interfaces[0] policy_name = f'qos-policy-{interface}' diff --git a/src/migration-scripts/qos/2-to-3 b/src/migration-scripts/qos/2-to-3 new file mode 100644 index 0000000000..284fe828e6 --- /dev/null +++ b/src/migration-scripts/qos/2-to-3 @@ -0,0 +1,34 @@ +# Copyright 2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +from vyos.configtree import ConfigTree + + +def migrate(config: ConfigTree) -> None: + base = ['qos', 'policy', 'cake'] + if config.exists(base): + for policy in config.list_nodes(base): + if config.exists(base + [policy, 'flow-isolation']): + isolation = None + for isol in config.list_nodes(base + [policy, 'flow-isolation']): + if isol == 'nat': + config.set(base + [policy, 'flow-isolation-nat']) + else: + isolation = isol + + config.delete(base + [policy, 'flow-isolation']) + + if isolation: + config.set(base + [policy, 'flow-isolation'], value=isolation)