| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: GPL-2.0 | |
| from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_true, KsftSkipEx | |
| from lib.py import EthtoolFamily, NetshaperFamily | |
| from lib.py import NetDrvEnv | |
| from lib.py import NlError | |
| from lib.py import cmd | |
| def get_shapers(cfg, nl_shaper) -> None: | |
| try: | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| # Default configuration: no shapers configured. | |
| ksft_eq(len(shapers), 0) | |
| def get_caps(cfg, nl_shaper) -> None: | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex}, dump=True) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| # Each device implementing shaper support must support some | |
| # features in at least a scope. | |
| ksft_true(len(caps)> 0) | |
| def set_qshapers(cfg, nl_shaper) -> None: | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, | |
| 'scope':'queue'}) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: | |
| raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps") | |
| cfg.queues = True; | |
| netnl = EthtoolFamily() | |
| channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) | |
| if channels['combined-count'] == 0: | |
| cfg.rx_type = 'rx' | |
| cfg.nr_queues = channels['rx-count'] | |
| else: | |
| cfg.rx_type = 'combined' | |
| cfg.nr_queues = channels['combined-count'] | |
| if cfg.nr_queues < 3: | |
| raise KsftSkipEx(f"device does not support enough queues min 3 found {cfg.nr_queues}") | |
| nl_shaper.set({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| nl_shaper.set({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 2}, | |
| 'metric': 'bps', | |
| 'bw-max': 20000}) | |
| # Querying a specific shaper not yet configured must fail. | |
| raised = False | |
| try: | |
| shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 0}}) | |
| except (NlError): | |
| raised = True | |
| ksft_eq(raised, True) | |
| shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}}) | |
| ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 2}, | |
| 'metric': 'bps', | |
| 'bw-max': 20000}]) | |
| def del_qshapers(cfg, nl_shaper) -> None: | |
| if not cfg.queues: | |
| raise KsftSkipEx("queue shapers not supported by device, skipping delete") | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 2}}) | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(len(shapers), 0) | |
| def set_nshapers(cfg, nl_shaper) -> None: | |
| # Check required features. | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, | |
| 'scope':'netdev'}) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: | |
| raise KsftSkipEx("device does not support nested netdev scope shapers with weight") | |
| cfg.netdev = True; | |
| nl_shaper.set({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'netdev', 'id': 0}, | |
| 'bw-max': 100000}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'netdev'}, | |
| 'metric': 'bps', | |
| 'bw-max': 100000}]) | |
| def del_nshapers(cfg, nl_shaper) -> None: | |
| if not cfg.netdev: | |
| raise KsftSkipEx("netdev shaper not supported by device, skipping delete") | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'netdev'}}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(len(shapers), 0) | |
| def basic_groups(cfg, nl_shaper) -> None: | |
| if not cfg.netdev: | |
| raise KsftSkipEx("netdev shaper not supported by the device") | |
| if cfg.nr_queues < 3: | |
| raise KsftSkipEx(f"netdev does not have enough queues min 3 reported {cfg.nr_queues}") | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, | |
| 'scope':'queue'}) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| if not 'support-weight' in caps: | |
| raise KsftSkipEx("device does not support queue scope shapers with weight") | |
| node_handle = nl_shaper.group({ | |
| 'ifindex': cfg.ifindex, | |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 1}, | |
| {'handle': {'scope': 'queue', 'id': 2}, | |
| 'weight': 2}], | |
| 'handle': {'scope':'netdev'}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| ksft_eq(node_handle, {'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'netdev'}}) | |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}}) | |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 1 }) | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 2}}) | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}}) | |
| # Deleting all the leaves shaper does not affect the node one | |
| # when the latter has 'netdev' scope. | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(len(shapers), 1) | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'netdev'}}) | |
| def qgroups(cfg, nl_shaper) -> None: | |
| if cfg.nr_queues < 4: | |
| raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}") | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, | |
| 'scope':'node'}) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: | |
| raise KsftSkipEx("device does not support node scope shapers with bw_max and metric bps") | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, | |
| 'scope':'queue'}) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("shapers not supported by the device") | |
| raise | |
| if not 'support-nesting' in caps or not 'support-weight' in caps or not 'support-metric-bps' in caps: | |
| raise KsftSkipEx("device does not support nested queue scope shapers with weight") | |
| cfg.groups = True; | |
| node_handle = nl_shaper.group({ | |
| 'ifindex': cfg.ifindex, | |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 3}, | |
| {'handle': {'scope': 'queue', 'id': 2}, | |
| 'weight': 2}], | |
| 'handle': {'scope':'node'}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| node_id = node_handle['handle']['id'] | |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}}) | |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 3}) | |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'node', 'id': node_id}}) | |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'node', 'id': node_id}, | |
| 'parent': {'scope': 'netdev'}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| # Grouping to a specified, not existing node scope shaper must fail | |
| raised = False | |
| try: | |
| nl_shaper.group({ | |
| 'ifindex': cfg.ifindex, | |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 3}], | |
| 'handle': {'scope':'node', 'id': node_id + 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| except (NlError): | |
| raised = True | |
| ksft_eq(raised, True) | |
| # Add to an existing node | |
| node_handle = nl_shaper.group({ | |
| 'ifindex': cfg.ifindex, | |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 4}], | |
| 'handle': {'scope':'node', 'id': node_id}}) | |
| ksft_eq(node_handle, {'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'node', 'id': node_id}}) | |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 3}}) | |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 4}) | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 2}}) | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 1}}) | |
| # Deleting a non empty node will move the leaves downstream. | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'node', 'id': node_id}}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 4}]) | |
| # Finish and verify the complete cleanup. | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': 3}}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(len(shapers), 0) | |
| def delegation(cfg, nl_shaper) -> None: | |
| if not cfg.groups: | |
| raise KsftSkipEx("device does not support node scope") | |
| try: | |
| caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, | |
| 'scope':'node'}) | |
| except NlError as e: | |
| if e.error == 95: | |
| raise KsftSkipEx("node scope shapers not supported by the device") | |
| raise | |
| if not 'support-nesting' in caps: | |
| raise KsftSkipEx("device does not support node scope shapers nesting") | |
| node_handle = nl_shaper.group({ | |
| 'ifindex': cfg.ifindex, | |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 3}, | |
| {'handle': {'scope': 'queue', 'id': 2}, | |
| 'weight': 2}, | |
| {'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 1}], | |
| 'handle': {'scope':'node'}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}) | |
| node_id = node_handle['handle']['id'] | |
| # Create the nested node and validate the hierarchy | |
| nested_node_handle = nl_shaper.group({ | |
| 'ifindex': cfg.ifindex, | |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 3}, | |
| {'handle': {'scope': 'queue', 'id': 2}, | |
| 'weight': 2}], | |
| 'handle': {'scope':'node'}, | |
| 'metric': 'bps', | |
| 'bw-max': 5000}) | |
| nested_node_id = nested_node_handle['handle']['id'] | |
| ksft_true(nested_node_id != node_id) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': nested_node_id}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 3}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': nested_node_id}, | |
| 'handle': {'scope': 'queue', 'id': 2}, | |
| 'weight': 2}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 1}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'node', 'id': node_id}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'node', 'id': nested_node_id}, | |
| 'metric': 'bps', | |
| 'bw-max': 5000}]) | |
| # Deleting a non empty node will move the leaves downstream. | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'node', 'id': nested_node_id}}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'weight': 3}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'queue', 'id': 2}, | |
| 'weight': 2}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'node', 'id': node_id}, | |
| 'handle': {'scope': 'queue', 'id': 3}, | |
| 'weight': 1}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'node', 'id': node_id}, | |
| 'metric': 'bps', | |
| 'bw-max': 10000}]) | |
| # Final cleanup. | |
| for i in range(1, 4): | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': i}}) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(len(shapers), 0) | |
| def queue_update(cfg, nl_shaper) -> None: | |
| if cfg.nr_queues < 4: | |
| raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}") | |
| if not cfg.queues: | |
| raise KsftSkipEx("device does not support queue scope") | |
| for i in range(3): | |
| nl_shaper.set({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': i}, | |
| 'metric': 'bps', | |
| 'bw-max': (i + 1) * 1000}) | |
| # Delete a channel, with no shapers configured on top of the related | |
| # queue: no changes expected | |
| cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3", timeout=10) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 0}, | |
| 'metric': 'bps', | |
| 'bw-max': 1000}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 2000}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 2}, | |
| 'metric': 'bps', | |
| 'bw-max': 3000}]) | |
| # Delete a channel, with a shaper configured on top of the related | |
| # queue: the shaper must be deleted, too | |
| cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2", timeout=10) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 0}, | |
| 'metric': 'bps', | |
| 'bw-max': 1000}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 2000}]) | |
| # Restore the original channels number, no expected changes | |
| cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}", timeout=10) | |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) | |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 0}, | |
| 'metric': 'bps', | |
| 'bw-max': 1000}, | |
| {'ifindex': cfg.ifindex, | |
| 'parent': {'scope': 'netdev'}, | |
| 'handle': {'scope': 'queue', 'id': 1}, | |
| 'metric': 'bps', | |
| 'bw-max': 2000}]) | |
| # Final cleanup. | |
| for i in range(0, 2): | |
| nl_shaper.delete({'ifindex': cfg.ifindex, | |
| 'handle': {'scope': 'queue', 'id': i}}) | |
| def main() -> None: | |
| with NetDrvEnv(__file__, queue_count=4) as cfg: | |
| cfg.queues = False | |
| cfg.netdev = False | |
| cfg.groups = False | |
| cfg.nr_queues = 0 | |
| ksft_run([get_shapers, | |
| get_caps, | |
| set_qshapers, | |
| del_qshapers, | |
| set_nshapers, | |
| del_nshapers, | |
| basic_groups, | |
| qgroups, | |
| delegation, | |
| queue_update], args=(cfg, NetshaperFamily())) | |
| ksft_exit() | |
| if __name__ == "__main__": | |
| main() |