* [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage
@ 2026-08-05 3:09 Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 01/14] selftests: net: shaper: Drop redundant command timeouts Mohsin Bashir
` (13 more replies)
0 siblings, 14 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add more net shaper selftest coverage for group operations and nested
node management.
The series first prepares shared cleanup and capability helpers, and
separates the basic netdev grouping coverage from the rate-limited
variant. It then adds tests for updating node shaper rates through both
.set and .group, discovering the supported nesting depth, deleting child
nodes and reparenting their leaves, moving queues between nodes, and
rejecting node reparenting.
Further patches broaden API coverage: exercising the full set of scalar
shaper attributes, rejecting invalid .set requests while leaving the
existing configuration intact, grouping leaves drawn from different
parents (which requires an explicit parent), and recursively cleaning up
nodes left empty.
The new tests use the capability helper to skip unsupported devices
instead of depending on earlier test ordering, size their queue
requirements from the number of TX queues exposed in sysfs, and register
cleanup for created shapers as soon as the operation succeeds.
TAP version 13
1..22
ok 1 shaper.get_shapers
ok 2 shaper.get_caps
ok 3 shaper.set_qshapers
ok 4 shaper.del_qshapers
ok 5 shaper.set_nshapers
ok 6 shaper.del_nshapers
ok 7 shaper.set_all_supported_attrs
ok 8 shaper.invalid_set_preserves_state
ok 9 shaper.mixed_parent_group_requires_parent
ok 10 shaper.recursive_empty_node_cleanup
ok 11 shaper.basic_groups
ok 12 shaper.basic_groups_with_rate
ok 13 shaper.qgroups
ok 14 shaper.set_node_shaper
ok 15 shaper.group_update_rate
ok 16 shaper.delegation
# max nesting depth tested: 3
ok 17 shaper.nested_depth_limit
ok 18 shaper.delete_child_reparent
ok 19 shaper.move_queue_between_nodes
ok 20 shaper.reject_reparenting
ok 21 shaper.dup_leaves
ok 22 shaper.queue_update
Mohsin Bashir (14):
selftests: net: shaper: Drop redundant command timeouts
selftests: net: shaper: Prepare helpers for group tests
selftests: net: shaper: Decouple basic_groups from netdev rate
limiting
selftests: net: shaper: Add basic_groups_with_rate test
selftests: net: shaper: Add node scope .set rate update test
selftests: net: shaper: Add .group rate update test
selftests: net: shaper: Add nested depth limit discovery test
selftests: net: shaper: Add child node deletion reparent test
selftests: net: shaper: Add queue migration between nodes test
selftests: net: shaper: Add reparenting rejection test
selftests: net: shaper: Cover scalar attributes
selftests: net: shaper: Reject invalid set requests
selftests: net: shaper: Cover mixed-parent grouping
selftests: net: shaper: Cover recursive node cleanup
tools/testing/selftests/drivers/net/shaper.py | 1065 +++++++++++++++--
1 file changed, 978 insertions(+), 87 deletions(-)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next 01/14] selftests: net: shaper: Drop redundant command timeouts
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 02/14] selftests: net: shaper: Prepare helpers for group tests Mohsin Bashir
` (12 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Commit 57bb59ab6fa3 ("selftests: net: bump default cmd() timeout to 20
seconds") raised the default cmd() timeout to 20 seconds, so the explicit
timeout=10 passed to the ethtool channel commands in queue_update() is
now redundant and, in fact, shorter than the default. Drop it and rely
on the default timeout.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index e39d270e688d..c80a4bf8cc05 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -388,7 +388,7 @@ def queue_update(cfg, nl_shaper) -> None:
'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)
+ cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3")
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(shapers, [{'ifindex': cfg.ifindex,
'parent': {'scope': 'netdev'},
@@ -408,7 +408,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']} {cfg.rx_type} 2")
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(shapers, [{'ifindex': cfg.ifindex,
@@ -423,7 +423,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']} {cfg.rx_type} {cfg.nr_queues}")
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(shapers, [{'ifindex': cfg.ifindex,
'parent': {'scope': 'netdev'},
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 02/14] selftests: net: shaper: Prepare helpers for group tests
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 01/14] selftests: net: shaper: Drop redundant command timeouts Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 03/14] selftests: net: shaper: Decouple basic_groups from netdev rate limiting Mohsin Bashir
` (11 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
dup_leaves expects the kernel to reject a group request that lists the same
queue twice. When that rejection does not happen, ksft_raises only records
a failed check and leaves cm.exception as None, so the following errno
check raises AttributeError. Worse, the accepted group request leaves a
node shaper and queue 0 behind, which makes later tests fail for an
unrelated reason. Handle the negative test explicitly instead. If group
fails, verify that the errno is EINVAL and return. If group succeeds,
delete the node returned by the operation and queue 0 before reporting the
failure.
Give the duplicate leaves different weights so the request still contains
two distinct leaf entries while exercising duplicate handle validation.
This also introduces _delete_shaper(), cached _cap_get(), and
_require_caps() helpers as preparation for the following shaper group
tests. The follow-on tests need the same capability checks for node and
queue scope support. Keeping that logic in one place avoids repeating raw
EOPNOTSUPP handling in each test.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 145 +++++++++++-------
1 file changed, 86 insertions(+), 59 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index c80a4bf8cc05..1954f3263f25 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -2,13 +2,52 @@
# SPDX-License-Identifier: GPL-2.0
import errno
+import glob
from lib.py import ksft_run, ksft_exit
-from lib.py import ksft_eq, ksft_raises, ksft_true, KsftSkipEx
+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:
@@ -44,17 +83,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},
@@ -140,8 +170,7 @@ def del_nshapers(cfg, nl_shaper) -> None:
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}")
+ _require_queues(cfg, 3)
try:
caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
@@ -186,28 +215,14 @@ def basic_groups(cfg, nl_shaper) -> None:
'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")
+ _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},
@@ -285,17 +300,12 @@ def qgroups(cfg, nl_shaper) -> None:
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,
@@ -376,19 +386,24 @@ def delegation(cfg, nl_shaper) -> None:
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")
+ 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'},
@@ -408,7 +423,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")
+ 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,
@@ -423,7 +438,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}")
+ 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'},
@@ -443,25 +458,37 @@ def queue_update(cfg, nl_shaper) -> None:
def dup_leaves(cfg, nl_shaper) -> None:
""" Ensure that the kernel rejects duplicate leaves. """
- if not cfg.groups:
- raise KsftSkipEx("device does not support node scope")
+ _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:
- nl_shaper.group({
+ node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
- 'leaves':[{'handle': {'scope': 'queue', 'id': 0}},
- {'handle': {'scope': 'queue', 'id': 0}}],
+ '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,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 03/14] selftests: net: shaper: Decouple basic_groups from netdev rate limiting
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 01/14] selftests: net: shaper: Drop redundant command timeouts Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 02/14] selftests: net: shaper: Prepare helpers for group tests Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 04/14] selftests: net: shaper: Add basic_groups_with_rate test Mohsin Bashir
` (10 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Decouple basic_groups from the set_nshapers test dependency. The
test was gated on cfg.netdev which is set by set_nshapers. Replace
with direct capability checks: netdev scope support (required for
grouping under netdev handle) and queue scope nesting + weight.
Remove bw-max and metric from the .group call so the test validates
pure queue grouping without rate limiting. The rate-limited variant is
restored in the following patch, which adds a dedicated
basic_groups_with_rate test.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 40 ++++++++-----------
1 file changed, 16 insertions(+), 24 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 1954f3263f25..45a4bf42995e 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -168,19 +168,11 @@ def del_nshapers(cfg, nl_shaper) -> None:
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")
_require_queues(cfg, 3)
- 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, '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")
node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
@@ -188,31 +180,31 @@ def basic_groups(cfg, nl_shaper) -> None:
'weight': 1},
{'handle': {'scope': 'queue', 'id': 2},
'weight': 2}],
- 'handle': {'scope':'netdev'},
- 'metric': 'bps',
- 'bw-max': 10000})
+ 'handle': {'scope':'netdev'}})
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()
- 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)
+ ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+ 'handle': {'scope': 'netdev'}}])
- nl_shaper.delete({'ifindex': cfg.ifindex,
- 'handle': {'scope': 'netdev'}})
+ del_node.exec()
+ shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+ ksft_eq(len(shapers), 0)
def qgroups(cfg, nl_shaper) -> None:
_require_queues(cfg, 4)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 04/14] selftests: net: shaper: Add basic_groups_with_rate test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (2 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 03/14] selftests: net: shaper: Decouple basic_groups from netdev rate limiting Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 05/14] selftests: net: shaper: Add node scope .set rate update test Mohsin Bashir
` (9 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add a test that groups queues under the netdev parent with rate
limiting enabled. Extract the common group-under-netdev flow into
_group_under_netdev helper to share with basic_groups.
The test independently checks for netdev scope bw_max and metric
capabilities before proceeding, and verifies that the netdev
shaper persists after leaf deletion.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 83 +++++++++++++++----
1 file changed, 68 insertions(+), 15 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 45a4bf42995e..168a8dd057e5 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -167,20 +167,25 @@ 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:
- _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")
-
- 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'}})
+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'}})
@@ -194,10 +199,29 @@ def basic_groups(cfg, nl_shaper) -> None:
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()
+ # 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'}}])
@@ -206,6 +230,34 @@ def basic_groups(cfg, nl_shaper) -> None:
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',
@@ -488,6 +540,7 @@ def main() -> None:
set_nshapers,
del_nshapers,
basic_groups,
+ basic_groups_with_rate,
qgroups,
delegation,
dup_leaves,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 05/14] selftests: net: shaper: Add node scope .set rate update test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (3 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 04/14] selftests: net: shaper: Add basic_groups_with_rate test Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 06/14] selftests: net: shaper: Add .group " Mohsin Bashir
` (8 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add set_node_shaper to test updating a NODE scope shaper's rate
via the .set callback. Creates a node group with bw_max=10000,
updates to 20000 via .set, and verifies the change.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 168a8dd057e5..62ac83b7701c 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -343,6 +343,43 @@ 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 delegation(cfg, nl_shaper) -> None:
_require_queues(cfg, 4)
_require_caps(cfg, nl_shaper, 'node',
@@ -542,6 +579,7 @@ def main() -> None:
basic_groups,
basic_groups_with_rate,
qgroups,
+ set_node_shaper,
delegation,
dup_leaves,
queue_update],
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 06/14] selftests: net: shaper: Add .group rate update test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (4 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 05/14] selftests: net: shaper: Add node scope .set rate update test Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 07/14] selftests: net: shaper: Add nested depth limit discovery test Mohsin Bashir
` (7 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add group_update_rate to test updating an existing node's rate
via the .group callback. Creates a node with bw_max=10000,
re-groups with bw_max=50000, and verifies the rate changed while
leaves remain under the same node.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 62ac83b7701c..5eccbe437ba3 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -380,6 +380,72 @@ def set_node_shaper(cfg, nl_shaper) -> None:
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',
@@ -580,6 +646,7 @@ def main() -> None:
basic_groups_with_rate,
qgroups,
set_node_shaper,
+ group_update_rate,
delegation,
dup_leaves,
queue_update],
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 07/14] selftests: net: shaper: Add nested depth limit discovery test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (5 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 06/14] selftests: net: shaper: Add .group " Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 08/14] selftests: net: shaper: Add child node deletion reparent test Mohsin Bashir
` (6 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add nested_depth_limit to incrementally create deeper nesting
levels until the driver rejects. Reports the maximum supported
nesting depth on both pass and fail. A device advertising nesting
support must support at least depth 2, otherwise nesting is
meaningless.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 117 ++++++++++++++++++
1 file changed, 117 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 5eccbe437ba3..3b72661202f9 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -532,6 +532,122 @@ 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 queue_update(cfg, nl_shaper) -> None:
nq = _require_queues(cfg, 4)
if not cfg.queues:
@@ -648,6 +764,7 @@ def main() -> None:
set_node_shaper,
group_update_rate,
delegation,
+ nested_depth_limit,
dup_leaves,
queue_update],
args=(cfg, NetshaperFamily()))
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 08/14] selftests: net: shaper: Add child node deletion reparent test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (6 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 07/14] selftests: net: shaper: Add nested depth limit discovery test Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 09/14] selftests: net: shaper: Add queue migration between nodes test Mohsin Bashir
` (5 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add delete_child_reparent to verify that deleting a child node
reparents its queue leaves to the parent node. Creates a two-level
hierarchy (N1 with Q1,Q2 and child N2 with Q3), deletes N2, and
verifies Q3's parent becomes N1.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 77 +++++++++++++++++++
1 file changed, 77 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 3b72661202f9..1b88e183f1b9 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -648,6 +648,82 @@ def nested_depth_limit(cfg, nl_shaper) -> None:
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 queue_update(cfg, nl_shaper) -> None:
nq = _require_queues(cfg, 4)
if not cfg.queues:
@@ -765,6 +841,7 @@ def main() -> None:
group_update_rate,
delegation,
nested_depth_limit,
+ delete_child_reparent,
dup_leaves,
queue_update],
args=(cfg, NetshaperFamily()))
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 09/14] selftests: net: shaper: Add queue migration between nodes test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (7 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 08/14] selftests: net: shaper: Add child node deletion reparent test Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 10/14] selftests: net: shaper: Add reparenting rejection test Mohsin Bashir
` (4 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add move_queue_between_nodes to verify that a queue can be moved
from one node to another via re-grouping. Creates N1 with Q1,Q2
and N2 with Q3, then re-groups N2 with Q1,Q3 to steal Q1 from
N1. Verifies Q1 moved to N2 and Q2 remains under N1.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 1b88e183f1b9..62c74a0c0563 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -724,6 +724,107 @@ def delete_child_reparent(cfg, nl_shaper) -> None:
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 queue_update(cfg, nl_shaper) -> None:
nq = _require_queues(cfg, 4)
if not cfg.queues:
@@ -842,6 +943,7 @@ def main() -> None:
delegation,
nested_depth_limit,
delete_child_reparent,
+ move_queue_between_nodes,
dup_leaves,
queue_update],
args=(cfg, NetshaperFamily()))
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 10/14] selftests: net: shaper: Add reparenting rejection test
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (8 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 09/14] selftests: net: shaper: Add queue migration between nodes test Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 11/14] selftests: net: shaper: Cover scalar attributes Mohsin Bashir
` (3 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add reject_reparenting to verify that the group operation rejects attempts
to change an existing node's parent. The test creates two node shapers
under netdev and verifies that re-grouping the first node under the second
fails with EOPNOTSUPP. It also verifies that updating the node with the
same parent succeeds, and that updating the node without specifying a
parent keeps the queue leaves under the original node while updating their
weights.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 150 ++++++++++++++++++
1 file changed, 150 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 62c74a0c0563..e7af94264409 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
+# pylint: disable=too-many-lines
import errno
import glob
@@ -825,6 +826,154 @@ def move_queue_between_nodes(cfg, nl_shaper) -> None:
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:
@@ -944,6 +1093,7 @@ def main() -> None:
nested_depth_limit,
delete_child_reparent,
move_queue_between_nodes,
+ reject_reparenting,
dup_leaves,
queue_update],
args=(cfg, NetshaperFamily()))
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 11/14] selftests: net: shaper: Cover scalar attributes
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (9 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 10/14] selftests: net: shaper: Add reparenting rejection test Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 12/14] selftests: net: shaper: Reject invalid set requests Mohsin Bashir
` (2 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Exercise queue-scope scalar shaper attributes reported by the device,
including rate limits, burst, priority and weight. Build the set request
from advertised capabilities so devices are tested for the attributes they
claim rather than skipped for missing unrelated fields.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index e7af94264409..8dd4897e999e 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -168,6 +168,73 @@ def del_nshapers(cfg, nl_shaper) -> None:
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 _group_under_netdev(cfg, nl_shaper, bw_max=None):
r"""Group queues under a netdev-scope node; caller owns node teardown.
@@ -1084,6 +1151,7 @@ def main() -> None:
del_qshapers,
set_nshapers,
del_nshapers,
+ set_all_supported_attrs,
basic_groups,
basic_groups_with_rate,
qgroups,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 12/14] selftests: net: shaper: Reject invalid set requests
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (10 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 11/14] selftests: net: shaper: Cover scalar attributes Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 13/14] selftests: net: shaper: Cover mixed-parent grouping Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 14/14] selftests: net: shaper: Cover recursive node cleanup Mohsin Bashir
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Verify that invalid set requests fail without corrupting existing queue
shaper state. The test covers invalid node creation through set and invalid
queue identifiers, then confirms the original queue configuration remains
unchanged.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 8dd4897e999e..02a11e6b9a05 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -235,6 +235,46 @@ def set_all_supported_attrs(cfg, nl_shaper) -> None:
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 _group_under_netdev(cfg, nl_shaper, bw_max=None):
r"""Group queues under a netdev-scope node; caller owns node teardown.
@@ -1152,6 +1192,7 @@ def main() -> None:
set_nshapers,
del_nshapers,
set_all_supported_attrs,
+ invalid_set_preserves_state,
basic_groups,
basic_groups_with_rate,
qgroups,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 13/14] selftests: net: shaper: Cover mixed-parent grouping
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (11 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 12/14] selftests: net: shaper: Reject invalid set requests Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 14/14] selftests: net: shaper: Cover recursive node cleanup Mohsin Bashir
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Add coverage for grouping leaves that currently belong to different parent
nodes. The test verifies that an implicit parent is rejected, an explicit
parent succeeds, and the old empty parent nodes are cleaned up.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 100 ++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 02a11e6b9a05..9264aeb74a7a 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -275,6 +275,105 @@ def invalid_set_preserves_state(cfg, nl_shaper) -> None:
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 _group_under_netdev(cfg, nl_shaper, bw_max=None):
r"""Group queues under a netdev-scope node; caller owns node teardown.
@@ -1193,6 +1292,7 @@ def main() -> None:
del_nshapers,
set_all_supported_attrs,
invalid_set_preserves_state,
+ mixed_parent_group_requires_parent,
basic_groups,
basic_groups_with_rate,
qgroups,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next 14/14] selftests: net: shaper: Cover recursive node cleanup
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
` (12 preceding siblings ...)
2026-08-05 3:09 ` [PATCH net-next 13/14] selftests: net: shaper: Cover mixed-parent grouping Mohsin Bashir
@ 2026-08-05 3:09 ` Mohsin Bashir
13 siblings, 0 replies; 15+ messages in thread
From: Mohsin Bashir @ 2026-08-05 3:09 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel,
linux-kselftest, pabeni, shuah, alexander.duyck
From: Mohsin Bashir <hmohsin@meta.com>
Exercise cleanup of nested nodes after deleting their last queue leaf. The
test builds a two-level node hierarchy and checks that removing the queue
also removes both now-empty node shapers.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
---
tools/testing/selftests/drivers/net/shaper.py | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 9264aeb74a7a..a53316726f69 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -374,6 +374,64 @@ def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None:
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.
@@ -1293,6 +1351,7 @@ def main() -> None:
set_all_supported_attrs,
invalid_set_preserves_state,
mixed_parent_group_requires_parent,
+ recursive_empty_node_cleanup,
basic_groups,
basic_groups_with_rate,
qgroups,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-08-05 3:10 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 3:09 [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 01/14] selftests: net: shaper: Drop redundant command timeouts Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 02/14] selftests: net: shaper: Prepare helpers for group tests Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 03/14] selftests: net: shaper: Decouple basic_groups from netdev rate limiting Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 04/14] selftests: net: shaper: Add basic_groups_with_rate test Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 05/14] selftests: net: shaper: Add node scope .set rate update test Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 06/14] selftests: net: shaper: Add .group " Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 07/14] selftests: net: shaper: Add nested depth limit discovery test Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 08/14] selftests: net: shaper: Add child node deletion reparent test Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 09/14] selftests: net: shaper: Add queue migration between nodes test Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 10/14] selftests: net: shaper: Add reparenting rejection test Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 11/14] selftests: net: shaper: Cover scalar attributes Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 12/14] selftests: net: shaper: Reject invalid set requests Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 13/14] selftests: net: shaper: Cover mixed-parent grouping Mohsin Bashir
2026-08-05 3:09 ` [PATCH net-next 14/14] selftests: net: shaper: Cover recursive node cleanup Mohsin Bashir
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox