| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: GPL-2.0 |
| # pylint: disable=too-many-lines |
| |
| import errno |
| import glob |
| |
| from lib.py import ksft_run, ksft_exit |
| from lib.py import ksft_eq, ksft_true, ksft_raises, KsftSkipEx |
| from lib.py import EthtoolFamily, NetshaperFamily |
| from lib.py import NetDrvEnv |
| from lib.py import NlError |
| from lib.py import cmd, defer |
| |
| def _delete_shaper(cfg, nl_shaper, handle) -> None: |
| """ Delete the shaper identified by handle, ignoring a missing-shaper error. """ |
| try: |
| nl_shaper.delete({'ifindex': cfg.ifindex, |
| 'handle': handle}) |
| except NlError as e: |
| if e.error != errno.ENOENT: |
| raise |
| |
| def _require_queues(cfg, count): |
| """ Return the netdev TX queue count, skipping the test if fewer than count exist. """ |
| qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/tx-*")) |
| if qcnt < count: |
| raise KsftSkipEx(f"netdev has {qcnt} queues, {count} required") |
| return qcnt |
| |
| def _cap_get(cfg, nl_shaper, scope): |
| """ Return the shaper capabilities for the given scope, caching them on cfg. """ |
| if not hasattr(cfg, 'cap_cache'): |
| cfg.cap_cache = {} |
| if scope not in cfg.cap_cache: |
| cfg.cap_cache[scope] = nl_shaper.cap_get({'ifindex': cfg.ifindex, |
| 'scope': scope}) |
| |
| return cfg.cap_cache[scope] |
| |
| def _require_caps(cfg, nl_shaper, scope, caps, msg) -> None: |
| """ Skip the test unless the given scope advertises all the required caps. """ |
| try: |
| supported = _cap_get(cfg, nl_shaper, scope) |
| except NlError as e: |
| if e.error == errno.EOPNOTSUPP: |
| raise KsftSkipEx(f"{scope} scope shapers not supported by the device") |
| raise |
| |
| if not set(caps).issubset(supported): |
| raise KsftSkipEx(msg) |
| |
| 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") |
| |
| _require_queues(cfg, 3) |
| cfg.queues = True |
| |
| 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 set_all_supported_attrs(cfg, nl_shaper) -> None: |
| """ Set every queue-scope attribute the device advertises and verify the read-back. """ |
| _require_queues(cfg, 1) |
| |
| _require_caps(cfg, nl_shaper, 'queue', [], |
| "queue scope shapers not supported by the device") |
| caps = _cap_get(cfg, nl_shaper, 'queue') |
| |
| attrs = {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}} |
| expected = {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'netdev'}, |
| 'handle': {'scope': 'queue', 'id': 0}} |
| |
| rate_attrs = {'support-bw-min': ('bw-min', 10000, 100), |
| 'support-bw-max': ('bw-max', 20000, 200), |
| 'support-burst': ('burst', 3000, 30)} |
| rate_attr_supported = any(cap in caps for cap in rate_attrs) |
| bps_supported = 'support-metric-bps' in caps |
| pps_supported = 'support-metric-pps' in caps |
| |
| def add_rate_attrs(metric, value_idx) -> None: |
| attrs['metric'] = metric |
| expected['metric'] = metric |
| for cap, (attr, bps_value, pps_value) in rate_attrs.items(): |
| if cap not in caps: |
| continue |
| |
| value = bps_value if value_idx == 0 else pps_value |
| attrs[attr] = value |
| expected[attr] = value |
| |
| if rate_attr_supported: |
| if bps_supported: |
| add_rate_attrs('bps', 0) |
| elif pps_supported: |
| add_rate_attrs('pps', 1) |
| |
| if 'support-priority' in caps: |
| attrs['priority'] = 1 |
| expected['priority'] = 1 |
| if 'support-weight' in caps: |
| attrs['weight'] = 2 |
| expected['weight'] = 2 |
| |
| if len(attrs) == 2: |
| raise KsftSkipEx("device does not advertise any supported queue shaper attributes") |
| |
| nl_shaper.set(attrs) |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper, expected) |
| |
| if rate_attr_supported and bps_supported and pps_supported: |
| add_rate_attrs('pps', 1) |
| nl_shaper.set(attrs) |
| |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper, expected) |
| |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def invalid_set_preserves_state(cfg, nl_shaper) -> None: |
| """ Verify a rejected .set leaves the existing shaper configuration unchanged. """ |
| nq = _require_queues(cfg, 1) |
| _require_caps(cfg, nl_shaper, 'queue', |
| ['support-bw-max', 'support-metric-bps'], |
| "device does not support queue scope bw_max with bps metric") |
| |
| initial = {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'netdev'}, |
| 'handle': {'scope': 'queue', 'id': 0}, |
| 'metric': 'bps', |
| 'bw-max': 10000} |
| nl_shaper.set({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}, |
| 'metric': 'bps', |
| 'bw-max': 10000}) |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| |
| with ksft_raises(NlError): |
| nl_shaper.set({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': 0}, |
| 'metric': 'bps', |
| 'bw-max': 20000}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper, initial) |
| |
| with ksft_raises(NlError): |
| nl_shaper.set({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': nq}, |
| 'metric': 'bps', |
| 'bw-max': 20000}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper, initial) |
| |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None: |
| r"""Grouping leaves from different nodes requires an explicit parent. |
| |
| netdev netdev |
| / \ parent=netdev |
| N1 N2 group N |
| | | {Q0,Q1} / \ |
| Q0 Q1 -------> Q0 Q1 |
| |
| Without an explicit parent the group is rejected; parent=netdev |
| collapses the leaves into one new node. |
| """ |
| _require_queues(cfg, 2) |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps'], |
| "device does not support node scope shapers with bw_max and metric bps") |
| _require_caps(cfg, nl_shaper, 'queue', |
| ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| n1_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 10000}) |
| n1_id = n1_handle['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| |
| n2_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 2}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 20000}) |
| n2_id = n2_handle['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) |
| |
| with ksft_raises(NlError): |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 3}, |
| {'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 4}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 30000}) |
| |
| shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n1_id}, |
| 'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 1}) |
| shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 1}}) |
| ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n2_id}, |
| 'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 2}) |
| |
| node_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 3}, |
| {'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 4}], |
| 'handle': {'scope':'node'}, |
| 'parent': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': 30000}) |
| node_id = node_handle['handle']['id'] |
| |
| for old_id in (n1_id, n2_id): |
| with ksft_raises(NlError): |
| nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': old_id}}) |
| |
| shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node_id}, |
| 'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 3}) |
| shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 1}}) |
| ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node_id}, |
| 'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 4}) |
| |
| for i in range(2): |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def recursive_empty_node_cleanup(cfg, nl_shaper) -> None: |
| r"""Deleting the last leaf recursively removes the emptied ancestors. |
| |
| netdev netdev |
| | del Q0 |
| N1 ------> (N1 and N2 removed too) |
| | |
| N2 |
| | |
| Q0 |
| """ |
| _require_queues(cfg, 1) |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps', 'support-nesting'], |
| "device does not support nested node scope shapers") |
| _require_caps(cfg, nl_shaper, 'queue', |
| ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| n1_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 10000}) |
| n1_id = n1_handle['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| |
| n2_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'parent': {'scope': 'node', 'id': n1_id}, |
| 'metric': 'bps', |
| 'bw-max': 5000}) |
| n2_id = n2_handle['handle']['id'] |
| |
| shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n2_id}, |
| 'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 1}) |
| |
| nl_shaper.delete({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 0}}) |
| |
| for handle in ({'scope': 'queue', 'id': 0}, |
| {'scope': 'node', 'id': n2_id}, |
| {'scope': 'node', 'id': n1_id}): |
| with ksft_raises(NlError): |
| nl_shaper.get({'ifindex': cfg.ifindex, 'handle': handle}) |
| |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def _group_under_netdev(cfg, nl_shaper, bw_max=None): |
| r"""Group queues under a netdev-scope node; caller owns node teardown. |
| |
| netdev netdev |
| / \ del Q1,Q2 |
| Q1 Q2 -------> (netdev node persists) |
| """ |
| group_args = { |
| 'ifindex': cfg.ifindex, |
| 'leaves': [{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}, |
| {'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 2}], |
| 'handle': {'scope': 'netdev'}} |
| if bw_max: |
| group_args['metric'] = 'bps' |
| group_args['bw-max'] = bw_max |
| |
| node_handle = nl_shaper.group(group_args) |
| ksft_eq(node_handle, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'netdev'}}) |
| |
| del_node = defer(_delete_shaper, cfg, nl_shaper, {'scope': 'netdev'}) |
| del_queues = [defer(_delete_shaper, cfg, nl_shaper, |
| {'scope': 'queue', 'id': qid}) |
| for qid in (1, 2)] |
| |
| 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}) |
| for dq in del_queues: |
| dq.exec() |
| |
| # Caller owns the node teardown so it can verify the netdev-scope node |
| # survives leaf deletion before removing it. |
| return del_node |
| |
| def basic_groups(cfg, nl_shaper) -> None: |
| r"""Group queues under a netdev-scope node, then tear it down. |
| |
| netdev |
| / \ |
| Q1 Q2 |
| """ |
| _require_queues(cfg, 3) |
| |
| _require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "queue scope not supported with nesting and weight") |
| |
| del_node = _group_under_netdev(cfg, nl_shaper) |
| |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(shapers, [{'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'netdev'}}]) |
| |
| del_node.exec() |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def basic_groups_with_rate(cfg, nl_shaper) -> None: |
| r"""Rate-limited netdev-scope node outlives deletion of its leaves. |
| |
| netdev[10kbps] netdev[10kbps] |
| / \ del Q1,Q2 |
| Q1 Q2 -------> (node persists) |
| """ |
| bw_max = 10000 |
| |
| _require_queues(cfg, 3) |
| |
| _require_caps(cfg, nl_shaper, 'netdev', ['support-bw-max', 'support-metric-bps'], |
| "device does not support netdev scope rate limiting") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support queue scope shapers with nesting and weight") |
| |
| del_node = _group_under_netdev(cfg, nl_shaper, bw_max=bw_max) |
| |
| # 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(shapers, [{'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': bw_max}]) |
| |
| del_node.exec() |
| |
| def qgroups(cfg, nl_shaper) -> None: |
| _require_queues(cfg, 4) |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps'], |
| "device does not support node scope shapers with bw_max and metric bps") |
| _require_caps(cfg, nl_shaper, 'queue', |
| ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| 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 set_node_shaper(cfg, nl_shaper) -> None: |
| """ Verify a node-scope shaper rate can be updated via .set. """ |
| _require_queues(cfg, 2) |
| _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], |
| "device does not support node scope shapers with bw_max and metric bps") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| node_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 10000}) |
| node_id = node_handle['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) |
| |
| # Update the node's rate via .set |
| nl_shaper.set({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node_id}, |
| 'metric': 'bps', |
| 'bw-max': 20000}) |
| |
| 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': 20000}) |
| |
| # Cleanup |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 1}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def group_update_rate(cfg, nl_shaper) -> None: |
| """ Verify re-grouping a node updates its rate while leaving the leaves untouched. """ |
| _require_queues(cfg, 3) |
| _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], |
| "device does not support node scope shapers with bw_max and metric bps") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| # Create node with Q1, Q2 at bw_max=10000 |
| node_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}, |
| {'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 10000}) |
| node_id = node_handle['handle']['id'] |
| for i in range(1, 3): |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| |
| # Update rate via .group on the same node |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}, |
| {'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}], |
| 'handle': {'scope':'node', 'id': node_id}, |
| 'metric': 'bps', |
| 'bw-max': 50000}) |
| |
| # Verify rate updated |
| 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': 50000}) |
| |
| # Verify leaves unchanged |
| shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 1}}) |
| ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node_id}, |
| 'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}) |
| shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 2}}) |
| ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node_id}, |
| 'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}) |
| |
| # Make sure we only have 3 shapers including 2 queues and the node |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 3) |
| |
| # Cleanup |
| for i in range(1, 3): |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def delegation(cfg, nl_shaper) -> None: |
| _require_queues(cfg, 4) |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps', 'support-nesting'], |
| "device does not support node scope shapers with bw_max, metric bps and nesting") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| 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 nested_depth_limit(cfg, nl_shaper) -> None: |
| r"""Nest nodes as deep as the device allows to find the max depth. |
| |
| netdev |
| | |
| N1 -- Q1 |
| | |
| N2 -- Q2 |
| | |
| N3 -- Q3 |
| : (deepen until the driver rejects) |
| """ |
| bw_max = 10000 |
| |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps', 'support-nesting'], |
| "device does not support node scope shapers with bw_max, metric bps and nesting") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| nq = _require_queues(cfg, 3) |
| |
| node_ids = [] |
| cleanups = [] |
| queue_id = 1 |
| max_depth = 0 |
| limit_err = None |
| |
| # Create initial node with a queue leaf |
| node_id = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves': [{'handle': {'scope': 'queue', 'id': queue_id}, |
| 'weight': 1}], |
| 'handle': {'scope': 'node'}, |
| 'metric': 'bps', |
| 'bw-max': bw_max})['handle']['id'] |
| node_ids.append(node_id) |
| cleanups.append(defer(_delete_shaper, cfg, nl_shaper, |
| {'scope': 'node', 'id': node_id})) |
| cleanups.append(defer(_delete_shaper, cfg, nl_shaper, |
| {'scope': 'queue', 'id': queue_id})) |
| max_depth = 1 |
| 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': bw_max}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': queue_id}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node_id}, |
| 'handle': {'scope': 'queue', 'id': queue_id}, |
| 'weight': 1}) |
| queue_id += 1 |
| |
| # Keep nesting deeper until the driver rejects or queues run out. |
| while queue_id < nq: |
| parent_id = node_ids[-1] |
| try: |
| node_id = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves': [{'handle': {'scope': 'queue', |
| 'id': queue_id}, |
| 'weight': 1}], |
| 'handle': {'scope': 'node'}, |
| 'parent': {'scope': 'node', |
| 'id': parent_id}, |
| 'metric': 'bps', |
| 'bw-max': bw_max})['handle']['id'] |
| except NlError as e: |
| # Only treat "cannot nest deeper" errors as the depth limit; |
| # drivers report it differently (EOPNOTSUPP/ENOSPC/E2BIG/EINVAL). |
| # Anything else (ENOMEM, EIO, EPERM, driver bug) is a real failure. |
| if e.error not in (errno.EOPNOTSUPP, errno.ENOSPC, |
| errno.E2BIG, errno.EINVAL): |
| raise |
| limit_err = e |
| break |
| |
| node_ids.append(node_id) |
| cleanups.append(defer(_delete_shaper, cfg, nl_shaper, |
| {'scope': 'node', 'id': node_id})) |
| cleanups.append(defer(_delete_shaper, cfg, nl_shaper, |
| {'scope': 'queue', 'id': queue_id})) |
| max_depth += 1 |
| 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': 'node', 'id': parent_id}, |
| 'metric': 'bps', |
| 'bw-max': bw_max}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', |
| 'id': queue_id}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node_id}, |
| 'handle': {'scope': 'queue', 'id': queue_id}, |
| 'weight': 1}) |
| queue_id += 1 |
| |
| if limit_err: |
| print(f"# max nesting depth supported: {max_depth} (errno {limit_err.error})") |
| else: |
| print(f"# max nesting depth tested: {max_depth}") |
| ksft_true(max_depth >= 2, |
| f"max nesting depth: {max_depth}") |
| |
| # Cleanup: exec the deferred deletes in reverse creation order, so each |
| # queue leaf and deeper node is removed before its parent node. |
| for cleanup in reversed(cleanups): |
| cleanup.exec() |
| ksft_eq(len(nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)), 0) |
| |
| def delete_child_reparent(cfg, nl_shaper) -> None: |
| r"""Deleting a child node reparents its queue leaf to the parent. |
| |
| netdev netdev |
| | | |
| N1 del N2 N1 |
| / | \ -----> / | \ |
| Q1 Q2 N2 Q1 Q2 Q3 |
| | |
| Q3 |
| """ |
| n1_bw_max = 10000 |
| n2_bw_max = 5000 |
| |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps', 'support-nesting'], |
| "device does not support node scope shapers with bw_max, metric bps and nesting") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| _require_queues(cfg, 4) |
| |
| # Create parent node N1 with Q1, Q2 |
| n1_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}, |
| {'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': n1_bw_max}) |
| n1_id = n1_handle['handle']['id'] |
| for i in range(1, 3): |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| |
| # Create child node N2 under N1 with Q3 |
| n2_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'parent': {'scope': 'node', 'id': n1_id}, |
| 'metric': 'bps', |
| 'bw-max': n2_bw_max}) |
| n2_id = n2_handle['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) |
| |
| # Delete child N2 - Q3 should reparent to N1 |
| nl_shaper.delete({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n2_id}}) |
| |
| with ksft_raises(NlError): |
| nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n2_id}}) |
| |
| shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n1_id}}) |
| ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n1_id}, |
| 'parent': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': n1_bw_max}) |
| shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 3}}) |
| ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n1_id}, |
| 'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}) |
| |
| # Cleanup |
| for i in range(1, 4): |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def move_queue_between_nodes(cfg, nl_shaper) -> None: |
| r"""Move a queue between nodes by re-grouping the destination node. |
| |
| netdev netdev |
| / \ .group N2 / \ |
| N1 N2 {Q1,Q3} N1 N2 |
| / \ | -------> | / \ |
| Q1 Q2 Q3 Q2 Q1 Q3 |
| """ |
| n1_bw_max = 10000 |
| n2_bw_max = 20000 |
| |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps', 'support-nesting'], |
| "device does not support node scope shapers with bw_max, metric bps and nesting") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| _require_queues(cfg, 4) |
| |
| # Create N1 with Q1, Q2 |
| n1_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}, |
| {'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': n1_bw_max}) |
| n1_id = n1_handle['handle']['id'] |
| for i in range(1, 3): |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| |
| # Create N2 with Q3 |
| n2_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': n2_bw_max}) |
| n2_id = n2_handle['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) |
| |
| # Move Q1 from N1 to N2 by re-grouping N2 with Q1, Q3 |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 2}, |
| {'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}], |
| 'handle': {'scope':'node', 'id': n2_id}, |
| 'metric': 'bps', |
| 'bw-max': n2_bw_max}) |
| |
| shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n1_id}}) |
| ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n1_id}, |
| 'parent': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': n1_bw_max}) |
| shaper_n2 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n2_id}}) |
| ksft_eq(shaper_n2, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': n2_id}, |
| 'parent': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': n2_bw_max}) |
| |
| # Verify Q1 moved to N2 |
| shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 1}}) |
| ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n2_id}, |
| 'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 2}) |
| |
| # Verify Q2 still under N1 |
| shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 2}}) |
| ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n1_id}, |
| 'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}) |
| |
| # Verify Q3 remained under N2 |
| shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 3}}) |
| ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': n2_id}, |
| 'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}) |
| |
| # Cleanup |
| for i in range(1, 4): |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def reject_reparenting(cfg, nl_shaper) -> None: |
| r"""Reject reparenting an existing node; the hierarchy stays intact. |
| |
| netdev |
| / \ rejected: N3 -> netdev |
| N1 N2 rejected: N1 -> N2 |
| / \ | (both EOPNOTSUPP) |
| Q1 N3 Q2 |
| | |
| Q3 |
| """ |
| node1_bw_max = 10000 |
| node2_bw_max = 5000 |
| node3_bw_max = 20000 |
| |
| _require_caps(cfg, nl_shaper, 'node', |
| ['support-bw-max', 'support-metric-bps', 'support-nesting'], |
| "device does not support node scope shapers with bw_max, metric bps and nesting") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| _require_queues(cfg, 4) |
| |
| # Create Node1 under netdev with Q1. |
| node1_id = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': node1_bw_max})['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node1_id}) |
| |
| # Create Node2 under netdev with Q2. |
| node2_id = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': node2_bw_max})['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 2}) |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node2_id}) |
| |
| # Create Node3 nested under Node1 with Q3. |
| node3_id = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': node3_bw_max, |
| 'parent': {'scope': 'node', 'id': node1_id}})['handle']['id'] |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) |
| defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node3_id}) |
| |
| # Reparenting a nested node up to netdev must fail. |
| with ksft_raises(NlError) as cm: |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}], |
| 'handle': {'scope':'node', 'id': node3_id}, |
| 'parent': {'scope': 'netdev'}}) |
| if cm.exception: |
| ksft_eq(cm.exception.error, errno.EOPNOTSUPP) |
| |
| # Reparenting a node under another node must fail as well. |
| with ksft_raises(NlError) as cm: |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 1}], |
| 'handle': {'scope':'node', 'id': node1_id}, |
| 'parent': {'scope': 'node', 'id': node2_id}}) |
| if cm.exception: |
| ksft_eq(cm.exception.error, errno.EOPNOTSUPP) |
| |
| # Updating a node with the same parent must succeed. |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 5}], |
| 'handle': {'scope':'node', 'id': node1_id}, |
| 'parent': {'scope': 'netdev'}}) |
| |
| # Updating a node without specifying the parent must succeed. |
| nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 7}], |
| 'handle': {'scope':'node', 'id': node2_id}}) |
| |
| # The rejected reparents must have left the hierarchy intact. |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node1_id}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node1_id}, |
| 'parent': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': node1_bw_max}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node2_id}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node2_id}, |
| 'parent': {'scope': 'netdev'}, |
| 'metric': 'bps', |
| 'bw-max': node2_bw_max}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node3_id}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'node', 'id': node3_id}, |
| 'parent': {'scope': 'node', 'id': node1_id}, |
| 'metric': 'bps', |
| 'bw-max': node3_bw_max}) |
| |
| # Verify the leaf weights were updated and parents unchanged. |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 1}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node1_id}, |
| 'handle': {'scope': 'queue', 'id': 1}, |
| 'weight': 5}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 2}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node2_id}, |
| 'handle': {'scope': 'queue', 'id': 2}, |
| 'weight': 7}) |
| shaper = nl_shaper.get({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': 3}}) |
| ksft_eq(shaper, {'ifindex': cfg.ifindex, |
| 'parent': {'scope': 'node', 'id': node3_id}, |
| 'handle': {'scope': 'queue', 'id': 3}, |
| 'weight': 1}) |
| |
| # Cleanup. Delete the nodes explicitly instead of relying on the |
| # empty-node auto-delete: a kernel that wrongly accepts a reparent may |
| # mishandle the leaf accounting and leave a node behind. Removing them |
| # by handle keeps a failing run from leaking state into later tests. |
| for i in range(1, 4): |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) |
| for nid in (node1_id, node2_id, node3_id): |
| _delete_shaper(cfg, nl_shaper, {'scope': 'node', 'id': nid}) |
| shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) |
| ksft_eq(len(shapers), 0) |
| |
| def queue_update(cfg, nl_shaper) -> None: |
| nq = _require_queues(cfg, 4) |
| if not cfg.queues: |
| raise KsftSkipEx("device does not support queue scope") |
| |
| netnl = EthtoolFamily() |
| channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) |
| ch_type = 'combined' if channels['combined-count'] else 'tx' |
| |
| for i in range(3): |
| nl_shaper.set({'ifindex': cfg.ifindex, |
| 'handle': {'scope': 'queue', 'id': i}, |
| 'metric': 'bps', |
| 'bw-max': (i + 1) * 1000}) |
| defer(cmd, f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") |
| |
| # Delete a channel, with no shapers configured on top of the related |
| # queue: no changes expected |
| cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 3") |
| 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']} {ch_type} 2") |
| |
| 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']} {ch_type} {nq}") |
| 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 dup_leaves(cfg, nl_shaper) -> None: |
| """ Ensure that the kernel rejects duplicate leaves. """ |
| _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], |
| "device does not support node scope shapers with bw_max and metric bps") |
| _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], |
| "device does not support nested queue scope shapers with weight") |
| |
| node_handle = None |
| with ksft_raises(NlError) as cm: |
| node_handle = nl_shaper.group({ |
| 'ifindex': cfg.ifindex, |
| 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 1}, |
| {'handle': {'scope': 'queue', 'id': 0}, |
| 'weight': 2}], |
| 'handle': {'scope':'node'}, |
| 'metric': 'bps', |
| 'bw-max': 10000}) |
| |
| # Clean up in case the kernel wrongly accepted the request. |
| if node_handle: |
| _delete_shaper(cfg, nl_shaper, node_handle['handle']) |
| _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) |
| |
| # ksft_raises() has already recorded the failure if nothing was raised. |
| if cm.exception is None: |
| return |
| ksft_eq(cm.exception.error, errno.EINVAL) |
| |
| def main() -> None: |
| with NetDrvEnv(__file__, queue_count=4) as cfg: |
| cfg.queues = False |
| cfg.netdev = False |
| ksft_run([get_shapers, |
| get_caps, |
| set_qshapers, |
| del_qshapers, |
| set_nshapers, |
| del_nshapers, |
| set_all_supported_attrs, |
| invalid_set_preserves_state, |
| mixed_parent_group_requires_parent, |
| recursive_empty_node_cleanup, |
| basic_groups, |
| basic_groups_with_rate, |
| qgroups, |
| set_node_shaper, |
| group_update_rate, |
| delegation, |
| nested_depth_limit, |
| delete_child_reparent, |
| move_queue_between_nodes, |
| reject_reparenting, |
| dup_leaves, |
| queue_update], |
| args=(cfg, NetshaperFamily())) |
| ksft_exit() |
| |
| |
| if __name__ == "__main__": |
| main() |