All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mohsin Bashir <mohsin.bashr@gmail.com>
To: netdev@vger.kernel.org
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org, pabeni@redhat.com,
	shuah@kernel.org, alexander.duyck@gmail.com
Subject: [PATCH net-next 10/14] selftests: net: shaper: Add reparenting rejection test
Date: Tue,  4 Aug 2026 20:09:32 -0700	[thread overview]
Message-ID: <20260805030936.1092907-11-mohsin.bashr@gmail.com> (raw)
In-Reply-To: <20260805030936.1092907-1-mohsin.bashr@gmail.com>

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


  parent reply	other threads:[~2026-08-05  3:10 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Mohsin Bashir [this message]
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
2026-08-07 23:50 ` [PATCH net-next 00/14] selftests: net: shaper: Expand shaper API coverage patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260805030936.1092907-11-mohsin.bashr@gmail.com \
    --to=mohsin.bashr@gmail.com \
    --cc=alexander.duyck@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=shuah@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.