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