Commit b3ea4164 authored by Paolo Abeni's avatar Paolo Abeni Committed by Jakub Kicinski
Browse files

testing: net-drv: add basic shaper test



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

Reviewed-by: default avatarJiri Pirko <jiri@nvidia.com>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
Link: https://patch.msgid.link/43092afbf38365c796088bf8fc155e523ab434ae.1728460186.git.pabeni@redhat.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent ecd82cfe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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;

+39 −0
Original line number Diff line number Diff line
@@ -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 = {
+1 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ TEST_PROGS := \
	ping.py \
	queues.py \
	stats.py \
	shaper.py
# end of TEST_PROGS

include ../../lib.mk
+461 −0
Original line number Diff line number Diff line
#!/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()
Loading