462 lines
19 KiB
Python
Executable File
462 lines
19 KiB
Python
Executable File
#!/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()
|