summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/drivers/net/shaper.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/drivers/net/shaper.py')
-rwxr-xr-xtools/testing/selftests/drivers/net/shaper.py1077
1 files changed, 994 insertions, 83 deletions
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 11310f19bfa0..a53316726f69 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -1,11 +1,54 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
+# pylint: disable=too-many-lines
-from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_true, KsftSkipEx
+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
+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:
@@ -41,17 +84,8 @@ def set_qshapers(cfg, nl_shaper) -> None:
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}")
+ _require_queues(cfg, 3)
+ cfg.queues = True
nl_shaper.set({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 1},
@@ -134,77 +168,370 @@ def del_nshapers(cfg, nl_shaper) -> None:
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}")
+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)
- 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")
+ _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': 1},
- 'weight': 1},
- {'handle': {'scope': 'queue', 'id': 2},
- 'weight': 2}],
- 'handle': {'scope':'netdev'},
- 'metric': 'bps',
- 'bw-max': 10000})
+ '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 })
+ 'weight': 1})
+ for dq in del_queues:
+ dq.exec()
- nl_shaper.delete({'ifindex': cfg.ifindex,
- 'handle': {'scope': 'queue', 'id': 2}})
- nl_shaper.delete({'ifindex': cfg.ifindex,
- 'handle': {'scope': 'queue', 'id': 1}})
+ # 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(len(shapers), 1)
+ ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+ 'handle': {'scope': 'netdev'},
+ 'metric': 'bps',
+ 'bw-max': bw_max}])
- nl_shaper.delete({'ifindex': cfg.ifindex,
- 'handle': {'scope': 'netdev'}})
+ del_node.exec()
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")
+ _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")
- cfg.groups = True;
node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
@@ -281,18 +608,116 @@ def qgroups(cfg, nl_shaper) -> None:
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:
- 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")
+ _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,
@@ -372,20 +797,466 @@ def delegation(cfg, nl_shaper) -> None:
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:
- if cfg.nr_queues < 4:
- raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}")
+ 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']} {cfg.rx_type} 3", timeout=10)
+ 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'},
@@ -405,7 +1276,7 @@ def queue_update(cfg, nl_shaper) -> None:
# 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)
+ 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,
@@ -420,7 +1291,7 @@ def queue_update(cfg, nl_shaper) -> None:
'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)
+ 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'},
@@ -438,22 +1309,62 @@ def queue_update(cfg, nl_shaper) -> None:
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
- cfg.groups = False
- cfg.nr_queues = 0
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,
- queue_update], args=(cfg, NetshaperFamily()))
+ nested_depth_limit,
+ delete_child_reparent,
+ move_queue_between_nodes,
+ reject_reparenting,
+ dup_leaves,
+ queue_update],
+ args=(cfg, NetshaperFamily()))
ksft_exit()