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