xref: /linux/tools/testing/selftests/drivers/net/shaper.py (revision 66182ca873a4e87b3496eca79d57f86b76d7f52d)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import errno
5
6from lib.py import ksft_run, ksft_exit
7from lib.py import ksft_eq, ksft_raises, ksft_true, KsftSkipEx
8from lib.py import EthtoolFamily, NetshaperFamily
9from lib.py import NetDrvEnv
10from lib.py import NlError
11from lib.py import cmd
12
13def get_shapers(cfg, nl_shaper) -> None:
14    try:
15        shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
16    except NlError as e:
17        if e.error == 95:
18            raise KsftSkipEx("shapers not supported by the device")
19        raise
20
21    # Default configuration: no shapers configured.
22    ksft_eq(len(shapers), 0)
23
24def get_caps(cfg, nl_shaper) -> None:
25    try:
26        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex}, dump=True)
27    except NlError as e:
28        if e.error == 95:
29            raise KsftSkipEx("shapers not supported by the device")
30        raise
31
32    # Each device implementing shaper support must support some
33    # features in at least a scope.
34    ksft_true(len(caps)> 0)
35
36def set_qshapers(cfg, nl_shaper) -> None:
37    try:
38        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
39                                 'scope':'queue'})
40    except NlError as e:
41        if e.error == 95:
42            raise KsftSkipEx("shapers not supported by the device")
43        raise
44    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
45        raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps")
46
47    cfg.queues = True;
48    netnl = EthtoolFamily()
49    channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}})
50    if channels['combined-count'] == 0:
51        cfg.rx_type = 'rx'
52        cfg.nr_queues = channels['rx-count']
53    else:
54        cfg.rx_type = 'combined'
55        cfg.nr_queues = channels['combined-count']
56    if cfg.nr_queues < 3:
57        raise KsftSkipEx(f"device does not support enough queues min 3 found {cfg.nr_queues}")
58
59    nl_shaper.set({'ifindex': cfg.ifindex,
60                   'handle': {'scope': 'queue', 'id': 1},
61                   'metric': 'bps',
62                   'bw-max': 10000})
63    nl_shaper.set({'ifindex': cfg.ifindex,
64                   'handle': {'scope': 'queue', 'id': 2},
65                   'metric': 'bps',
66                   'bw-max': 20000})
67
68    # Querying a specific shaper not yet configured must fail.
69    raised = False
70    try:
71        shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
72                                   'handle': {'scope': 'queue', 'id': 0}})
73    except (NlError):
74        raised = True
75    ksft_eq(raised, True)
76
77    shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex,
78                              'handle': {'scope': 'queue', 'id': 1}})
79    ksft_eq(shaper_q1, {'ifindex': cfg.ifindex,
80                        'parent': {'scope': 'netdev'},
81                        'handle': {'scope': 'queue', 'id': 1},
82                        'metric': 'bps',
83                        'bw-max': 10000})
84
85    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
86    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
87                       'parent': {'scope': 'netdev'},
88                       'handle': {'scope': 'queue', 'id': 1},
89                       'metric': 'bps',
90                       'bw-max': 10000},
91                      {'ifindex': cfg.ifindex,
92                       'parent': {'scope': 'netdev'},
93                       'handle': {'scope': 'queue', 'id': 2},
94                       'metric': 'bps',
95                       'bw-max': 20000}])
96
97def del_qshapers(cfg, nl_shaper) -> None:
98    if not cfg.queues:
99        raise KsftSkipEx("queue shapers not supported by device, skipping delete")
100
101    nl_shaper.delete({'ifindex': cfg.ifindex,
102                      'handle': {'scope': 'queue', 'id': 2}})
103    nl_shaper.delete({'ifindex': cfg.ifindex,
104                      'handle': {'scope': 'queue', 'id': 1}})
105    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
106    ksft_eq(len(shapers), 0)
107
108def set_nshapers(cfg, nl_shaper) -> None:
109    # Check required features.
110    try:
111        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
112                                  'scope':'netdev'})
113    except NlError as e:
114        if e.error == 95:
115            raise KsftSkipEx("shapers not supported by the device")
116        raise
117    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
118        raise KsftSkipEx("device does not support nested netdev scope shapers with weight")
119
120    cfg.netdev = True;
121    nl_shaper.set({'ifindex': cfg.ifindex,
122                   'handle': {'scope': 'netdev', 'id': 0},
123                   'bw-max': 100000})
124
125    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
126    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
127                       'handle': {'scope': 'netdev'},
128                       'metric': 'bps',
129                       'bw-max': 100000}])
130
131def del_nshapers(cfg, nl_shaper) -> None:
132    if not cfg.netdev:
133        raise KsftSkipEx("netdev shaper not supported by device, skipping delete")
134
135    nl_shaper.delete({'ifindex': cfg.ifindex,
136                      'handle': {'scope': 'netdev'}})
137    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
138    ksft_eq(len(shapers), 0)
139
140def basic_groups(cfg, nl_shaper) -> None:
141    if not cfg.netdev:
142        raise KsftSkipEx("netdev shaper not supported by the device")
143    if cfg.nr_queues < 3:
144        raise KsftSkipEx(f"netdev does not have enough queues min 3 reported {cfg.nr_queues}")
145
146    try:
147        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
148                                  'scope':'queue'})
149    except NlError as e:
150        if e.error == 95:
151            raise KsftSkipEx("shapers not supported by the device")
152        raise
153    if not 'support-weight' in caps:
154        raise KsftSkipEx("device does not support queue scope shapers with weight")
155
156    node_handle = nl_shaper.group({
157                        'ifindex': cfg.ifindex,
158                        'leaves':[{'handle': {'scope': 'queue', 'id': 1},
159                                   'weight': 1},
160                                  {'handle': {'scope': 'queue', 'id': 2},
161                                   'weight': 2}],
162                         'handle': {'scope':'netdev'},
163                         'metric': 'bps',
164                         'bw-max': 10000})
165    ksft_eq(node_handle, {'ifindex': cfg.ifindex,
166                          'handle': {'scope': 'netdev'}})
167
168    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
169                            'handle': {'scope': 'queue', 'id': 1}})
170    ksft_eq(shaper, {'ifindex': cfg.ifindex,
171                     'parent': {'scope': 'netdev'},
172                     'handle': {'scope': 'queue', 'id': 1},
173                     'weight': 1 })
174
175    nl_shaper.delete({'ifindex': cfg.ifindex,
176                      'handle': {'scope': 'queue', 'id': 2}})
177    nl_shaper.delete({'ifindex': cfg.ifindex,
178                      'handle': {'scope': 'queue', 'id': 1}})
179
180    # Deleting all the leaves shaper does not affect the node one
181    # when the latter has 'netdev' scope.
182    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
183    ksft_eq(len(shapers), 1)
184
185    nl_shaper.delete({'ifindex': cfg.ifindex,
186                      'handle': {'scope': 'netdev'}})
187
188def qgroups(cfg, nl_shaper) -> None:
189    if cfg.nr_queues < 4:
190        raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}")
191    try:
192        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
193                                  'scope':'node'})
194    except NlError as e:
195        if e.error == 95:
196            raise KsftSkipEx("shapers not supported by the device")
197        raise
198    if not 'support-bw-max' in caps or not 'support-metric-bps' in caps:
199        raise KsftSkipEx("device does not support node scope shapers with bw_max and metric bps")
200    try:
201        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
202                                  'scope':'queue'})
203    except NlError as e:
204        if e.error == 95:
205            raise KsftSkipEx("shapers not supported by the device")
206        raise
207    if not 'support-nesting' in caps or not 'support-weight' in caps or not 'support-metric-bps' in caps:
208            raise KsftSkipEx("device does not support nested queue scope shapers with weight")
209
210    cfg.groups = True;
211    node_handle = nl_shaper.group({
212                   'ifindex': cfg.ifindex,
213                   'leaves':[{'handle': {'scope': 'queue', 'id': 1},
214                              'weight': 3},
215                             {'handle': {'scope': 'queue', 'id': 2},
216                              'weight': 2}],
217                   'handle': {'scope':'node'},
218                   'metric': 'bps',
219                   'bw-max': 10000})
220    node_id = node_handle['handle']['id']
221
222    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
223                            'handle': {'scope': 'queue', 'id': 1}})
224    ksft_eq(shaper, {'ifindex': cfg.ifindex,
225                     'parent': {'scope': 'node', 'id': node_id},
226                     'handle': {'scope': 'queue', 'id': 1},
227                     'weight': 3})
228    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
229                            'handle': {'scope': 'node', 'id': node_id}})
230    ksft_eq(shaper, {'ifindex': cfg.ifindex,
231                     'handle': {'scope': 'node', 'id': node_id},
232                     'parent': {'scope': 'netdev'},
233                     'metric': 'bps',
234                     'bw-max': 10000})
235
236    # Grouping to a specified, not existing node scope shaper must fail
237    raised = False
238    try:
239        nl_shaper.group({
240                   'ifindex': cfg.ifindex,
241                   'leaves':[{'handle': {'scope': 'queue', 'id': 3},
242                              'weight': 3}],
243                   'handle': {'scope':'node', 'id': node_id + 1},
244                   'metric': 'bps',
245                   'bw-max': 10000})
246
247    except (NlError):
248        raised = True
249    ksft_eq(raised, True)
250
251    # Add to an existing node
252    node_handle = nl_shaper.group({
253                   'ifindex': cfg.ifindex,
254                   'leaves':[{'handle': {'scope': 'queue', 'id': 3},
255                              'weight': 4}],
256                   'handle': {'scope':'node', 'id': node_id}})
257    ksft_eq(node_handle, {'ifindex': cfg.ifindex,
258                          'handle': {'scope': 'node', 'id': node_id}})
259
260    shaper = nl_shaper.get({'ifindex': cfg.ifindex,
261                            'handle': {'scope': 'queue', 'id': 3}})
262    ksft_eq(shaper, {'ifindex': cfg.ifindex,
263                     'parent': {'scope': 'node', 'id': node_id},
264                     'handle': {'scope': 'queue', 'id': 3},
265                     'weight': 4})
266
267    nl_shaper.delete({'ifindex': cfg.ifindex,
268                      'handle': {'scope': 'queue', 'id': 2}})
269    nl_shaper.delete({'ifindex': cfg.ifindex,
270                      'handle': {'scope': 'queue', 'id': 1}})
271
272    # Deleting a non empty node will move the leaves downstream.
273    nl_shaper.delete({'ifindex': cfg.ifindex,
274                      'handle': {'scope': 'node', 'id': node_id}})
275    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
276    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
277                       'parent': {'scope': 'netdev'},
278                       'handle': {'scope': 'queue', 'id': 3},
279                       'weight': 4}])
280
281    # Finish and verify the complete cleanup.
282    nl_shaper.delete({'ifindex': cfg.ifindex,
283                      'handle': {'scope': 'queue', 'id': 3}})
284    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
285    ksft_eq(len(shapers), 0)
286
287def delegation(cfg, nl_shaper) -> None:
288    if not cfg.groups:
289        raise KsftSkipEx("device does not support node scope")
290    try:
291        caps = nl_shaper.cap_get({'ifindex': cfg.ifindex,
292                                  'scope':'node'})
293    except NlError as e:
294        if e.error == 95:
295            raise KsftSkipEx("node scope shapers not supported by the device")
296        raise
297    if not 'support-nesting' in caps:
298        raise KsftSkipEx("device does not support node scope shapers nesting")
299
300    node_handle = nl_shaper.group({
301                   'ifindex': cfg.ifindex,
302                   'leaves':[{'handle': {'scope': 'queue', 'id': 1},
303                              'weight': 3},
304                             {'handle': {'scope': 'queue', 'id': 2},
305                              'weight': 2},
306                             {'handle': {'scope': 'queue', 'id': 3},
307                              'weight': 1}],
308                   'handle': {'scope':'node'},
309                   'metric': 'bps',
310                   'bw-max': 10000})
311    node_id = node_handle['handle']['id']
312
313    # Create the nested node and validate the hierarchy
314    nested_node_handle = nl_shaper.group({
315                   'ifindex': cfg.ifindex,
316                   'leaves':[{'handle': {'scope': 'queue', 'id': 1},
317                              'weight': 3},
318                             {'handle': {'scope': 'queue', 'id': 2},
319                              'weight': 2}],
320                   'handle': {'scope':'node'},
321                   'metric': 'bps',
322                   'bw-max': 5000})
323    nested_node_id = nested_node_handle['handle']['id']
324    ksft_true(nested_node_id != node_id)
325    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
326    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
327                       'parent': {'scope': 'node', 'id': nested_node_id},
328                       'handle': {'scope': 'queue', 'id': 1},
329                       'weight': 3},
330                      {'ifindex': cfg.ifindex,
331                       'parent': {'scope': 'node', 'id': nested_node_id},
332                       'handle': {'scope': 'queue', 'id': 2},
333                       'weight': 2},
334                      {'ifindex': cfg.ifindex,
335                       'parent': {'scope': 'node', 'id': node_id},
336                       'handle': {'scope': 'queue', 'id': 3},
337                       'weight': 1},
338                      {'ifindex': cfg.ifindex,
339                       'parent': {'scope': 'netdev'},
340                       'handle': {'scope': 'node', 'id': node_id},
341                       'metric': 'bps',
342                       'bw-max': 10000},
343                      {'ifindex': cfg.ifindex,
344                       'parent': {'scope': 'node', 'id': node_id},
345                       'handle': {'scope': 'node', 'id': nested_node_id},
346                       'metric': 'bps',
347                       'bw-max': 5000}])
348
349    # Deleting a non empty node will move the leaves downstream.
350    nl_shaper.delete({'ifindex': cfg.ifindex,
351                      'handle': {'scope': 'node', 'id': nested_node_id}})
352    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
353    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
354                       'parent': {'scope': 'node', 'id': node_id},
355                       'handle': {'scope': 'queue', 'id': 1},
356                       'weight': 3},
357                      {'ifindex': cfg.ifindex,
358                       'parent': {'scope': 'node', 'id': node_id},
359                       'handle': {'scope': 'queue', 'id': 2},
360                       'weight': 2},
361                      {'ifindex': cfg.ifindex,
362                       'parent': {'scope': 'node', 'id': node_id},
363                       'handle': {'scope': 'queue', 'id': 3},
364                       'weight': 1},
365                      {'ifindex': cfg.ifindex,
366                       'parent': {'scope': 'netdev'},
367                       'handle': {'scope': 'node', 'id': node_id},
368                       'metric': 'bps',
369                       'bw-max': 10000}])
370
371    # Final cleanup.
372    for i in range(1, 4):
373        nl_shaper.delete({'ifindex': cfg.ifindex,
374                          'handle': {'scope': 'queue', 'id': i}})
375    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
376    ksft_eq(len(shapers), 0)
377
378def queue_update(cfg, nl_shaper) -> None:
379    if cfg.nr_queues < 4:
380        raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}")
381    if not cfg.queues:
382        raise KsftSkipEx("device does not support queue scope")
383
384    for i in range(3):
385        nl_shaper.set({'ifindex': cfg.ifindex,
386                       'handle': {'scope': 'queue', 'id': i},
387                       'metric': 'bps',
388                       'bw-max': (i + 1) * 1000})
389    # Delete a channel, with no shapers configured on top of the related
390    # queue: no changes expected
391    cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3", timeout=10)
392    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
393    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
394                       'parent': {'scope': 'netdev'},
395                       'handle': {'scope': 'queue', 'id': 0},
396                       'metric': 'bps',
397                       'bw-max': 1000},
398                      {'ifindex': cfg.ifindex,
399                       'parent': {'scope': 'netdev'},
400                       'handle': {'scope': 'queue', 'id': 1},
401                       'metric': 'bps',
402                       'bw-max': 2000},
403                      {'ifindex': cfg.ifindex,
404                       'parent': {'scope': 'netdev'},
405                       'handle': {'scope': 'queue', 'id': 2},
406                       'metric': 'bps',
407                       'bw-max': 3000}])
408
409    # Delete a channel, with a shaper configured on top of the related
410    # queue: the shaper must be deleted, too
411    cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2", timeout=10)
412
413    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
414    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
415                       'parent': {'scope': 'netdev'},
416                       'handle': {'scope': 'queue', 'id': 0},
417                       'metric': 'bps',
418                       'bw-max': 1000},
419                      {'ifindex': cfg.ifindex,
420                       'parent': {'scope': 'netdev'},
421                       'handle': {'scope': 'queue', 'id': 1},
422                       'metric': 'bps',
423                       'bw-max': 2000}])
424
425    # Restore the original channels number, no expected changes
426    cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}", timeout=10)
427    shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
428    ksft_eq(shapers, [{'ifindex': cfg.ifindex,
429                       'parent': {'scope': 'netdev'},
430                       'handle': {'scope': 'queue', 'id': 0},
431                       'metric': 'bps',
432                       'bw-max': 1000},
433                      {'ifindex': cfg.ifindex,
434                       'parent': {'scope': 'netdev'},
435                       'handle': {'scope': 'queue', 'id': 1},
436                       'metric': 'bps',
437                       'bw-max': 2000}])
438
439    # Final cleanup.
440    for i in range(0, 2):
441        nl_shaper.delete({'ifindex': cfg.ifindex,
442                          'handle': {'scope': 'queue', 'id': i}})
443
444def dup_leaves(cfg, nl_shaper) -> None:
445    """ Ensure that the kernel rejects duplicate leaves. """
446    if not cfg.groups:
447        raise KsftSkipEx("device does not support node scope")
448
449    with ksft_raises(NlError) as cm:
450        nl_shaper.group({
451                   'ifindex': cfg.ifindex,
452                   'leaves':[{'handle': {'scope': 'queue', 'id': 0}},
453                             {'handle': {'scope': 'queue', 'id': 0}}],
454                   'handle': {'scope':'node'},
455                   'metric': 'bps',
456                   'bw-max': 10000})
457    ksft_eq(cm.exception.error, errno.EINVAL)
458
459def main() -> None:
460    with NetDrvEnv(__file__, queue_count=4) as cfg:
461        cfg.queues = False
462        cfg.netdev = False
463        cfg.groups = False
464        cfg.nr_queues = 0
465        ksft_run([get_shapers,
466                  get_caps,
467                  set_qshapers,
468                  del_qshapers,
469                  set_nshapers,
470                  del_nshapers,
471                  basic_groups,
472                  qgroups,
473                  delegation,
474                  dup_leaves,
475                  queue_update],
476                 args=(cfg, NetshaperFamily()))
477    ksft_exit()
478
479
480if __name__ == "__main__":
481    main()
482