#!/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()