diff mbox series

[v7,net-next,11/15] testing: net-drv: add basic shaper test

Message ID 5f17d61004db141808b15d50485d0ccb69cbfa12.1725919039.git.pabeni@redhat.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net: introduce TX H/W shaping API | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next, async
netdev/ynl success Generated files up to date; no warnings/errors; GEN HAS DIFF 2 files changed, 1242 insertions(+);
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 54 this patch: 54
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 3 maintainers not CCed: shuah@kernel.org petrm@nvidia.com linux-kselftest@vger.kernel.org
netdev/build_clang success Errors and warnings before: 101 this patch: 101
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 4066 this patch: 4066
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest fail net-next-2024-09-10--21-00 (tests: 724)

Commit Message

Paolo Abeni Sept. 9, 2024, 10:10 p.m. UTC
Leverage a basic/dummy netdevsim implementation to do functional
coverage for NL interface.

Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
v5 -> v6:
  - additional test-cases for delegation and queue reconf

v4 -> v5:
  - updated to new driver API
  - more consistent indentation

rfc v1 -> v2:
  - added more test-cases WRT nesting and grouping
---
 drivers/net/Kconfig                           |   1 +
 drivers/net/netdevsim/ethtool.c               |   2 +
 drivers/net/netdevsim/netdev.c                |  39 ++
 tools/testing/selftests/drivers/net/Makefile  |   1 +
 tools/testing/selftests/drivers/net/shaper.py | 457 ++++++++++++++++++
 .../testing/selftests/net/lib/py/__init__.py  |   1 +
 tools/testing/selftests/net/lib/py/ynl.py     |   5 +
 7 files changed, 506 insertions(+)
 create mode 100755 tools/testing/selftests/drivers/net/shaper.py

Comments

Stanislav Fomichev Sept. 10, 2024, 9:41 p.m. UTC | #1
On 09/10, Paolo Abeni wrote:
> Leverage a basic/dummy netdevsim implementation to do functional
> coverage for NL interface.
> 
> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
> ---
> v5 -> v6:
>   - additional test-cases for delegation and queue reconf
> 
> v4 -> v5:
>   - updated to new driver API
>   - more consistent indentation
> 
> rfc v1 -> v2:
>   - added more test-cases WRT nesting and grouping
> ---
>  drivers/net/Kconfig                           |   1 +
>  drivers/net/netdevsim/ethtool.c               |   2 +
>  drivers/net/netdevsim/netdev.c                |  39 ++
>  tools/testing/selftests/drivers/net/Makefile  |   1 +
>  tools/testing/selftests/drivers/net/shaper.py | 457 ++++++++++++++++++
>  .../testing/selftests/net/lib/py/__init__.py  |   1 +
>  tools/testing/selftests/net/lib/py/ynl.py     |   5 +
>  7 files changed, 506 insertions(+)
>  create mode 100755 tools/testing/selftests/drivers/net/shaper.py
> 
> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> index 9920b3a68ed1..1fd5acdc73c6 100644
> --- a/drivers/net/Kconfig
> +++ b/drivers/net/Kconfig
> @@ -641,6 +641,7 @@ config NETDEVSIM
>  	depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
>  	select NET_DEVLINK
>  	select PAGE_POOL
> +	select NET_SHAPER
>  	help
>  	  This driver is a developer testing tool and software model that can
>  	  be used to test various control path networking APIs, especially
> diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
> index 1436905bc106..5fe1eaef99b5 100644
> --- a/drivers/net/netdevsim/ethtool.c
> +++ b/drivers/net/netdevsim/ethtool.c
> @@ -103,8 +103,10 @@ nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch)
>  	struct netdevsim *ns = netdev_priv(dev);
>  	int err;
>  
> +	mutex_lock(&dev->lock);
>  	err = netif_set_real_num_queues(dev, ch->combined_count,
>  					ch->combined_count);
> +	mutex_unlock(&dev->lock);
>  	if (err)
>  		return err;
>  
> diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
> index 017a6102be0a..cad85bb0cf54 100644
> --- a/drivers/net/netdevsim/netdev.c
> +++ b/drivers/net/netdevsim/netdev.c
> @@ -22,6 +22,7 @@
>  #include <net/netdev_queues.h>
>  #include <net/page_pool/helpers.h>
>  #include <net/netlink.h>
> +#include <net/net_shaper.h>
>  #include <net/pkt_cls.h>
>  #include <net/rtnetlink.h>
>  #include <net/udp_tunnel.h>
> @@ -475,6 +476,43 @@ static int nsim_stop(struct net_device *dev)
>  	return 0;
>  }
>  
> +static int nsim_shaper_set(struct net_shaper_binding *binding,
> +			   const struct net_shaper *shaper,
> +			   struct netlink_ext_ack *extack)
> +{
> +	return 0;
> +}
> +
> +static int nsim_shaper_del(struct net_shaper_binding *binding,
> +			   const struct net_shaper_handle *handle,
> +			   struct netlink_ext_ack *extack)
> +{
> +	return 0;
> +}
> +
> +static int nsim_shaper_group(struct net_shaper_binding *binding,
> +			     int leaves_count,
> +			     const struct net_shaper *leaves,
> +			     const struct net_shaper *root,
> +			     struct netlink_ext_ack *extack)
> +{
> +	return 0;
> +}
> +
> +static void nsim_shaper_cap(struct net_shaper_binding *binding,
> +			    enum net_shaper_scope scope,
> +			    unsigned long *flags)
> +{
> +	*flags = ULONG_MAX;
> +}
> +
> +static const struct net_shaper_ops nsim_shaper_ops = {
> +	.set			= nsim_shaper_set,
> +	.delete			= nsim_shaper_del,
> +	.group			= nsim_shaper_group,
> +	.capabilities		= nsim_shaper_cap,
> +};
> +
>  static const struct net_device_ops nsim_netdev_ops = {
>  	.ndo_start_xmit		= nsim_start_xmit,
>  	.ndo_set_rx_mode	= nsim_set_rx_mode,
> @@ -496,6 +534,7 @@ static const struct net_device_ops nsim_netdev_ops = {
>  	.ndo_bpf		= nsim_bpf,
>  	.ndo_open		= nsim_open,
>  	.ndo_stop		= nsim_stop,
> +	.net_shaper_ops		= &nsim_shaper_ops,
>  };
>  
>  static const struct net_device_ops nsim_vf_netdev_ops = {
> diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
> index 39fb97a8c1df..25aec5c081df 100644
> --- a/tools/testing/selftests/drivers/net/Makefile
> +++ b/tools/testing/selftests/drivers/net/Makefile
> @@ -9,6 +9,7 @@ TEST_PROGS := \
>  	ping.py \
>  	queues.py \
>  	stats.py \
> +	shaper.py
>  # end of TEST_PROGS
>  
>  include ../../lib.mk
> diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
> new file mode 100755
> index 000000000000..3504d51985bc
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/shaper.py
> @@ -0,0 +1,457 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_true, KsftSkipEx
> +from lib.py import EthtoolFamily, NetshaperFamily
> +from lib.py import NetDrvEnv
> +from lib.py import NlError
> +from lib.py import cmd
> +
> +def get_shapers(cfg, nl_shaper) -> None:
> +    try:
> +        shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftSkipEx("shapers not supported by the device")
> +        raise
> +
> +    # Default configuration: no shapers configured.
> +    ksft_eq(len(shapers), 0)
> +
> +def get_caps(cfg, nl_shaper) -> None:
> +    try:
> +        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex}, dump=True)
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftSkipEx("shapers not supported by the device")
> +        raise
> +
> +    # Each device implementing shaper support must support some
> +    # features in at least a scope.
> +    ksft_true(len(caps)> 0)
> +
> +def set_qshapers(cfg, nl_shaper) -> None:
> +    try:
> +        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
> +                                 'scope':'queue'})
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftSkipEx("shapers not supported by the device")
> +        raise
> +    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
> +        raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps")
> +
> +    cfg.queues = True;
> +    netnl = EthtoolFamily()
> +    channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}})
> +    if channels['combined-count'] == 0:
> +        cfg.rx_type = 'rx'
> +        cfg.nr_queues = channels['rx-count']
> +    else:
> +        cfg.rx_type = 'combined'
> +        cfg.nr_queues = channels['combined-count']
> +    if cfg.nr_queues < 3:
> +        raise KsftSkipEx("device does not support enough queues min 3 found {cfg.nr_queues}")
> +
> +    nl_shaper.set({'ifindex': cfg.ifindex,
> +                   'handle': {'scope': 'queue', 'id': 1},
> +                   'metric': 'bps',
> +                   'bw-max': 10000})
> +    nl_shaper.set({'ifindex': cfg.ifindex,
> +                   'handle': {'scope': 'queue', 'id': 2},
> +                   'metric': 'bps',
> +                   'bw-max': 20000})
> +
> +    # Querying a specific shaper not yet configured must fail.
> +    raised = False
> +    try:
> +        shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
> +                                   'handle': {'scope': 'queue', 'id': 0}})
> +    except (NlError):
> +        raised = True
> +    ksft_eq(raised, True)
> +
> +    shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
> +                              'handle': {'scope': 'queue', 'id': 1}})

[..]

> +    ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
> +                        'parent': {'scope': 'netdev'},
> +                        'handle': {'scope': 'queue', 'id': 1},
> +                        'metric': 'bps',
> +                        'bw-max': 10000})
> +

Before comparison, you probably need to drop some fields that are not
expected? 

# # Check failed {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-min': 517778718638633216, 'bw-max': 10000, 'burst': 18446683600580769792, 'priority': 60858368, 'weight': 4294936704} != {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-max': 10000} 
# # Check| At /home/virtme/testing-18/tools/testing/selftests/drivers/net/./shaper.py, line 83, in set_qshapers:
# # Check|     ksft_eq(shapers, [{'ifindex': cfg.ifindex,
# # Check failed [{'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-min': 517778718638633216, 'bw-max': 10000, 'burst': 18446683600580769792, 'priority': 60858368, 'weight': 4294936704}, {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 2}, 'metric': 'bps', 'bw-min': 517778718638633216, 'bw-max': 20000, 'burst': 18446683600580769792, 'priority': 60858368, 'weight': 4294936704}] != [{'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-max': 10000}, {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 2}, 'metric': 'bps', 'bw-max': 20000}]

https://netdev-3.bots.linux.dev/vmksft-net-drv-dbg/results/766702/4-shaper-py/stdout

Debug builds are also reporting a UBSAN error:

https://netdev-3.bots.linux.dev/vmksft-net-drv-dbg/results/766702/4-shaper-py/stderr

---
pw-bot: cr
Paolo Abeni Sept. 19, 2024, 3:02 p.m. UTC | #2
On 9/10/24 23:41, Stanislav Fomichev wrote:
> On 09/10, Paolo Abeni wrote:
>> Leverage a basic/dummy netdevsim implementation to do functional
>> coverage for NL interface.
>>
>> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
>> ---
>> v5 -> v6:
>>    - additional test-cases for delegation and queue reconf
>>
>> v4 -> v5:
>>    - updated to new driver API
>>    - more consistent indentation
>>
>> rfc v1 -> v2:
>>    - added more test-cases WRT nesting and grouping
>> ---
>>   drivers/net/Kconfig                           |   1 +
>>   drivers/net/netdevsim/ethtool.c               |   2 +
>>   drivers/net/netdevsim/netdev.c                |  39 ++
>>   tools/testing/selftests/drivers/net/Makefile  |   1 +
>>   tools/testing/selftests/drivers/net/shaper.py | 457 ++++++++++++++++++
>>   .../testing/selftests/net/lib/py/__init__.py  |   1 +
>>   tools/testing/selftests/net/lib/py/ynl.py     |   5 +
>>   7 files changed, 506 insertions(+)
>>   create mode 100755 tools/testing/selftests/drivers/net/shaper.py
>>
>> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
>> index 9920b3a68ed1..1fd5acdc73c6 100644
>> --- a/drivers/net/Kconfig
>> +++ b/drivers/net/Kconfig
>> @@ -641,6 +641,7 @@ config NETDEVSIM
>>   	depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
>>   	select NET_DEVLINK
>>   	select PAGE_POOL
>> +	select NET_SHAPER
>>   	help
>>   	  This driver is a developer testing tool and software model that can
>>   	  be used to test various control path networking APIs, especially
>> diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
>> index 1436905bc106..5fe1eaef99b5 100644
>> --- a/drivers/net/netdevsim/ethtool.c
>> +++ b/drivers/net/netdevsim/ethtool.c
>> @@ -103,8 +103,10 @@ nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch)
>>   	struct netdevsim *ns = netdev_priv(dev);
>>   	int err;
>>   
>> +	mutex_lock(&dev->lock);
>>   	err = netif_set_real_num_queues(dev, ch->combined_count,
>>   					ch->combined_count);
>> +	mutex_unlock(&dev->lock);
>>   	if (err)
>>   		return err;
>>   
>> diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
>> index 017a6102be0a..cad85bb0cf54 100644
>> --- a/drivers/net/netdevsim/netdev.c
>> +++ b/drivers/net/netdevsim/netdev.c
>> @@ -22,6 +22,7 @@
>>   #include <net/netdev_queues.h>
>>   #include <net/page_pool/helpers.h>
>>   #include <net/netlink.h>
>> +#include <net/net_shaper.h>
>>   #include <net/pkt_cls.h>
>>   #include <net/rtnetlink.h>
>>   #include <net/udp_tunnel.h>
>> @@ -475,6 +476,43 @@ static int nsim_stop(struct net_device *dev)
>>   	return 0;
>>   }
>>   
>> +static int nsim_shaper_set(struct net_shaper_binding *binding,
>> +			   const struct net_shaper *shaper,
>> +			   struct netlink_ext_ack *extack)
>> +{
>> +	return 0;
>> +}
>> +
>> +static int nsim_shaper_del(struct net_shaper_binding *binding,
>> +			   const struct net_shaper_handle *handle,
>> +			   struct netlink_ext_ack *extack)
>> +{
>> +	return 0;
>> +}
>> +
>> +static int nsim_shaper_group(struct net_shaper_binding *binding,
>> +			     int leaves_count,
>> +			     const struct net_shaper *leaves,
>> +			     const struct net_shaper *root,
>> +			     struct netlink_ext_ack *extack)
>> +{
>> +	return 0;
>> +}
>> +
>> +static void nsim_shaper_cap(struct net_shaper_binding *binding,
>> +			    enum net_shaper_scope scope,
>> +			    unsigned long *flags)
>> +{
>> +	*flags = ULONG_MAX;
>> +}
>> +
>> +static const struct net_shaper_ops nsim_shaper_ops = {
>> +	.set			= nsim_shaper_set,
>> +	.delete			= nsim_shaper_del,
>> +	.group			= nsim_shaper_group,
>> +	.capabilities		= nsim_shaper_cap,
>> +};
>> +
>>   static const struct net_device_ops nsim_netdev_ops = {
>>   	.ndo_start_xmit		= nsim_start_xmit,
>>   	.ndo_set_rx_mode	= nsim_set_rx_mode,
>> @@ -496,6 +534,7 @@ static const struct net_device_ops nsim_netdev_ops = {
>>   	.ndo_bpf		= nsim_bpf,
>>   	.ndo_open		= nsim_open,
>>   	.ndo_stop		= nsim_stop,
>> +	.net_shaper_ops		= &nsim_shaper_ops,
>>   };
>>   
>>   static const struct net_device_ops nsim_vf_netdev_ops = {
>> diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
>> index 39fb97a8c1df..25aec5c081df 100644
>> --- a/tools/testing/selftests/drivers/net/Makefile
>> +++ b/tools/testing/selftests/drivers/net/Makefile
>> @@ -9,6 +9,7 @@ TEST_PROGS := \
>>   	ping.py \
>>   	queues.py \
>>   	stats.py \
>> +	shaper.py
>>   # end of TEST_PROGS
>>   
>>   include ../../lib.mk
>> diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
>> new file mode 100755
>> index 000000000000..3504d51985bc
>> --- /dev/null
>> +++ b/tools/testing/selftests/drivers/net/shaper.py
>> @@ -0,0 +1,457 @@
>> +#!/usr/bin/env python3
>> +# SPDX-License-Identifier: GPL-2.0
>> +
>> +from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_true, KsftSkipEx
>> +from lib.py import EthtoolFamily, NetshaperFamily
>> +from lib.py import NetDrvEnv
>> +from lib.py import NlError
>> +from lib.py import cmd
>> +
>> +def get_shapers(cfg, nl_shaper) -> None:
>> +    try:
>> +        shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
>> +    except NlError as e:
>> +        if e.error == 95:
>> +            raise KsftSkipEx("shapers not supported by the device")
>> +        raise
>> +
>> +    # Default configuration: no shapers configured.
>> +    ksft_eq(len(shapers), 0)
>> +
>> +def get_caps(cfg, nl_shaper) -> None:
>> +    try:
>> +        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex}, dump=True)
>> +    except NlError as e:
>> +        if e.error == 95:
>> +            raise KsftSkipEx("shapers not supported by the device")
>> +        raise
>> +
>> +    # Each device implementing shaper support must support some
>> +    # features in at least a scope.
>> +    ksft_true(len(caps)> 0)
>> +
>> +def set_qshapers(cfg, nl_shaper) -> None:
>> +    try:
>> +        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
>> +                                 'scope':'queue'})
>> +    except NlError as e:
>> +        if e.error == 95:
>> +            raise KsftSkipEx("shapers not supported by the device")
>> +        raise
>> +    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
>> +        raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps")
>> +
>> +    cfg.queues = True;
>> +    netnl = EthtoolFamily()
>> +    channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}})
>> +    if channels['combined-count'] == 0:
>> +        cfg.rx_type = 'rx'
>> +        cfg.nr_queues = channels['rx-count']
>> +    else:
>> +        cfg.rx_type = 'combined'
>> +        cfg.nr_queues = channels['combined-count']
>> +    if cfg.nr_queues < 3:
>> +        raise KsftSkipEx("device does not support enough queues min 3 found {cfg.nr_queues}")
>> +
>> +    nl_shaper.set({'ifindex': cfg.ifindex,
>> +                   'handle': {'scope': 'queue', 'id': 1},
>> +                   'metric': 'bps',
>> +                   'bw-max': 10000})
>> +    nl_shaper.set({'ifindex': cfg.ifindex,
>> +                   'handle': {'scope': 'queue', 'id': 2},
>> +                   'metric': 'bps',
>> +                   'bw-max': 20000})
>> +
>> +    # Querying a specific shaper not yet configured must fail.
>> +    raised = False
>> +    try:
>> +        shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
>> +                                   'handle': {'scope': 'queue', 'id': 0}})
>> +    except (NlError):
>> +        raised = True
>> +    ksft_eq(raised, True)
>> +
>> +    shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
>> +                              'handle': {'scope': 'queue', 'id': 1}})
> 
> [..]
> 
>> +    ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
>> +                        'parent': {'scope': 'netdev'},
>> +                        'handle': {'scope': 'queue', 'id': 1},
>> +                        'metric': 'bps',
>> +                        'bw-max': 10000})
>> +
> 
> Before comparison, you probably need to drop some fields that are not
> expected?
> 
> # # Check failed {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-min': 517778718638633216, 'bw-max': 10000, 'burst': 18446683600580769792, 'priority': 60858368, 'weight': 4294936704} != {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-max': 10000}
> # # Check| At /home/virtme/testing-18/tools/testing/selftests/drivers/net/./shaper.py, line 83, in set_qshapers:
> # # Check|     ksft_eq(shapers, [{'ifindex': cfg.ifindex,
> # # Check failed [{'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-min': 517778718638633216, 'bw-max': 10000, 'burst': 18446683600580769792, 'priority': 60858368, 'weight': 4294936704}, {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 2}, 'metric': 'bps', 'bw-min': 517778718638633216, 'bw-max': 20000, 'burst': 18446683600580769792, 'priority': 60858368, 'weight': 4294936704}] != [{'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'metric': 'bps', 'bw-max': 10000}, {'ifindex': 8, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 2}, 'metric': 'bps', 'bw-max': 20000}]
> 
> https://netdev-3.bots.linux.dev/vmksft-net-drv-dbg/results/766702/4-shaper-py/stdout
> 
> Debug builds are also reporting a UBSAN error:
> 
> https://netdev-3.bots.linux.dev/vmksft-net-drv-dbg/results/766702/4-shaper-py/stderr

Thanks for the feedback, and sorry for the late reply, I was traveling.

It looks like the root cause is the same, a couple of stack allocated 
structs are not zeroed before usage.

For the record I could not reproduce the issue locally, because I 
probably miss some debug kconf.

I'll address the issue in the next iteration.

Thanks!

Paolo
diff mbox series

Patch

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 9920b3a68ed1..1fd5acdc73c6 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -641,6 +641,7 @@  config NETDEVSIM
 	depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
 	select NET_DEVLINK
 	select PAGE_POOL
+	select NET_SHAPER
 	help
 	  This driver is a developer testing tool and software model that can
 	  be used to test various control path networking APIs, especially
diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 1436905bc106..5fe1eaef99b5 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -103,8 +103,10 @@  nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch)
 	struct netdevsim *ns = netdev_priv(dev);
 	int err;
 
+	mutex_lock(&dev->lock);
 	err = netif_set_real_num_queues(dev, ch->combined_count,
 					ch->combined_count);
+	mutex_unlock(&dev->lock);
 	if (err)
 		return err;
 
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index 017a6102be0a..cad85bb0cf54 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -22,6 +22,7 @@ 
 #include <net/netdev_queues.h>
 #include <net/page_pool/helpers.h>
 #include <net/netlink.h>
+#include <net/net_shaper.h>
 #include <net/pkt_cls.h>
 #include <net/rtnetlink.h>
 #include <net/udp_tunnel.h>
@@ -475,6 +476,43 @@  static int nsim_stop(struct net_device *dev)
 	return 0;
 }
 
+static int nsim_shaper_set(struct net_shaper_binding *binding,
+			   const struct net_shaper *shaper,
+			   struct netlink_ext_ack *extack)
+{
+	return 0;
+}
+
+static int nsim_shaper_del(struct net_shaper_binding *binding,
+			   const struct net_shaper_handle *handle,
+			   struct netlink_ext_ack *extack)
+{
+	return 0;
+}
+
+static int nsim_shaper_group(struct net_shaper_binding *binding,
+			     int leaves_count,
+			     const struct net_shaper *leaves,
+			     const struct net_shaper *root,
+			     struct netlink_ext_ack *extack)
+{
+	return 0;
+}
+
+static void nsim_shaper_cap(struct net_shaper_binding *binding,
+			    enum net_shaper_scope scope,
+			    unsigned long *flags)
+{
+	*flags = ULONG_MAX;
+}
+
+static const struct net_shaper_ops nsim_shaper_ops = {
+	.set			= nsim_shaper_set,
+	.delete			= nsim_shaper_del,
+	.group			= nsim_shaper_group,
+	.capabilities		= nsim_shaper_cap,
+};
+
 static const struct net_device_ops nsim_netdev_ops = {
 	.ndo_start_xmit		= nsim_start_xmit,
 	.ndo_set_rx_mode	= nsim_set_rx_mode,
@@ -496,6 +534,7 @@  static const struct net_device_ops nsim_netdev_ops = {
 	.ndo_bpf		= nsim_bpf,
 	.ndo_open		= nsim_open,
 	.ndo_stop		= nsim_stop,
+	.net_shaper_ops		= &nsim_shaper_ops,
 };
 
 static const struct net_device_ops nsim_vf_netdev_ops = {
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
index 39fb97a8c1df..25aec5c081df 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -9,6 +9,7 @@  TEST_PROGS := \
 	ping.py \
 	queues.py \
 	stats.py \
+	shaper.py
 # end of TEST_PROGS
 
 include ../../lib.mk
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
new file mode 100755
index 000000000000..3504d51985bc
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -0,0 +1,457 @@ 
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_true, KsftSkipEx
+from lib.py import EthtoolFamily, NetshaperFamily
+from lib.py import NetDrvEnv
+from lib.py import NlError
+from lib.py import cmd
+
+def get_shapers(cfg, nl_shaper) -> None:
+    try:
+        shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+
+    # Default configuration: no shapers configured.
+    ksft_eq(len(shapers), 0)
+
+def get_caps(cfg, nl_shaper) -> None:
+    try:
+        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex}, dump=True)
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+
+    # Each device implementing shaper support must support some
+    # features in at least a scope.
+    ksft_true(len(caps)> 0)
+
+def set_qshapers(cfg, nl_shaper) -> None:
+    try:
+        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
+                                 'scope':'queue'})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
+        raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps")
+
+    cfg.queues = True;
+    netnl = EthtoolFamily()
+    channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+    if channels['combined-count'] == 0:
+        cfg.rx_type = 'rx'
+        cfg.nr_queues = channels['rx-count']
+    else:
+        cfg.rx_type = 'combined'
+        cfg.nr_queues = channels['combined-count']
+    if cfg.nr_queues < 3:
+        raise KsftSkipEx("device does not support enough queues min 3 found {cfg.nr_queues}")
+
+    nl_shaper.set({'ifindex': cfg.ifindex,
+                   'handle': {'scope': 'queue', 'id': 1},
+                   'metric': 'bps',
+                   'bw-max': 10000})
+    nl_shaper.set({'ifindex': cfg.ifindex,
+                   'handle': {'scope': 'queue', 'id': 2},
+                   'metric': 'bps',
+                   'bw-max': 20000})
+
+    # Querying a specific shaper not yet configured must fail.
+    raised = False
+    try:
+        shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
+                                   'handle': {'scope': 'queue', 'id': 0}})
+    except (NlError):
+        raised = True
+    ksft_eq(raised, True)
+
+    shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
+                              'handle': {'scope': 'queue', 'id': 1}})
+    ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
+                        'parent': {'scope': 'netdev'},
+                        'handle': {'scope': 'queue', 'id': 1},
+                        'metric': 'bps',
+                        'bw-max': 10000})
+
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 1},
+                       'metric': 'bps',
+                       'bw-max': 10000},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 2},
+                       'metric': 'bps',
+                       'bw-max': 20000}])
+
+def del_qshapers(cfg, nl_shaper) -> None:
+    if not cfg.queues:
+        raise KsftSkipEx("queue shapers not supported by device, skipping delete")
+
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 2}})
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 1}})
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(len(shapers), 0)
+
+def set_nshapers(cfg, nl_shaper) -> None:
+    # Check required features.
+    try:
+        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
+                                  'scope':'netdev'})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
+        raise KsftSkipEx("device does not support nested netdev scope shapers with weight")
+
+    cfg.netdev = True;
+    nl_shaper.set({'ifindex': cfg.ifindex,
+                   'handle': {'scope': 'netdev', 'id': 0},
+                   'bw-max': 100000})
+
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'handle': {'scope': 'netdev'},
+                       'metric': 'bps',
+                       'bw-max': 100000}])
+
+def del_nshapers(cfg, nl_shaper) -> None:
+    if not cfg.netdev:
+        raise KsftSkipEx("netdev shaper not supported by device, skipping delete")
+
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'netdev'}})
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(len(shapers), 0)
+
+def basic_groups(cfg, nl_shaper) -> None:
+    if not cfg.netdev:
+        raise KsftSkipEx("netdev shaper not supported by the device")
+    if cfg.nr_queues < 3:
+        raise KsftSkipEx("netdev does not have enough queues min 3 reported {cfg.nr_queues}")
+
+    try:
+        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
+                                  'scope':'queue'})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+    if not 'support-weight' in caps:
+        raise KsftSkipEx("device does not support queue scope shapers with weight")
+
+    node_handle = nl_shaper.group({
+                        'ifindex': cfg.ifindex,
+                        'leaves':[{'handle': {'scope': 'queue', 'id': 1},
+                                   'weight': 1},
+                                  {'handle': {'scope': 'queue', 'id': 2},
+                                   'weight': 2}],
+                         'handle': {'scope':'netdev'},
+                         'metric': 'bps',
+                         'bw-max': 10000})
+    ksft_eq(node_handle, {'ifindex': cfg.ifindex,
+                          'handle': {'scope': 'netdev'}})
+
+    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
+                            'handle': {'scope': 'queue', 'id': 1}})
+    ksft_eq(shaper, {'ifindex': cfg.ifindex,
+                     'parent': {'scope': 'netdev'},
+                     'handle': {'scope': 'queue', 'id': 1},
+                     'weight': 1 })
+
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 2}})
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 1}})
+
+    # Deleting all the leaves shaper does not affect the node one
+    # when the latter has 'netdev' scope.
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(len(shapers), 1)
+
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'netdev'}})
+
+def qgroups(cfg, nl_shaper) -> None:
+    if cfg.nr_queues < 4:
+        raise KsftSkipEx("netdev does not have enough queues min 4 reported {cfg.nr_queues}")
+    try:
+        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
+                                  'scope':'node'})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
+        raise KsftSkipEx("device does not support node scope shapers with bw_max and metric bps")
+    try:
+        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
+                                  'scope':'queue'})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+    if not 'support-nesting' in caps or not 'support-weight' in caps or not 'support-metric-bps' in caps:
+            raise KsftSkipEx("device does not support nested queue scope shapers with weight")
+
+    cfg.group = True;
+    node_handle = nl_shaper.group({
+                   'ifindex': cfg.ifindex,
+                   'leaves':[{'handle': {'scope': 'queue', 'id': 1},
+                              'weight': 3},
+                             {'handle': {'scope': 'queue', 'id': 2},
+                              'weight': 2}],
+                   'handle': {'scope':'node'},
+                   'metric': 'bps',
+                   'bw-max': 10000})
+    node_id = node_handle['handle']['id']
+
+    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
+                            'handle': {'scope': 'queue', 'id': 1}})
+    ksft_eq(shaper, {'ifindex': cfg.ifindex,
+                     'parent': {'scope': 'node', 'id': node_id},
+                     'handle': {'scope': 'queue', 'id': 1},
+                     'weight': 3})
+    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
+                            'handle': {'scope': 'node', 'id': node_id}})
+    ksft_eq(shaper, {'ifindex': cfg.ifindex,
+                     'handle': {'scope': 'node', 'id': node_id},
+                     'parent': {'scope': 'netdev'},
+                     'metric': 'bps',
+                     'bw-max': 10000})
+
+    # Grouping to a specified, not existing node scope shaper must fail
+    raised = False
+    try:
+        nl_shaper.group({
+                   'ifindex': cfg.ifindex,
+                   'leaves':[{'handle': {'scope': 'queue', 'id': 3},
+                              'weight': 3}],
+                   'handle': {'scope':'node', 'id': node_id + 1},
+                   'metric': 'bps',
+                   'bw-max': 10000})
+
+    except (NlError):
+        raised = True
+    ksft_eq(raised, True)
+
+    # Add to an existing node
+    node_handle = nl_shaper.group({
+                   'ifindex': cfg.ifindex,
+                   'leaves':[{'handle': {'scope': 'queue', 'id': 3},
+                              'weight': 4}],
+                   'handle': {'scope':'node', 'id': node_id}})
+    ksft_eq(node_handle, {'ifindex': cfg.ifindex,
+                          'handle': {'scope': 'node', 'id': node_id}})
+
+    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
+                            'handle': {'scope': 'queue', 'id': 3}})
+    ksft_eq(shaper, {'ifindex': cfg.ifindex,
+                     'parent': {'scope': 'node', 'id': node_id},
+                     'handle': {'scope': 'queue', 'id': 3},
+                     'weight': 4})
+
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 2}})
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 1}})
+
+    # Deleting a non empty node will move the leaves downstream.
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'node', 'id': node_id}})
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 3},
+                       'weight': 4}])
+
+    # Finish and verify the complete cleanup.
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'queue', 'id': 3}})
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(len(shapers), 0)
+
+def delegation(cfg, nl_shaper) -> None:
+    if not cfg.group:
+        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:
+            cfg.group = False;
+            raise KsftSkipEx("shapers not supported by the device")
+        raise
+    if not 'support-nesting' in caps:
+        raise KsftSkipEx("device does not support node scope shapers nesting")
+
+    node_handle = nl_shaper.group({
+                   'ifindex': cfg.ifindex,
+                   'leaves':[{'handle': {'scope': 'queue', 'id': 1},
+                              'weight': 3},
+                             {'handle': {'scope': 'queue', 'id': 2},
+                              'weight': 2},
+                             {'handle': {'scope': 'queue', 'id': 3},
+                              'weight': 1}],
+                   'handle': {'scope':'node'},
+                   'metric': 'bps',
+                   'bw-max': 10000})
+    node_id = node_handle['handle']['id']
+
+    # Create the nested node and validate the hierarchy
+    nested_node_handle = nl_shaper.group({
+                   'ifindex': cfg.ifindex,
+                   'leaves':[{'handle': {'scope': 'queue', 'id': 1},
+                              'weight': 3},
+                             {'handle': {'scope': 'queue', 'id': 2},
+                              'weight': 2}],
+                   'handle': {'scope':'node'},
+                   'metric': 'bps',
+                   'bw-max': 5000})
+    nested_node_id = nested_node_handle['handle']['id']
+    ksft_true(nested_node_id != node_id)
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': nested_node_id},
+                       'handle': {'scope': 'queue', 'id': 1},
+                       'weight': 3},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': nested_node_id},
+                       'handle': {'scope': 'queue', 'id': 2},
+                       'weight': 2},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': node_id},
+                       'handle': {'scope': 'queue', 'id': 3},
+                       'weight': 1},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'node', 'id': node_id},
+                       'metric': 'bps',
+                       'bw-max': 10000},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': node_id},
+                       'handle': {'scope': 'node', 'id': nested_node_id},
+                       'metric': 'bps',
+                       'bw-max': 5000}])
+
+    # Deleting a non empty node will move the leaves downstream.
+    nl_shaper.delete({'ifindex': cfg.ifindex,
+                      'handle': {'scope': 'node', 'id': nested_node_id}})
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': node_id},
+                       'handle': {'scope': 'queue', 'id': 1},
+                       'weight': 3},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': node_id},
+                       'handle': {'scope': 'queue', 'id': 2},
+                       'weight': 2},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'node', 'id': node_id},
+                       'handle': {'scope': 'queue', 'id': 3},
+                       'weight': 1},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'node', 'id': node_id},
+                       'metric': 'bps',
+                       'bw-max': 10000}])
+
+    # Final cleanup.
+    for i in range(1, 4):
+        nl_shaper.delete({'ifindex': cfg.ifindex,
+                          'handle': {'scope': 'queue', 'id': i}})
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(len(shapers), 0)
+
+def queue_update(cfg, nl_shaper) -> None:
+    if cfg.nr_queues < 4:
+        raise KsftSkipEx("netdev does not have enough queues min 4 reported {cfg.nr_queues}")
+    if not cfg.queues:
+        raise KsftSkipEx("device does not support queue scope")
+
+    for i in range(3):
+        nl_shaper.set({'ifindex': cfg.ifindex,
+                       'handle': {'scope': 'queue', 'id': i},
+                       'metric': 'bps',
+                       'bw-max': (i + 1) * 1000})
+    # Delete a channel, with no shapers configured on top of the related
+    # queue: no changes expected
+    cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3", timeout=10)
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 0},
+                       'metric': 'bps',
+                       'bw-max': 1000},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 1},
+                       'metric': 'bps',
+                       'bw-max': 2000},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 2},
+                       'metric': 'bps',
+                       'bw-max': 3000}])
+
+    # Delete a channel, with a shaper configured on top of the related
+    # queue: the shaper must be deleted, too
+    cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2", timeout=10)
+
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 0},
+                       'metric': 'bps',
+                       'bw-max': 1000},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 1},
+                       'metric': 'bps',
+                       'bw-max': 2000}])
+
+    # Restore the original channels number, no expected changes
+    cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}", timeout=10)
+    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 0},
+                       'metric': 'bps',
+                       'bw-max': 1000},
+                      {'ifindex': cfg.ifindex,
+                       'parent': {'scope': 'netdev'},
+                       'handle': {'scope': 'queue', 'id': 1},
+                       'metric': 'bps',
+                       'bw-max': 2000}])
+
+def main() -> None:
+    with NetDrvEnv(__file__, queue_count=4) as cfg:
+        cfg.queues = False
+        cfg.netdev = False
+        cfg.groups = False
+        cfg.nr_queues = 0
+        ksft_run([get_shapers,
+                  get_caps,
+                  set_qshapers,
+                  del_qshapers,
+                  set_nshapers,
+                  del_nshapers,
+                  basic_groups,
+                  qgroups,
+                  delegation,
+                  queue_update], args=(cfg, NetshaperFamily()))
+    ksft_exit()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index b6d498d125fe..54d8f5eba810 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -6,3 +6,4 @@  from .netns import NetNS
 from .nsim import *
 from .utils import *
 from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
+from .ynl import NetshaperFamily
diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py
index 1ace58370c06..a0d689d58c57 100644
--- a/tools/testing/selftests/net/lib/py/ynl.py
+++ b/tools/testing/selftests/net/lib/py/ynl.py
@@ -47,3 +47,8 @@  class NetdevFamily(YnlFamily):
     def __init__(self):
         super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(),
                          schema='')
+
+class NetshaperFamily(YnlFamily):
+    def __init__(self):
+        super().__init__((SPEC_PATH / Path('net_shaper.yaml')).as_posix(),
+                         schema='')