#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# pylint: disable=too-many-lines
import errno
import glob
from lib.py import ksft_run, ksft_exit
from lib.py import ksft_eq, ksft_true, ksft_raises, KsftSkipEx
from lib.py import EthtoolFamily, NetshaperFamily
from lib.py import NetDrvEnv
from lib.py import NlError
from lib.py import cmd, defer
def _delete_shaper(cfg, nl_shaper, handle) -> None:
""" Delete the shaper identified by handle, ignoring a missing-shaper error. """
try:
nl_shaper.delete({'ifindex': cfg.ifindex,
'handle': handle})
except NlError as e:
if e.error != errno.ENOENT:
raise
def _require_queues(cfg, count):
""" Return the netdev TX queue count, skipping the test if fewer than count exist. """
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/tx-*"))
if qcnt < count:
raise KsftSkipEx(f"netdev has {qcnt} queues, {count} required")
return qcnt
def _cap_get(cfg, nl_shaper, scope):
""" Return the shaper capabilities for the given scope, caching them on cfg. """
if not hasattr(cfg, 'cap_cache'):
cfg.cap_cache = {}
if scope not in cfg.cap_cache:
cfg.cap_cache[scope] = nl_shaper.cap_get({'ifindex': cfg.ifindex,
'scope': scope})
return cfg.cap_cache[scope]
def _require_caps(cfg, nl_shaper, scope, caps, msg) -> None:
""" Skip the test unless the given scope advertises all the required caps. """
try:
supported = _cap_get(cfg, nl_shaper, scope)
except NlError as e:
if e.error == errno.EOPNOTSUPP:
raise KsftSkipEx(f"{scope} scope shapers not supported by the device")
raise
if not set(caps).issubset(supported):
raise KsftSkipEx(msg)
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")
_require_queues(cfg, 3)
cfg.queues = True
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 set_all_supported_attrs(cfg, nl_shaper) -> None:
""" Set every queue-scope attribute the device advertises and verify the read-back. """
_require_queues(cfg, 1)
_require_caps(cfg, nl_shaper, 'queue', [],
"queue scope shapers not supported by the device")
caps = _cap_get(cfg, nl_shaper, 'queue')
attrs = {'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}}
expected = {'ifindex': cfg.ifindex,
'parent': {'scope': 'netdev'},
'handle': {'scope': 'queue', 'id': 0}}
rate_attrs = {'support-bw-min': ('bw-min', 10000, 100),
'support-bw-max': ('bw-max', 20000, 200),
'support-burst': ('burst', 3000, 30)}
rate_attr_supported = any(cap in caps for cap in rate_attrs)
bps_supported = 'support-metric-bps' in caps
pps_supported = 'support-metric-pps' in caps
def add_rate_attrs(metric, value_idx) -> None:
attrs['metric'] = metric
expected['metric'] = metric
for cap, (attr, bps_value, pps_value) in rate_attrs.items():
if cap not in caps:
continue
value = bps_value if value_idx == 0 else pps_value
attrs[attr] = value
expected[attr] = value
if rate_attr_supported:
if bps_supported:
add_rate_attrs('bps', 0)
elif pps_supported:
add_rate_attrs('pps', 1)
if 'support-priority' in caps:
attrs['priority'] = 1
expected['priority'] = 1
if 'support-weight' in caps:
attrs['weight'] = 2
expected['weight'] = 2
if len(attrs) == 2:
raise KsftSkipEx("device does not advertise any supported queue shaper attributes")
nl_shaper.set(attrs)
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper, expected)
if rate_attr_supported and bps_supported and pps_supported:
add_rate_attrs('pps', 1)
nl_shaper.set(attrs)
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper, expected)
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def invalid_set_preserves_state(cfg, nl_shaper) -> None:
""" Verify a rejected .set leaves the existing shaper configuration unchanged. """
nq = _require_queues(cfg, 1)
_require_caps(cfg, nl_shaper, 'queue',
['support-bw-max', 'support-metric-bps'],
"device does not support queue scope bw_max with bps metric")
initial = {'ifindex': cfg.ifindex,
'parent': {'scope': 'netdev'},
'handle': {'scope': 'queue', 'id': 0},
'metric': 'bps',
'bw-max': 10000}
nl_shaper.set({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0},
'metric': 'bps',
'bw-max': 10000})
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0})
with ksft_raises(NlError):
nl_shaper.set({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': 0},
'metric': 'bps',
'bw-max': 20000})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper, initial)
with ksft_raises(NlError):
nl_shaper.set({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': nq},
'metric': 'bps',
'bw-max': 20000})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper, initial)
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None:
r"""Grouping leaves from different nodes requires an explicit parent.
netdev netdev
/ \ parent=netdev
N1 N2 group N
| | {Q0,Q1} / \
Q0 Q1 -------> Q0 Q1
Without an explicit parent the group is rejected; parent=netdev
collapses the leaves into one new node.
"""
_require_queues(cfg, 2)
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps'],
"device does not support node scope shapers with bw_max and metric bps")
_require_caps(cfg, nl_shaper, 'queue',
['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
n1_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 0},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 10000})
n1_id = n1_handle['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0})
n2_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 2}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 20000})
n2_id = n2_handle['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1})
with ksft_raises(NlError):
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 0},
'weight': 3},
{'handle': {'scope': 'queue', 'id': 1},
'weight': 4}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 30000})
shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper_q0, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n1_id},
'handle': {'scope': 'queue', 'id': 0},
'weight': 1})
shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 1}})
ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n2_id},
'handle': {'scope': 'queue', 'id': 1},
'weight': 2})
node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 0},
'weight': 3},
{'handle': {'scope': 'queue', 'id': 1},
'weight': 4}],
'handle': {'scope':'node'},
'parent': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': 30000})
node_id = node_handle['handle']['id']
for old_id in (n1_id, n2_id):
with ksft_raises(NlError):
nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': old_id}})
shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper_q0, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node_id},
'handle': {'scope': 'queue', 'id': 0},
'weight': 3})
shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 1}})
ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node_id},
'handle': {'scope': 'queue', 'id': 1},
'weight': 4})
for i in range(2):
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def recursive_empty_node_cleanup(cfg, nl_shaper) -> None:
r"""Deleting the last leaf recursively removes the emptied ancestors.
netdev netdev
| del Q0
N1 ------> (N1 and N2 removed too)
|
N2
|
Q0
"""
_require_queues(cfg, 1)
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps', 'support-nesting'],
"device does not support nested node scope shapers")
_require_caps(cfg, nl_shaper, 'queue',
['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
n1_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 0},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 10000})
n1_id = n1_handle['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0})
n2_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 0},
'weight': 1}],
'handle': {'scope':'node'},
'parent': {'scope': 'node', 'id': n1_id},
'metric': 'bps',
'bw-max': 5000})
n2_id = n2_handle['handle']['id']
shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
ksft_eq(shaper_q0, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n2_id},
'handle': {'scope': 'queue', 'id': 0},
'weight': 1})
nl_shaper.delete({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 0}})
for handle in ({'scope': 'queue', 'id': 0},
{'scope': 'node', 'id': n2_id},
{'scope': 'node', 'id': n1_id}):
with ksft_raises(NlError):
nl_shaper.get({'ifindex': cfg.ifindex, 'handle': handle})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def _group_under_netdev(cfg, nl_shaper, bw_max=None):
r"""Group queues under a netdev-scope node; caller owns node teardown.
netdev netdev
/ \ del Q1,Q2
Q1 Q2 -------> (netdev node persists)
"""
group_args = {
'ifindex': cfg.ifindex,
'leaves': [{'handle': {'scope': 'queue', 'id': 1},
'weight': 1},
{'handle': {'scope': 'queue', 'id': 2},
'weight': 2}],
'handle': {'scope': 'netdev'}}
if bw_max:
group_args['metric'] = 'bps'
group_args['bw-max'] = bw_max
node_handle = nl_shaper.group(group_args)
ksft_eq(node_handle, {'ifindex': cfg.ifindex,
'handle': {'scope': 'netdev'}})
del_node = defer(_delete_shaper, cfg, nl_shaper, {'scope': 'netdev'})
del_queues = [defer(_delete_shaper, cfg, nl_shaper,
{'scope': 'queue', 'id': qid})
for qid in (1, 2)]
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})
for dq in del_queues:
dq.exec()
# Caller owns the node teardown so it can verify the netdev-scope node
# survives leaf deletion before removing it.
return del_node
def basic_groups(cfg, nl_shaper) -> None:
r"""Group queues under a netdev-scope node, then tear it down.
netdev
/ \
Q1 Q2
"""
_require_queues(cfg, 3)
_require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"queue scope not supported with nesting and weight")
del_node = _group_under_netdev(cfg, nl_shaper)
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(shapers, [{'ifindex': cfg.ifindex,
'handle': {'scope': 'netdev'}}])
del_node.exec()
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def basic_groups_with_rate(cfg, nl_shaper) -> None:
r"""Rate-limited netdev-scope node outlives deletion of its leaves.
netdev[10kbps] netdev[10kbps]
/ \ del Q1,Q2
Q1 Q2 -------> (node persists)
"""
bw_max = 10000
_require_queues(cfg, 3)
_require_caps(cfg, nl_shaper, 'netdev', ['support-bw-max', 'support-metric-bps'],
"device does not support netdev scope rate limiting")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support queue scope shapers with nesting and weight")
del_node = _group_under_netdev(cfg, nl_shaper, bw_max=bw_max)
# 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(shapers, [{'ifindex': cfg.ifindex,
'handle': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': bw_max}])
del_node.exec()
def qgroups(cfg, nl_shaper) -> None:
_require_queues(cfg, 4)
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps'],
"device does not support node scope shapers with bw_max and metric bps")
_require_caps(cfg, nl_shaper, 'queue',
['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
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 set_node_shaper(cfg, nl_shaper) -> None:
""" Verify a node-scope shaper rate can be updated via .set. """
_require_queues(cfg, 2)
_require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'],
"device does not support node scope shapers with bw_max and metric bps")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 10000})
node_id = node_handle['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1})
# Update the node's rate via .set
nl_shaper.set({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node_id},
'metric': 'bps',
'bw-max': 20000})
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': 20000})
# Cleanup
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 1})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def group_update_rate(cfg, nl_shaper) -> None:
""" Verify re-grouping a node updates its rate while leaving the leaves untouched. """
_require_queues(cfg, 3)
_require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'],
"device does not support node scope shapers with bw_max and metric bps")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
# Create node with Q1, Q2 at bw_max=10000
node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1},
{'handle': {'scope': 'queue', 'id': 2},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 10000})
node_id = node_handle['handle']['id']
for i in range(1, 3):
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i})
# Update rate via .group on the same node
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1},
{'handle': {'scope': 'queue', 'id': 2},
'weight': 1}],
'handle': {'scope':'node', 'id': node_id},
'metric': 'bps',
'bw-max': 50000})
# Verify rate updated
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': 50000})
# Verify leaves unchanged
shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 1}})
ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node_id},
'handle': {'scope': 'queue', 'id': 1},
'weight': 1})
shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 2}})
ksft_eq(shaper_q2, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node_id},
'handle': {'scope': 'queue', 'id': 2},
'weight': 1})
# Make sure we only have 3 shapers including 2 queues and the node
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 3)
# Cleanup
for i in range(1, 3):
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def delegation(cfg, nl_shaper) -> None:
_require_queues(cfg, 4)
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps', 'support-nesting'],
"device does not support node scope shapers with bw_max, metric bps and nesting")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
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 nested_depth_limit(cfg, nl_shaper) -> None:
r"""Nest nodes as deep as the device allows to find the max depth.
netdev
|
N1 -- Q1
|
N2 -- Q2
|
N3 -- Q3
: (deepen until the driver rejects)
"""
bw_max = 10000
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps', 'support-nesting'],
"device does not support node scope shapers with bw_max, metric bps and nesting")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
nq = _require_queues(cfg, 3)
node_ids = []
cleanups = []
queue_id = 1
max_depth = 0
limit_err = None
# Create initial node with a queue leaf
node_id = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves': [{'handle': {'scope': 'queue', 'id': queue_id},
'weight': 1}],
'handle': {'scope': 'node'},
'metric': 'bps',
'bw-max': bw_max})['handle']['id']
node_ids.append(node_id)
cleanups.append(defer(_delete_shaper, cfg, nl_shaper,
{'scope': 'node', 'id': node_id}))
cleanups.append(defer(_delete_shaper, cfg, nl_shaper,
{'scope': 'queue', 'id': queue_id}))
max_depth = 1
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': bw_max})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': queue_id}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node_id},
'handle': {'scope': 'queue', 'id': queue_id},
'weight': 1})
queue_id += 1
# Keep nesting deeper until the driver rejects or queues run out.
while queue_id < nq:
parent_id = node_ids[-1]
try:
node_id = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves': [{'handle': {'scope': 'queue',
'id': queue_id},
'weight': 1}],
'handle': {'scope': 'node'},
'parent': {'scope': 'node',
'id': parent_id},
'metric': 'bps',
'bw-max': bw_max})['handle']['id']
except NlError as e:
# Only treat "cannot nest deeper" errors as the depth limit;
# drivers report it differently (EOPNOTSUPP/ENOSPC/E2BIG/EINVAL).
# Anything else (ENOMEM, EIO, EPERM, driver bug) is a real failure.
if e.error not in (errno.EOPNOTSUPP, errno.ENOSPC,
errno.E2BIG, errno.EINVAL):
raise
limit_err = e
break
node_ids.append(node_id)
cleanups.append(defer(_delete_shaper, cfg, nl_shaper,
{'scope': 'node', 'id': node_id}))
cleanups.append(defer(_delete_shaper, cfg, nl_shaper,
{'scope': 'queue', 'id': queue_id}))
max_depth += 1
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': 'node', 'id': parent_id},
'metric': 'bps',
'bw-max': bw_max})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue',
'id': queue_id}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node_id},
'handle': {'scope': 'queue', 'id': queue_id},
'weight': 1})
queue_id += 1
if limit_err:
print(f"# max nesting depth supported: {max_depth} (errno {limit_err.error})")
else:
print(f"# max nesting depth tested: {max_depth}")
ksft_true(max_depth >= 2,
f"max nesting depth: {max_depth}")
# Cleanup: exec the deferred deletes in reverse creation order, so each
# queue leaf and deeper node is removed before its parent node.
for cleanup in reversed(cleanups):
cleanup.exec()
ksft_eq(len(nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)), 0)
def delete_child_reparent(cfg, nl_shaper) -> None:
r"""Deleting a child node reparents its queue leaf to the parent.
netdev netdev
| |
N1 del N2 N1
/ | \ -----> / | \
Q1 Q2 N2 Q1 Q2 Q3
|
Q3
"""
n1_bw_max = 10000
n2_bw_max = 5000
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps', 'support-nesting'],
"device does not support node scope shapers with bw_max, metric bps and nesting")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
_require_queues(cfg, 4)
# Create parent node N1 with Q1, Q2
n1_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1},
{'handle': {'scope': 'queue', 'id': 2},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': n1_bw_max})
n1_id = n1_handle['handle']['id']
for i in range(1, 3):
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i})
# Create child node N2 under N1 with Q3
n2_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 3},
'weight': 1}],
'handle': {'scope':'node'},
'parent': {'scope': 'node', 'id': n1_id},
'metric': 'bps',
'bw-max': n2_bw_max})
n2_id = n2_handle['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3})
# Delete child N2 - Q3 should reparent to N1
nl_shaper.delete({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n2_id}})
with ksft_raises(NlError):
nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n2_id}})
shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n1_id}})
ksft_eq(shaper_n1, {'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n1_id},
'parent': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': n1_bw_max})
shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 3}})
ksft_eq(shaper_q3, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n1_id},
'handle': {'scope': 'queue', 'id': 3},
'weight': 1})
# Cleanup
for i in range(1, 4):
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def move_queue_between_nodes(cfg, nl_shaper) -> None:
r"""Move a queue between nodes by re-grouping the destination node.
netdev netdev
/ \ .group N2 / \
N1 N2 {Q1,Q3} N1 N2
/ \ | -------> | / \
Q1 Q2 Q3 Q2 Q1 Q3
"""
n1_bw_max = 10000
n2_bw_max = 20000
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps', 'support-nesting'],
"device does not support node scope shapers with bw_max, metric bps and nesting")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
_require_queues(cfg, 4)
# Create N1 with Q1, Q2
n1_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1},
{'handle': {'scope': 'queue', 'id': 2},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': n1_bw_max})
n1_id = n1_handle['handle']['id']
for i in range(1, 3):
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i})
# Create N2 with Q3
n2_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 3},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': n2_bw_max})
n2_id = n2_handle['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3})
# Move Q1 from N1 to N2 by re-grouping N2 with Q1, Q3
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 2},
{'handle': {'scope': 'queue', 'id': 3},
'weight': 1}],
'handle': {'scope':'node', 'id': n2_id},
'metric': 'bps',
'bw-max': n2_bw_max})
shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n1_id}})
ksft_eq(shaper_n1, {'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n1_id},
'parent': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': n1_bw_max})
shaper_n2 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n2_id}})
ksft_eq(shaper_n2, {'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': n2_id},
'parent': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': n2_bw_max})
# Verify Q1 moved to N2
shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 1}})
ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n2_id},
'handle': {'scope': 'queue', 'id': 1},
'weight': 2})
# Verify Q2 still under N1
shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 2}})
ksft_eq(shaper_q2, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n1_id},
'handle': {'scope': 'queue', 'id': 2},
'weight': 1})
# Verify Q3 remained under N2
shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 3}})
ksft_eq(shaper_q3, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': n2_id},
'handle': {'scope': 'queue', 'id': 3},
'weight': 1})
# Cleanup
for i in range(1, 4):
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def reject_reparenting(cfg, nl_shaper) -> None:
r"""Reject reparenting an existing node; the hierarchy stays intact.
netdev
/ \ rejected: N3 -> netdev
N1 N2 rejected: N1 -> N2
/ \ | (both EOPNOTSUPP)
Q1 N3 Q2
|
Q3
"""
node1_bw_max = 10000
node2_bw_max = 5000
node3_bw_max = 20000
_require_caps(cfg, nl_shaper, 'node',
['support-bw-max', 'support-metric-bps', 'support-nesting'],
"device does not support node scope shapers with bw_max, metric bps and nesting")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
_require_queues(cfg, 4)
# Create Node1 under netdev with Q1.
node1_id = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': node1_bw_max})['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1})
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node1_id})
# Create Node2 under netdev with Q2.
node2_id = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 2},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': node2_bw_max})['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 2})
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node2_id})
# Create Node3 nested under Node1 with Q3.
node3_id = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 3},
'weight': 1}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': node3_bw_max,
'parent': {'scope': 'node', 'id': node1_id}})['handle']['id']
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3})
defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node3_id})
# Reparenting a nested node up to netdev must fail.
with ksft_raises(NlError) as cm:
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 3},
'weight': 1}],
'handle': {'scope':'node', 'id': node3_id},
'parent': {'scope': 'netdev'}})
if cm.exception:
ksft_eq(cm.exception.error, errno.EOPNOTSUPP)
# Reparenting a node under another node must fail as well.
with ksft_raises(NlError) as cm:
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 1}],
'handle': {'scope':'node', 'id': node1_id},
'parent': {'scope': 'node', 'id': node2_id}})
if cm.exception:
ksft_eq(cm.exception.error, errno.EOPNOTSUPP)
# Updating a node with the same parent must succeed.
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 1},
'weight': 5}],
'handle': {'scope':'node', 'id': node1_id},
'parent': {'scope': 'netdev'}})
# Updating a node without specifying the parent must succeed.
nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 2},
'weight': 7}],
'handle': {'scope':'node', 'id': node2_id}})
# The rejected reparents must have left the hierarchy intact.
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node1_id}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node1_id},
'parent': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': node1_bw_max})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node2_id}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node2_id},
'parent': {'scope': 'netdev'},
'metric': 'bps',
'bw-max': node2_bw_max})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node3_id}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'handle': {'scope': 'node', 'id': node3_id},
'parent': {'scope': 'node', 'id': node1_id},
'metric': 'bps',
'bw-max': node3_bw_max})
# Verify the leaf weights were updated and parents unchanged.
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 1}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node1_id},
'handle': {'scope': 'queue', 'id': 1},
'weight': 5})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 2}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node2_id},
'handle': {'scope': 'queue', 'id': 2},
'weight': 7})
shaper = nl_shaper.get({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': 3}})
ksft_eq(shaper, {'ifindex': cfg.ifindex,
'parent': {'scope': 'node', 'id': node3_id},
'handle': {'scope': 'queue', 'id': 3},
'weight': 1})
# Cleanup. Delete the nodes explicitly instead of relying on the
# empty-node auto-delete: a kernel that wrongly accepts a reparent may
# mishandle the leaf accounting and leave a node behind. Removing them
# by handle keeps a failing run from leaking state into later tests.
for i in range(1, 4):
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i})
for nid in (node1_id, node2_id, node3_id):
_delete_shaper(cfg, nl_shaper, {'scope': 'node', 'id': nid})
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
def queue_update(cfg, nl_shaper) -> None:
nq = _require_queues(cfg, 4)
if not cfg.queues:
raise KsftSkipEx("device does not support queue scope")
netnl = EthtoolFamily()
channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}})
ch_type = 'combined' if channels['combined-count'] else 'tx'
for i in range(3):
nl_shaper.set({'ifindex': cfg.ifindex,
'handle': {'scope': 'queue', 'id': i},
'metric': 'bps',
'bw-max': (i + 1) * 1000})
defer(cmd, f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}")
# Delete a channel, with no shapers configured on top of the related
# queue: no changes expected
cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 3")
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']} {ch_type} 2")
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']} {ch_type} {nq}")
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 dup_leaves(cfg, nl_shaper) -> None:
""" Ensure that the kernel rejects duplicate leaves. """
_require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'],
"device does not support node scope shapers with bw_max and metric bps")
_require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'],
"device does not support nested queue scope shapers with weight")
node_handle = None
with ksft_raises(NlError) as cm:
node_handle = nl_shaper.group({
'ifindex': cfg.ifindex,
'leaves':[{'handle': {'scope': 'queue', 'id': 0},
'weight': 1},
{'handle': {'scope': 'queue', 'id': 0},
'weight': 2}],
'handle': {'scope':'node'},
'metric': 'bps',
'bw-max': 10000})
# Clean up in case the kernel wrongly accepted the request.
if node_handle:
_delete_shaper(cfg, nl_shaper, node_handle['handle'])
_delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0})
# ksft_raises() has already recorded the failure if nothing was raised.
if cm.exception is None:
return
ksft_eq(cm.exception.error, errno.EINVAL)
def main() -> None:
with NetDrvEnv(__file__, queue_count=4) as cfg:
cfg.queues = False
cfg.netdev = False
ksft_run([get_shapers,
get_caps,
set_qshapers,
del_qshapers,
set_nshapers,
del_nshapers,
set_all_supported_attrs,
invalid_set_preserves_state,
mixed_parent_group_requires_parent,
recursive_empty_node_cleanup,
basic_groups,
basic_groups_with_rate,
qgroups,
set_node_shaper,
group_update_rate,
delegation,
nested_depth_limit,
delete_child_reparent,
move_queue_between_nodes,
reject_reparenting,
dup_leaves,
queue_update],
args=(cfg, NetshaperFamily()))
ksft_exit()
if __name__ == "__main__":
main()