testing: net-drv: add basic shaper test

Leverage a basic/dummy netdevsim implementation to do functional
coverage for NL interface.

Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Link: https://patch.msgid.link/43092afbf38365c796088bf8fc155e523ab434ae.1728460186.git.pabeni@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Paolo Abeni 2024-10-09 10:09:57 +02:00 committed by Jakub Kicinski
parent ecd82cfee3
commit b3ea416419
7 changed files with 510 additions and 0 deletions

View File

@ -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

View File

@ -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;

View File

@ -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 = {

View File

@ -9,6 +9,7 @@ TEST_PROGS := \
ping.py \
queues.py \
stats.py \
shaper.py
# end of TEST_PROGS
include ../../lib.mk

View File

@ -0,0 +1,461 @@
#!/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(f"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(f"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(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")
cfg.groups = 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.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")
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(f"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}])
# Final cleanup.
for i in range(0, 2):
nl_shaper.delete({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': i}})
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()

View File

@ -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

View File

@ -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='')