1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3# pylint: disable=too-many-lines 4 5import errno 6import glob 7 8from lib.py import ksft_run, ksft_exit 9from lib.py import ksft_eq, ksft_true, ksft_raises, KsftSkipEx 10from lib.py import EthtoolFamily, NetshaperFamily 11from lib.py import NetDrvEnv 12from lib.py import NlError 13from lib.py import cmd, defer 14 15def _delete_shaper(cfg, nl_shaper, handle) -> None: 16 """ Delete the shaper identified by handle, ignoring a missing-shaper error. """ 17 try: 18 nl_shaper.delete({'ifindex': cfg.ifindex, 19 'handle': handle}) 20 except NlError as e: 21 if e.error != errno.ENOENT: 22 raise 23 24def _require_queues(cfg, count): 25 """ Return the netdev TX queue count, skipping the test if fewer than count exist. """ 26 qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/tx-*")) 27 if qcnt < count: 28 raise KsftSkipEx(f"netdev has {qcnt} queues, {count} required") 29 return qcnt 30 31def _cap_get(cfg, nl_shaper, scope): 32 """ Return the shaper capabilities for the given scope, caching them on cfg. """ 33 if not hasattr(cfg, 'cap_cache'): 34 cfg.cap_cache = {} 35 if scope not in cfg.cap_cache: 36 cfg.cap_cache[scope] = nl_shaper.cap_get({'ifindex': cfg.ifindex, 37 'scope': scope}) 38 39 return cfg.cap_cache[scope] 40 41def _require_caps(cfg, nl_shaper, scope, caps, msg) -> None: 42 """ Skip the test unless the given scope advertises all the required caps. """ 43 try: 44 supported = _cap_get(cfg, nl_shaper, scope) 45 except NlError as e: 46 if e.error == errno.EOPNOTSUPP: 47 raise KsftSkipEx(f"{scope} scope shapers not supported by the device") 48 raise 49 50 if not set(caps).issubset(supported): 51 raise KsftSkipEx(msg) 52 53def get_shapers(cfg, nl_shaper) -> None: 54 try: 55 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 56 except NlError as e: 57 if e.error == 95: 58 raise KsftSkipEx("shapers not supported by the device") 59 raise 60 61 # Default configuration: no shapers configured. 62 ksft_eq(len(shapers), 0) 63 64def get_caps(cfg, nl_shaper) -> None: 65 try: 66 caps = nl_shaper.cap_get({'ifindex': cfg.ifindex}, dump=True) 67 except NlError as e: 68 if e.error == 95: 69 raise KsftSkipEx("shapers not supported by the device") 70 raise 71 72 # Each device implementing shaper support must support some 73 # features in at least a scope. 74 ksft_true(len(caps)> 0) 75 76def set_qshapers(cfg, nl_shaper) -> None: 77 try: 78 caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, 79 'scope':'queue'}) 80 except NlError as e: 81 if e.error == 95: 82 raise KsftSkipEx("shapers not supported by the device") 83 raise 84 if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: 85 raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps") 86 87 _require_queues(cfg, 3) 88 cfg.queues = True 89 90 nl_shaper.set({'ifindex': cfg.ifindex, 91 'handle': {'scope': 'queue', 'id': 1}, 92 'metric': 'bps', 93 'bw-max': 10000}) 94 nl_shaper.set({'ifindex': cfg.ifindex, 95 'handle': {'scope': 'queue', 'id': 2}, 96 'metric': 'bps', 97 'bw-max': 20000}) 98 99 # Querying a specific shaper not yet configured must fail. 100 raised = False 101 try: 102 shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, 103 'handle': {'scope': 'queue', 'id': 0}}) 104 except (NlError): 105 raised = True 106 ksft_eq(raised, True) 107 108 shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, 109 'handle': {'scope': 'queue', 'id': 1}}) 110 ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, 111 'parent': {'scope': 'netdev'}, 112 'handle': {'scope': 'queue', 'id': 1}, 113 'metric': 'bps', 114 'bw-max': 10000}) 115 116 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 117 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 118 'parent': {'scope': 'netdev'}, 119 'handle': {'scope': 'queue', 'id': 1}, 120 'metric': 'bps', 121 'bw-max': 10000}, 122 {'ifindex': cfg.ifindex, 123 'parent': {'scope': 'netdev'}, 124 'handle': {'scope': 'queue', 'id': 2}, 125 'metric': 'bps', 126 'bw-max': 20000}]) 127 128def del_qshapers(cfg, nl_shaper) -> None: 129 if not cfg.queues: 130 raise KsftSkipEx("queue shapers not supported by device, skipping delete") 131 132 nl_shaper.delete({'ifindex': cfg.ifindex, 133 'handle': {'scope': 'queue', 'id': 2}}) 134 nl_shaper.delete({'ifindex': cfg.ifindex, 135 'handle': {'scope': 'queue', 'id': 1}}) 136 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 137 ksft_eq(len(shapers), 0) 138 139def set_nshapers(cfg, nl_shaper) -> None: 140 # Check required features. 141 try: 142 caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, 143 'scope':'netdev'}) 144 except NlError as e: 145 if e.error == 95: 146 raise KsftSkipEx("shapers not supported by the device") 147 raise 148 if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: 149 raise KsftSkipEx("device does not support nested netdev scope shapers with weight") 150 151 cfg.netdev = True; 152 nl_shaper.set({'ifindex': cfg.ifindex, 153 'handle': {'scope': 'netdev', 'id': 0}, 154 'bw-max': 100000}) 155 156 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 157 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 158 'handle': {'scope': 'netdev'}, 159 'metric': 'bps', 160 'bw-max': 100000}]) 161 162def del_nshapers(cfg, nl_shaper) -> None: 163 if not cfg.netdev: 164 raise KsftSkipEx("netdev shaper not supported by device, skipping delete") 165 166 nl_shaper.delete({'ifindex': cfg.ifindex, 167 'handle': {'scope': 'netdev'}}) 168 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 169 ksft_eq(len(shapers), 0) 170 171def set_all_supported_attrs(cfg, nl_shaper) -> None: 172 """ Set every queue-scope attribute the device advertises and verify the read-back. """ 173 _require_queues(cfg, 1) 174 175 _require_caps(cfg, nl_shaper, 'queue', [], 176 "queue scope shapers not supported by the device") 177 caps = _cap_get(cfg, nl_shaper, 'queue') 178 179 attrs = {'ifindex': cfg.ifindex, 180 'handle': {'scope': 'queue', 'id': 0}} 181 expected = {'ifindex': cfg.ifindex, 182 'parent': {'scope': 'netdev'}, 183 'handle': {'scope': 'queue', 'id': 0}} 184 185 rate_attrs = {'support-bw-min': ('bw-min', 10000, 100), 186 'support-bw-max': ('bw-max', 20000, 200), 187 'support-burst': ('burst', 3000, 30)} 188 rate_attr_supported = any(cap in caps for cap in rate_attrs) 189 bps_supported = 'support-metric-bps' in caps 190 pps_supported = 'support-metric-pps' in caps 191 192 def add_rate_attrs(metric, value_idx) -> None: 193 attrs['metric'] = metric 194 expected['metric'] = metric 195 for cap, (attr, bps_value, pps_value) in rate_attrs.items(): 196 if cap not in caps: 197 continue 198 199 value = bps_value if value_idx == 0 else pps_value 200 attrs[attr] = value 201 expected[attr] = value 202 203 if rate_attr_supported: 204 if bps_supported: 205 add_rate_attrs('bps', 0) 206 elif pps_supported: 207 add_rate_attrs('pps', 1) 208 209 if 'support-priority' in caps: 210 attrs['priority'] = 1 211 expected['priority'] = 1 212 if 'support-weight' in caps: 213 attrs['weight'] = 2 214 expected['weight'] = 2 215 216 if len(attrs) == 2: 217 raise KsftSkipEx("device does not advertise any supported queue shaper attributes") 218 219 nl_shaper.set(attrs) 220 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 221 222 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 223 'handle': {'scope': 'queue', 'id': 0}}) 224 ksft_eq(shaper, expected) 225 226 if rate_attr_supported and bps_supported and pps_supported: 227 add_rate_attrs('pps', 1) 228 nl_shaper.set(attrs) 229 230 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 231 'handle': {'scope': 'queue', 'id': 0}}) 232 ksft_eq(shaper, expected) 233 234 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 235 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 236 ksft_eq(len(shapers), 0) 237 238def invalid_set_preserves_state(cfg, nl_shaper) -> None: 239 """ Verify a rejected .set leaves the existing shaper configuration unchanged. """ 240 nq = _require_queues(cfg, 1) 241 _require_caps(cfg, nl_shaper, 'queue', 242 ['support-bw-max', 'support-metric-bps'], 243 "device does not support queue scope bw_max with bps metric") 244 245 initial = {'ifindex': cfg.ifindex, 246 'parent': {'scope': 'netdev'}, 247 'handle': {'scope': 'queue', 'id': 0}, 248 'metric': 'bps', 249 'bw-max': 10000} 250 nl_shaper.set({'ifindex': cfg.ifindex, 251 'handle': {'scope': 'queue', 'id': 0}, 252 'metric': 'bps', 253 'bw-max': 10000}) 254 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 255 256 with ksft_raises(NlError): 257 nl_shaper.set({'ifindex': cfg.ifindex, 258 'handle': {'scope': 'node', 'id': 0}, 259 'metric': 'bps', 260 'bw-max': 20000}) 261 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 262 'handle': {'scope': 'queue', 'id': 0}}) 263 ksft_eq(shaper, initial) 264 265 with ksft_raises(NlError): 266 nl_shaper.set({'ifindex': cfg.ifindex, 267 'handle': {'scope': 'queue', 'id': nq}, 268 'metric': 'bps', 269 'bw-max': 20000}) 270 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 271 'handle': {'scope': 'queue', 'id': 0}}) 272 ksft_eq(shaper, initial) 273 274 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 275 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 276 ksft_eq(len(shapers), 0) 277 278def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None: 279 r"""Grouping leaves from different nodes requires an explicit parent. 280 281 netdev netdev 282 / \ parent=netdev 283 N1 N2 group N 284 | | {Q0,Q1} / \ 285 Q0 Q1 -------> Q0 Q1 286 287 Without an explicit parent the group is rejected; parent=netdev 288 collapses the leaves into one new node. 289 """ 290 _require_queues(cfg, 2) 291 _require_caps(cfg, nl_shaper, 'node', 292 ['support-bw-max', 'support-metric-bps'], 293 "device does not support node scope shapers with bw_max and metric bps") 294 _require_caps(cfg, nl_shaper, 'queue', 295 ['support-nesting', 'support-weight'], 296 "device does not support nested queue scope shapers with weight") 297 298 n1_handle = nl_shaper.group({ 299 'ifindex': cfg.ifindex, 300 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, 301 'weight': 1}], 302 'handle': {'scope':'node'}, 303 'metric': 'bps', 304 'bw-max': 10000}) 305 n1_id = n1_handle['handle']['id'] 306 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 307 308 n2_handle = nl_shaper.group({ 309 'ifindex': cfg.ifindex, 310 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 311 'weight': 2}], 312 'handle': {'scope':'node'}, 313 'metric': 'bps', 314 'bw-max': 20000}) 315 n2_id = n2_handle['handle']['id'] 316 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) 317 318 with ksft_raises(NlError): 319 nl_shaper.group({ 320 'ifindex': cfg.ifindex, 321 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, 322 'weight': 3}, 323 {'handle': {'scope': 'queue', 'id': 1}, 324 'weight': 4}], 325 'handle': {'scope':'node'}, 326 'metric': 'bps', 327 'bw-max': 30000}) 328 329 shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, 330 'handle': {'scope': 'queue', 'id': 0}}) 331 ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, 332 'parent': {'scope': 'node', 'id': n1_id}, 333 'handle': {'scope': 'queue', 'id': 0}, 334 'weight': 1}) 335 shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, 336 'handle': {'scope': 'queue', 'id': 1}}) 337 ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, 338 'parent': {'scope': 'node', 'id': n2_id}, 339 'handle': {'scope': 'queue', 'id': 1}, 340 'weight': 2}) 341 342 node_handle = nl_shaper.group({ 343 'ifindex': cfg.ifindex, 344 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, 345 'weight': 3}, 346 {'handle': {'scope': 'queue', 'id': 1}, 347 'weight': 4}], 348 'handle': {'scope':'node'}, 349 'parent': {'scope': 'netdev'}, 350 'metric': 'bps', 351 'bw-max': 30000}) 352 node_id = node_handle['handle']['id'] 353 354 for old_id in (n1_id, n2_id): 355 with ksft_raises(NlError): 356 nl_shaper.get({'ifindex': cfg.ifindex, 357 'handle': {'scope': 'node', 'id': old_id}}) 358 359 shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, 360 'handle': {'scope': 'queue', 'id': 0}}) 361 ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, 362 'parent': {'scope': 'node', 'id': node_id}, 363 'handle': {'scope': 'queue', 'id': 0}, 364 'weight': 3}) 365 shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, 366 'handle': {'scope': 'queue', 'id': 1}}) 367 ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, 368 'parent': {'scope': 'node', 'id': node_id}, 369 'handle': {'scope': 'queue', 'id': 1}, 370 'weight': 4}) 371 372 for i in range(2): 373 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) 374 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 375 ksft_eq(len(shapers), 0) 376 377def recursive_empty_node_cleanup(cfg, nl_shaper) -> None: 378 r"""Deleting the last leaf recursively removes the emptied ancestors. 379 380 netdev netdev 381 | del Q0 382 N1 ------> (N1 and N2 removed too) 383 | 384 N2 385 | 386 Q0 387 """ 388 _require_queues(cfg, 1) 389 _require_caps(cfg, nl_shaper, 'node', 390 ['support-bw-max', 'support-metric-bps', 'support-nesting'], 391 "device does not support nested node scope shapers") 392 _require_caps(cfg, nl_shaper, 'queue', 393 ['support-nesting', 'support-weight'], 394 "device does not support nested queue scope shapers with weight") 395 396 n1_handle = nl_shaper.group({ 397 'ifindex': cfg.ifindex, 398 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, 399 'weight': 1}], 400 'handle': {'scope':'node'}, 401 'metric': 'bps', 402 'bw-max': 10000}) 403 n1_id = n1_handle['handle']['id'] 404 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 405 406 n2_handle = nl_shaper.group({ 407 'ifindex': cfg.ifindex, 408 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, 409 'weight': 1}], 410 'handle': {'scope':'node'}, 411 'parent': {'scope': 'node', 'id': n1_id}, 412 'metric': 'bps', 413 'bw-max': 5000}) 414 n2_id = n2_handle['handle']['id'] 415 416 shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, 417 'handle': {'scope': 'queue', 'id': 0}}) 418 ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, 419 'parent': {'scope': 'node', 'id': n2_id}, 420 'handle': {'scope': 'queue', 'id': 0}, 421 'weight': 1}) 422 423 nl_shaper.delete({'ifindex': cfg.ifindex, 424 'handle': {'scope': 'queue', 'id': 0}}) 425 426 for handle in ({'scope': 'queue', 'id': 0}, 427 {'scope': 'node', 'id': n2_id}, 428 {'scope': 'node', 'id': n1_id}): 429 with ksft_raises(NlError): 430 nl_shaper.get({'ifindex': cfg.ifindex, 'handle': handle}) 431 432 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 433 ksft_eq(len(shapers), 0) 434 435def _group_under_netdev(cfg, nl_shaper, bw_max=None): 436 r"""Group queues under a netdev-scope node; caller owns node teardown. 437 438 netdev netdev 439 / \ del Q1,Q2 440 Q1 Q2 -------> (netdev node persists) 441 """ 442 group_args = { 443 'ifindex': cfg.ifindex, 444 'leaves': [{'handle': {'scope': 'queue', 'id': 1}, 445 'weight': 1}, 446 {'handle': {'scope': 'queue', 'id': 2}, 447 'weight': 2}], 448 'handle': {'scope': 'netdev'}} 449 if bw_max: 450 group_args['metric'] = 'bps' 451 group_args['bw-max'] = bw_max 452 453 node_handle = nl_shaper.group(group_args) 454 ksft_eq(node_handle, {'ifindex': cfg.ifindex, 455 'handle': {'scope': 'netdev'}}) 456 457 del_node = defer(_delete_shaper, cfg, nl_shaper, {'scope': 'netdev'}) 458 del_queues = [defer(_delete_shaper, cfg, nl_shaper, 459 {'scope': 'queue', 'id': qid}) 460 for qid in (1, 2)] 461 462 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 463 'handle': {'scope': 'queue', 'id': 1}}) 464 ksft_eq(shaper, {'ifindex': cfg.ifindex, 465 'parent': {'scope': 'netdev'}, 466 'handle': {'scope': 'queue', 'id': 1}, 467 'weight': 1}) 468 for dq in del_queues: 469 dq.exec() 470 471 # Caller owns the node teardown so it can verify the netdev-scope node 472 # survives leaf deletion before removing it. 473 return del_node 474 475def basic_groups(cfg, nl_shaper) -> None: 476 r"""Group queues under a netdev-scope node, then tear it down. 477 478 netdev 479 / \ 480 Q1 Q2 481 """ 482 _require_queues(cfg, 3) 483 484 _require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device") 485 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 486 "queue scope not supported with nesting and weight") 487 488 del_node = _group_under_netdev(cfg, nl_shaper) 489 490 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 491 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 492 'handle': {'scope': 'netdev'}}]) 493 494 del_node.exec() 495 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 496 ksft_eq(len(shapers), 0) 497 498def basic_groups_with_rate(cfg, nl_shaper) -> None: 499 r"""Rate-limited netdev-scope node outlives deletion of its leaves. 500 501 netdev[10kbps] netdev[10kbps] 502 / \ del Q1,Q2 503 Q1 Q2 -------> (node persists) 504 """ 505 bw_max = 10000 506 507 _require_queues(cfg, 3) 508 509 _require_caps(cfg, nl_shaper, 'netdev', ['support-bw-max', 'support-metric-bps'], 510 "device does not support netdev scope rate limiting") 511 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 512 "device does not support queue scope shapers with nesting and weight") 513 514 del_node = _group_under_netdev(cfg, nl_shaper, bw_max=bw_max) 515 516 # Deleting all the leaves shaper does not affect the node one 517 # when the latter has 'netdev' scope. 518 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 519 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 520 'handle': {'scope': 'netdev'}, 521 'metric': 'bps', 522 'bw-max': bw_max}]) 523 524 del_node.exec() 525 526def qgroups(cfg, nl_shaper) -> None: 527 _require_queues(cfg, 4) 528 _require_caps(cfg, nl_shaper, 'node', 529 ['support-bw-max', 'support-metric-bps'], 530 "device does not support node scope shapers with bw_max and metric bps") 531 _require_caps(cfg, nl_shaper, 'queue', 532 ['support-nesting', 'support-weight'], 533 "device does not support nested queue scope shapers with weight") 534 535 node_handle = nl_shaper.group({ 536 'ifindex': cfg.ifindex, 537 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 538 'weight': 3}, 539 {'handle': {'scope': 'queue', 'id': 2}, 540 'weight': 2}], 541 'handle': {'scope':'node'}, 542 'metric': 'bps', 543 'bw-max': 10000}) 544 node_id = node_handle['handle']['id'] 545 546 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 547 'handle': {'scope': 'queue', 'id': 1}}) 548 ksft_eq(shaper, {'ifindex': cfg.ifindex, 549 'parent': {'scope': 'node', 'id': node_id}, 550 'handle': {'scope': 'queue', 'id': 1}, 551 'weight': 3}) 552 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 553 'handle': {'scope': 'node', 'id': node_id}}) 554 ksft_eq(shaper, {'ifindex': cfg.ifindex, 555 'handle': {'scope': 'node', 'id': node_id}, 556 'parent': {'scope': 'netdev'}, 557 'metric': 'bps', 558 'bw-max': 10000}) 559 560 # Grouping to a specified, not existing node scope shaper must fail 561 raised = False 562 try: 563 nl_shaper.group({ 564 'ifindex': cfg.ifindex, 565 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, 566 'weight': 3}], 567 'handle': {'scope':'node', 'id': node_id + 1}, 568 'metric': 'bps', 569 'bw-max': 10000}) 570 571 except (NlError): 572 raised = True 573 ksft_eq(raised, True) 574 575 # Add to an existing node 576 node_handle = nl_shaper.group({ 577 'ifindex': cfg.ifindex, 578 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, 579 'weight': 4}], 580 'handle': {'scope':'node', 'id': node_id}}) 581 ksft_eq(node_handle, {'ifindex': cfg.ifindex, 582 'handle': {'scope': 'node', 'id': node_id}}) 583 584 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 585 'handle': {'scope': 'queue', 'id': 3}}) 586 ksft_eq(shaper, {'ifindex': cfg.ifindex, 587 'parent': {'scope': 'node', 'id': node_id}, 588 'handle': {'scope': 'queue', 'id': 3}, 589 'weight': 4}) 590 591 nl_shaper.delete({'ifindex': cfg.ifindex, 592 'handle': {'scope': 'queue', 'id': 2}}) 593 nl_shaper.delete({'ifindex': cfg.ifindex, 594 'handle': {'scope': 'queue', 'id': 1}}) 595 596 # Deleting a non empty node will move the leaves downstream. 597 nl_shaper.delete({'ifindex': cfg.ifindex, 598 'handle': {'scope': 'node', 'id': node_id}}) 599 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 600 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 601 'parent': {'scope': 'netdev'}, 602 'handle': {'scope': 'queue', 'id': 3}, 603 'weight': 4}]) 604 605 # Finish and verify the complete cleanup. 606 nl_shaper.delete({'ifindex': cfg.ifindex, 607 'handle': {'scope': 'queue', 'id': 3}}) 608 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 609 ksft_eq(len(shapers), 0) 610 611def set_node_shaper(cfg, nl_shaper) -> None: 612 """ Verify a node-scope shaper rate can be updated via .set. """ 613 _require_queues(cfg, 2) 614 _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], 615 "device does not support node scope shapers with bw_max and metric bps") 616 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 617 "device does not support nested queue scope shapers with weight") 618 619 node_handle = nl_shaper.group({ 620 'ifindex': cfg.ifindex, 621 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 622 'weight': 1}], 623 'handle': {'scope':'node'}, 624 'metric': 'bps', 625 'bw-max': 10000}) 626 node_id = node_handle['handle']['id'] 627 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) 628 629 # Update the node's rate via .set 630 nl_shaper.set({'ifindex': cfg.ifindex, 631 'handle': {'scope': 'node', 'id': node_id}, 632 'metric': 'bps', 633 'bw-max': 20000}) 634 635 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 636 'handle': {'scope': 'node', 'id': node_id}}) 637 ksft_eq(shaper, {'ifindex': cfg.ifindex, 638 'handle': {'scope': 'node', 'id': node_id}, 639 'parent': {'scope': 'netdev'}, 640 'metric': 'bps', 641 'bw-max': 20000}) 642 643 # Cleanup 644 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 1}) 645 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 646 ksft_eq(len(shapers), 0) 647 648def group_update_rate(cfg, nl_shaper) -> None: 649 """ Verify re-grouping a node updates its rate while leaving the leaves untouched. """ 650 _require_queues(cfg, 3) 651 _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], 652 "device does not support node scope shapers with bw_max and metric bps") 653 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 654 "device does not support nested queue scope shapers with weight") 655 656 # Create node with Q1, Q2 at bw_max=10000 657 node_handle = nl_shaper.group({ 658 'ifindex': cfg.ifindex, 659 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 660 'weight': 1}, 661 {'handle': {'scope': 'queue', 'id': 2}, 662 'weight': 1}], 663 'handle': {'scope':'node'}, 664 'metric': 'bps', 665 'bw-max': 10000}) 666 node_id = node_handle['handle']['id'] 667 for i in range(1, 3): 668 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) 669 670 # Update rate via .group on the same node 671 nl_shaper.group({ 672 'ifindex': cfg.ifindex, 673 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 674 'weight': 1}, 675 {'handle': {'scope': 'queue', 'id': 2}, 676 'weight': 1}], 677 'handle': {'scope':'node', 'id': node_id}, 678 'metric': 'bps', 679 'bw-max': 50000}) 680 681 # Verify rate updated 682 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 683 'handle': {'scope': 'node', 'id': node_id}}) 684 ksft_eq(shaper, {'ifindex': cfg.ifindex, 685 'handle': {'scope': 'node', 'id': node_id}, 686 'parent': {'scope': 'netdev'}, 687 'metric': 'bps', 688 'bw-max': 50000}) 689 690 # Verify leaves unchanged 691 shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, 692 'handle': {'scope': 'queue', 'id': 1}}) 693 ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, 694 'parent': {'scope': 'node', 'id': node_id}, 695 'handle': {'scope': 'queue', 'id': 1}, 696 'weight': 1}) 697 shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, 698 'handle': {'scope': 'queue', 'id': 2}}) 699 ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, 700 'parent': {'scope': 'node', 'id': node_id}, 701 'handle': {'scope': 'queue', 'id': 2}, 702 'weight': 1}) 703 704 # Make sure we only have 3 shapers including 2 queues and the node 705 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 706 ksft_eq(len(shapers), 3) 707 708 # Cleanup 709 for i in range(1, 3): 710 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) 711 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 712 ksft_eq(len(shapers), 0) 713 714def delegation(cfg, nl_shaper) -> None: 715 _require_queues(cfg, 4) 716 _require_caps(cfg, nl_shaper, 'node', 717 ['support-bw-max', 'support-metric-bps', 'support-nesting'], 718 "device does not support node scope shapers with bw_max, metric bps and nesting") 719 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 720 "device does not support nested queue scope shapers with weight") 721 722 node_handle = nl_shaper.group({ 723 'ifindex': cfg.ifindex, 724 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 725 'weight': 3}, 726 {'handle': {'scope': 'queue', 'id': 2}, 727 'weight': 2}, 728 {'handle': {'scope': 'queue', 'id': 3}, 729 'weight': 1}], 730 'handle': {'scope':'node'}, 731 'metric': 'bps', 732 'bw-max': 10000}) 733 node_id = node_handle['handle']['id'] 734 735 # Create the nested node and validate the hierarchy 736 nested_node_handle = nl_shaper.group({ 737 'ifindex': cfg.ifindex, 738 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 739 'weight': 3}, 740 {'handle': {'scope': 'queue', 'id': 2}, 741 'weight': 2}], 742 'handle': {'scope':'node'}, 743 'metric': 'bps', 744 'bw-max': 5000}) 745 nested_node_id = nested_node_handle['handle']['id'] 746 ksft_true(nested_node_id != node_id) 747 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 748 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 749 'parent': {'scope': 'node', 'id': nested_node_id}, 750 'handle': {'scope': 'queue', 'id': 1}, 751 'weight': 3}, 752 {'ifindex': cfg.ifindex, 753 'parent': {'scope': 'node', 'id': nested_node_id}, 754 'handle': {'scope': 'queue', 'id': 2}, 755 'weight': 2}, 756 {'ifindex': cfg.ifindex, 757 'parent': {'scope': 'node', 'id': node_id}, 758 'handle': {'scope': 'queue', 'id': 3}, 759 'weight': 1}, 760 {'ifindex': cfg.ifindex, 761 'parent': {'scope': 'netdev'}, 762 'handle': {'scope': 'node', 'id': node_id}, 763 'metric': 'bps', 764 'bw-max': 10000}, 765 {'ifindex': cfg.ifindex, 766 'parent': {'scope': 'node', 'id': node_id}, 767 'handle': {'scope': 'node', 'id': nested_node_id}, 768 'metric': 'bps', 769 'bw-max': 5000}]) 770 771 # Deleting a non empty node will move the leaves downstream. 772 nl_shaper.delete({'ifindex': cfg.ifindex, 773 'handle': {'scope': 'node', 'id': nested_node_id}}) 774 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 775 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 776 'parent': {'scope': 'node', 'id': node_id}, 777 'handle': {'scope': 'queue', 'id': 1}, 778 'weight': 3}, 779 {'ifindex': cfg.ifindex, 780 'parent': {'scope': 'node', 'id': node_id}, 781 'handle': {'scope': 'queue', 'id': 2}, 782 'weight': 2}, 783 {'ifindex': cfg.ifindex, 784 'parent': {'scope': 'node', 'id': node_id}, 785 'handle': {'scope': 'queue', 'id': 3}, 786 'weight': 1}, 787 {'ifindex': cfg.ifindex, 788 'parent': {'scope': 'netdev'}, 789 'handle': {'scope': 'node', 'id': node_id}, 790 'metric': 'bps', 791 'bw-max': 10000}]) 792 793 # Final cleanup. 794 for i in range(1, 4): 795 nl_shaper.delete({'ifindex': cfg.ifindex, 796 'handle': {'scope': 'queue', 'id': i}}) 797 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 798 ksft_eq(len(shapers), 0) 799 800def nested_depth_limit(cfg, nl_shaper) -> None: 801 r"""Nest nodes as deep as the device allows to find the max depth. 802 803 netdev 804 | 805 N1 -- Q1 806 | 807 N2 -- Q2 808 | 809 N3 -- Q3 810 : (deepen until the driver rejects) 811 """ 812 bw_max = 10000 813 814 _require_caps(cfg, nl_shaper, 'node', 815 ['support-bw-max', 'support-metric-bps', 'support-nesting'], 816 "device does not support node scope shapers with bw_max, metric bps and nesting") 817 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 818 "device does not support nested queue scope shapers with weight") 819 820 nq = _require_queues(cfg, 3) 821 822 node_ids = [] 823 cleanups = [] 824 queue_id = 1 825 max_depth = 0 826 limit_err = None 827 828 # Create initial node with a queue leaf 829 node_id = nl_shaper.group({ 830 'ifindex': cfg.ifindex, 831 'leaves': [{'handle': {'scope': 'queue', 'id': queue_id}, 832 'weight': 1}], 833 'handle': {'scope': 'node'}, 834 'metric': 'bps', 835 'bw-max': bw_max})['handle']['id'] 836 node_ids.append(node_id) 837 cleanups.append(defer(_delete_shaper, cfg, nl_shaper, 838 {'scope': 'node', 'id': node_id})) 839 cleanups.append(defer(_delete_shaper, cfg, nl_shaper, 840 {'scope': 'queue', 'id': queue_id})) 841 max_depth = 1 842 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 843 'handle': {'scope': 'node', 'id': node_id}}) 844 ksft_eq(shaper, {'ifindex': cfg.ifindex, 845 'handle': {'scope': 'node', 'id': node_id}, 846 'parent': {'scope': 'netdev'}, 847 'metric': 'bps', 848 'bw-max': bw_max}) 849 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 850 'handle': {'scope': 'queue', 'id': queue_id}}) 851 ksft_eq(shaper, {'ifindex': cfg.ifindex, 852 'parent': {'scope': 'node', 'id': node_id}, 853 'handle': {'scope': 'queue', 'id': queue_id}, 854 'weight': 1}) 855 queue_id += 1 856 857 # Keep nesting deeper until the driver rejects or queues run out. 858 while queue_id < nq: 859 parent_id = node_ids[-1] 860 try: 861 node_id = nl_shaper.group({ 862 'ifindex': cfg.ifindex, 863 'leaves': [{'handle': {'scope': 'queue', 864 'id': queue_id}, 865 'weight': 1}], 866 'handle': {'scope': 'node'}, 867 'parent': {'scope': 'node', 868 'id': parent_id}, 869 'metric': 'bps', 870 'bw-max': bw_max})['handle']['id'] 871 except NlError as e: 872 # Only treat "cannot nest deeper" errors as the depth limit; 873 # drivers report it differently (EOPNOTSUPP/ENOSPC/E2BIG/EINVAL). 874 # Anything else (ENOMEM, EIO, EPERM, driver bug) is a real failure. 875 if e.error not in (errno.EOPNOTSUPP, errno.ENOSPC, 876 errno.E2BIG, errno.EINVAL): 877 raise 878 limit_err = e 879 break 880 881 node_ids.append(node_id) 882 cleanups.append(defer(_delete_shaper, cfg, nl_shaper, 883 {'scope': 'node', 'id': node_id})) 884 cleanups.append(defer(_delete_shaper, cfg, nl_shaper, 885 {'scope': 'queue', 'id': queue_id})) 886 max_depth += 1 887 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 888 'handle': {'scope': 'node', 'id': node_id}}) 889 ksft_eq(shaper, {'ifindex': cfg.ifindex, 890 'handle': {'scope': 'node', 'id': node_id}, 891 'parent': {'scope': 'node', 'id': parent_id}, 892 'metric': 'bps', 893 'bw-max': bw_max}) 894 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 895 'handle': {'scope': 'queue', 896 'id': queue_id}}) 897 ksft_eq(shaper, {'ifindex': cfg.ifindex, 898 'parent': {'scope': 'node', 'id': node_id}, 899 'handle': {'scope': 'queue', 'id': queue_id}, 900 'weight': 1}) 901 queue_id += 1 902 903 if limit_err: 904 print(f"# max nesting depth supported: {max_depth} (errno {limit_err.error})") 905 else: 906 print(f"# max nesting depth tested: {max_depth}") 907 ksft_true(max_depth >= 2, 908 f"max nesting depth: {max_depth}") 909 910 # Cleanup: exec the deferred deletes in reverse creation order, so each 911 # queue leaf and deeper node is removed before its parent node. 912 for cleanup in reversed(cleanups): 913 cleanup.exec() 914 ksft_eq(len(nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)), 0) 915 916def delete_child_reparent(cfg, nl_shaper) -> None: 917 r"""Deleting a child node reparents its queue leaf to the parent. 918 919 netdev netdev 920 | | 921 N1 del N2 N1 922 / | \ -----> / | \ 923 Q1 Q2 N2 Q1 Q2 Q3 924 | 925 Q3 926 """ 927 n1_bw_max = 10000 928 n2_bw_max = 5000 929 930 _require_caps(cfg, nl_shaper, 'node', 931 ['support-bw-max', 'support-metric-bps', 'support-nesting'], 932 "device does not support node scope shapers with bw_max, metric bps and nesting") 933 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 934 "device does not support nested queue scope shapers with weight") 935 936 _require_queues(cfg, 4) 937 938 # Create parent node N1 with Q1, Q2 939 n1_handle = nl_shaper.group({ 940 'ifindex': cfg.ifindex, 941 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 942 'weight': 1}, 943 {'handle': {'scope': 'queue', 'id': 2}, 944 'weight': 1}], 945 'handle': {'scope':'node'}, 946 'metric': 'bps', 947 'bw-max': n1_bw_max}) 948 n1_id = n1_handle['handle']['id'] 949 for i in range(1, 3): 950 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) 951 952 # Create child node N2 under N1 with Q3 953 n2_handle = nl_shaper.group({ 954 'ifindex': cfg.ifindex, 955 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, 956 'weight': 1}], 957 'handle': {'scope':'node'}, 958 'parent': {'scope': 'node', 'id': n1_id}, 959 'metric': 'bps', 960 'bw-max': n2_bw_max}) 961 n2_id = n2_handle['handle']['id'] 962 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) 963 964 # Delete child N2 - Q3 should reparent to N1 965 nl_shaper.delete({'ifindex': cfg.ifindex, 966 'handle': {'scope': 'node', 'id': n2_id}}) 967 968 with ksft_raises(NlError): 969 nl_shaper.get({'ifindex': cfg.ifindex, 970 'handle': {'scope': 'node', 'id': n2_id}}) 971 972 shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, 973 'handle': {'scope': 'node', 'id': n1_id}}) 974 ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, 975 'handle': {'scope': 'node', 'id': n1_id}, 976 'parent': {'scope': 'netdev'}, 977 'metric': 'bps', 978 'bw-max': n1_bw_max}) 979 shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, 980 'handle': {'scope': 'queue', 'id': 3}}) 981 ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, 982 'parent': {'scope': 'node', 'id': n1_id}, 983 'handle': {'scope': 'queue', 'id': 3}, 984 'weight': 1}) 985 986 # Cleanup 987 for i in range(1, 4): 988 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) 989 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 990 ksft_eq(len(shapers), 0) 991 992def move_queue_between_nodes(cfg, nl_shaper) -> None: 993 r"""Move a queue between nodes by re-grouping the destination node. 994 995 netdev netdev 996 / \ .group N2 / \ 997 N1 N2 {Q1,Q3} N1 N2 998 / \ | -------> | / \ 999 Q1 Q2 Q3 Q2 Q1 Q3 1000 """ 1001 n1_bw_max = 10000 1002 n2_bw_max = 20000 1003 1004 _require_caps(cfg, nl_shaper, 'node', 1005 ['support-bw-max', 'support-metric-bps', 'support-nesting'], 1006 "device does not support node scope shapers with bw_max, metric bps and nesting") 1007 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 1008 "device does not support nested queue scope shapers with weight") 1009 1010 _require_queues(cfg, 4) 1011 1012 # Create N1 with Q1, Q2 1013 n1_handle = nl_shaper.group({ 1014 'ifindex': cfg.ifindex, 1015 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 1016 'weight': 1}, 1017 {'handle': {'scope': 'queue', 'id': 2}, 1018 'weight': 1}], 1019 'handle': {'scope':'node'}, 1020 'metric': 'bps', 1021 'bw-max': n1_bw_max}) 1022 n1_id = n1_handle['handle']['id'] 1023 for i in range(1, 3): 1024 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) 1025 1026 # Create N2 with Q3 1027 n2_handle = nl_shaper.group({ 1028 'ifindex': cfg.ifindex, 1029 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, 1030 'weight': 1}], 1031 'handle': {'scope':'node'}, 1032 'metric': 'bps', 1033 'bw-max': n2_bw_max}) 1034 n2_id = n2_handle['handle']['id'] 1035 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) 1036 1037 # Move Q1 from N1 to N2 by re-grouping N2 with Q1, Q3 1038 nl_shaper.group({ 1039 'ifindex': cfg.ifindex, 1040 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 1041 'weight': 2}, 1042 {'handle': {'scope': 'queue', 'id': 3}, 1043 'weight': 1}], 1044 'handle': {'scope':'node', 'id': n2_id}, 1045 'metric': 'bps', 1046 'bw-max': n2_bw_max}) 1047 1048 shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, 1049 'handle': {'scope': 'node', 'id': n1_id}}) 1050 ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, 1051 'handle': {'scope': 'node', 'id': n1_id}, 1052 'parent': {'scope': 'netdev'}, 1053 'metric': 'bps', 1054 'bw-max': n1_bw_max}) 1055 shaper_n2 = nl_shaper.get({'ifindex': cfg.ifindex, 1056 'handle': {'scope': 'node', 'id': n2_id}}) 1057 ksft_eq(shaper_n2, {'ifindex': cfg.ifindex, 1058 'handle': {'scope': 'node', 'id': n2_id}, 1059 'parent': {'scope': 'netdev'}, 1060 'metric': 'bps', 1061 'bw-max': n2_bw_max}) 1062 1063 # Verify Q1 moved to N2 1064 shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, 1065 'handle': {'scope': 'queue', 'id': 1}}) 1066 ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, 1067 'parent': {'scope': 'node', 'id': n2_id}, 1068 'handle': {'scope': 'queue', 'id': 1}, 1069 'weight': 2}) 1070 1071 # Verify Q2 still under N1 1072 shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, 1073 'handle': {'scope': 'queue', 'id': 2}}) 1074 ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, 1075 'parent': {'scope': 'node', 'id': n1_id}, 1076 'handle': {'scope': 'queue', 'id': 2}, 1077 'weight': 1}) 1078 1079 # Verify Q3 remained under N2 1080 shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, 1081 'handle': {'scope': 'queue', 'id': 3}}) 1082 ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, 1083 'parent': {'scope': 'node', 'id': n2_id}, 1084 'handle': {'scope': 'queue', 'id': 3}, 1085 'weight': 1}) 1086 1087 # Cleanup 1088 for i in range(1, 4): 1089 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) 1090 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 1091 ksft_eq(len(shapers), 0) 1092 1093def reject_reparenting(cfg, nl_shaper) -> None: 1094 r"""Reject reparenting an existing node; the hierarchy stays intact. 1095 1096 netdev 1097 / \ rejected: N3 -> netdev 1098 N1 N2 rejected: N1 -> N2 1099 / \ | (both EOPNOTSUPP) 1100 Q1 N3 Q2 1101 | 1102 Q3 1103 """ 1104 node1_bw_max = 10000 1105 node2_bw_max = 5000 1106 node3_bw_max = 20000 1107 1108 _require_caps(cfg, nl_shaper, 'node', 1109 ['support-bw-max', 'support-metric-bps', 'support-nesting'], 1110 "device does not support node scope shapers with bw_max, metric bps and nesting") 1111 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 1112 "device does not support nested queue scope shapers with weight") 1113 1114 _require_queues(cfg, 4) 1115 1116 # Create Node1 under netdev with Q1. 1117 node1_id = nl_shaper.group({ 1118 'ifindex': cfg.ifindex, 1119 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 1120 'weight': 1}], 1121 'handle': {'scope':'node'}, 1122 'metric': 'bps', 1123 'bw-max': node1_bw_max})['handle']['id'] 1124 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) 1125 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node1_id}) 1126 1127 # Create Node2 under netdev with Q2. 1128 node2_id = nl_shaper.group({ 1129 'ifindex': cfg.ifindex, 1130 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, 1131 'weight': 1}], 1132 'handle': {'scope':'node'}, 1133 'metric': 'bps', 1134 'bw-max': node2_bw_max})['handle']['id'] 1135 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 2}) 1136 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node2_id}) 1137 1138 # Create Node3 nested under Node1 with Q3. 1139 node3_id = nl_shaper.group({ 1140 'ifindex': cfg.ifindex, 1141 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, 1142 'weight': 1}], 1143 'handle': {'scope':'node'}, 1144 'metric': 'bps', 1145 'bw-max': node3_bw_max, 1146 'parent': {'scope': 'node', 'id': node1_id}})['handle']['id'] 1147 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) 1148 defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node3_id}) 1149 1150 # Reparenting a nested node up to netdev must fail. 1151 with ksft_raises(NlError) as cm: 1152 nl_shaper.group({ 1153 'ifindex': cfg.ifindex, 1154 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, 1155 'weight': 1}], 1156 'handle': {'scope':'node', 'id': node3_id}, 1157 'parent': {'scope': 'netdev'}}) 1158 if cm.exception: 1159 ksft_eq(cm.exception.error, errno.EOPNOTSUPP) 1160 1161 # Reparenting a node under another node must fail as well. 1162 with ksft_raises(NlError) as cm: 1163 nl_shaper.group({ 1164 'ifindex': cfg.ifindex, 1165 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 1166 'weight': 1}], 1167 'handle': {'scope':'node', 'id': node1_id}, 1168 'parent': {'scope': 'node', 'id': node2_id}}) 1169 if cm.exception: 1170 ksft_eq(cm.exception.error, errno.EOPNOTSUPP) 1171 1172 # Updating a node with the same parent must succeed. 1173 nl_shaper.group({ 1174 'ifindex': cfg.ifindex, 1175 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, 1176 'weight': 5}], 1177 'handle': {'scope':'node', 'id': node1_id}, 1178 'parent': {'scope': 'netdev'}}) 1179 1180 # Updating a node without specifying the parent must succeed. 1181 nl_shaper.group({ 1182 'ifindex': cfg.ifindex, 1183 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, 1184 'weight': 7}], 1185 'handle': {'scope':'node', 'id': node2_id}}) 1186 1187 # The rejected reparents must have left the hierarchy intact. 1188 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 1189 'handle': {'scope': 'node', 'id': node1_id}}) 1190 ksft_eq(shaper, {'ifindex': cfg.ifindex, 1191 'handle': {'scope': 'node', 'id': node1_id}, 1192 'parent': {'scope': 'netdev'}, 1193 'metric': 'bps', 1194 'bw-max': node1_bw_max}) 1195 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 1196 'handle': {'scope': 'node', 'id': node2_id}}) 1197 ksft_eq(shaper, {'ifindex': cfg.ifindex, 1198 'handle': {'scope': 'node', 'id': node2_id}, 1199 'parent': {'scope': 'netdev'}, 1200 'metric': 'bps', 1201 'bw-max': node2_bw_max}) 1202 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 1203 'handle': {'scope': 'node', 'id': node3_id}}) 1204 ksft_eq(shaper, {'ifindex': cfg.ifindex, 1205 'handle': {'scope': 'node', 'id': node3_id}, 1206 'parent': {'scope': 'node', 'id': node1_id}, 1207 'metric': 'bps', 1208 'bw-max': node3_bw_max}) 1209 1210 # Verify the leaf weights were updated and parents unchanged. 1211 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 1212 'handle': {'scope': 'queue', 'id': 1}}) 1213 ksft_eq(shaper, {'ifindex': cfg.ifindex, 1214 'parent': {'scope': 'node', 'id': node1_id}, 1215 'handle': {'scope': 'queue', 'id': 1}, 1216 'weight': 5}) 1217 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 1218 'handle': {'scope': 'queue', 'id': 2}}) 1219 ksft_eq(shaper, {'ifindex': cfg.ifindex, 1220 'parent': {'scope': 'node', 'id': node2_id}, 1221 'handle': {'scope': 'queue', 'id': 2}, 1222 'weight': 7}) 1223 shaper = nl_shaper.get({'ifindex': cfg.ifindex, 1224 'handle': {'scope': 'queue', 'id': 3}}) 1225 ksft_eq(shaper, {'ifindex': cfg.ifindex, 1226 'parent': {'scope': 'node', 'id': node3_id}, 1227 'handle': {'scope': 'queue', 'id': 3}, 1228 'weight': 1}) 1229 1230 # Cleanup. Delete the nodes explicitly instead of relying on the 1231 # empty-node auto-delete: a kernel that wrongly accepts a reparent may 1232 # mishandle the leaf accounting and leave a node behind. Removing them 1233 # by handle keeps a failing run from leaking state into later tests. 1234 for i in range(1, 4): 1235 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) 1236 for nid in (node1_id, node2_id, node3_id): 1237 _delete_shaper(cfg, nl_shaper, {'scope': 'node', 'id': nid}) 1238 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 1239 ksft_eq(len(shapers), 0) 1240 1241def queue_update(cfg, nl_shaper) -> None: 1242 nq = _require_queues(cfg, 4) 1243 if not cfg.queues: 1244 raise KsftSkipEx("device does not support queue scope") 1245 1246 netnl = EthtoolFamily() 1247 channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 1248 ch_type = 'combined' if channels['combined-count'] else 'tx' 1249 1250 for i in range(3): 1251 nl_shaper.set({'ifindex': cfg.ifindex, 1252 'handle': {'scope': 'queue', 'id': i}, 1253 'metric': 'bps', 1254 'bw-max': (i + 1) * 1000}) 1255 defer(cmd, f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") 1256 1257 # Delete a channel, with no shapers configured on top of the related 1258 # queue: no changes expected 1259 cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 3") 1260 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 1261 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 1262 'parent': {'scope': 'netdev'}, 1263 'handle': {'scope': 'queue', 'id': 0}, 1264 'metric': 'bps', 1265 'bw-max': 1000}, 1266 {'ifindex': cfg.ifindex, 1267 'parent': {'scope': 'netdev'}, 1268 'handle': {'scope': 'queue', 'id': 1}, 1269 'metric': 'bps', 1270 'bw-max': 2000}, 1271 {'ifindex': cfg.ifindex, 1272 'parent': {'scope': 'netdev'}, 1273 'handle': {'scope': 'queue', 'id': 2}, 1274 'metric': 'bps', 1275 'bw-max': 3000}]) 1276 1277 # Delete a channel, with a shaper configured on top of the related 1278 # queue: the shaper must be deleted, too 1279 cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 2") 1280 1281 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 1282 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 1283 'parent': {'scope': 'netdev'}, 1284 'handle': {'scope': 'queue', 'id': 0}, 1285 'metric': 'bps', 1286 'bw-max': 1000}, 1287 {'ifindex': cfg.ifindex, 1288 'parent': {'scope': 'netdev'}, 1289 'handle': {'scope': 'queue', 'id': 1}, 1290 'metric': 'bps', 1291 'bw-max': 2000}]) 1292 1293 # Restore the original channels number, no expected changes 1294 cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") 1295 shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) 1296 ksft_eq(shapers, [{'ifindex': cfg.ifindex, 1297 'parent': {'scope': 'netdev'}, 1298 'handle': {'scope': 'queue', 'id': 0}, 1299 'metric': 'bps', 1300 'bw-max': 1000}, 1301 {'ifindex': cfg.ifindex, 1302 'parent': {'scope': 'netdev'}, 1303 'handle': {'scope': 'queue', 'id': 1}, 1304 'metric': 'bps', 1305 'bw-max': 2000}]) 1306 1307 # Final cleanup. 1308 for i in range(0, 2): 1309 nl_shaper.delete({'ifindex': cfg.ifindex, 1310 'handle': {'scope': 'queue', 'id': i}}) 1311 1312def dup_leaves(cfg, nl_shaper) -> None: 1313 """ Ensure that the kernel rejects duplicate leaves. """ 1314 _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], 1315 "device does not support node scope shapers with bw_max and metric bps") 1316 _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], 1317 "device does not support nested queue scope shapers with weight") 1318 1319 node_handle = None 1320 with ksft_raises(NlError) as cm: 1321 node_handle = nl_shaper.group({ 1322 'ifindex': cfg.ifindex, 1323 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, 1324 'weight': 1}, 1325 {'handle': {'scope': 'queue', 'id': 0}, 1326 'weight': 2}], 1327 'handle': {'scope':'node'}, 1328 'metric': 'bps', 1329 'bw-max': 10000}) 1330 1331 # Clean up in case the kernel wrongly accepted the request. 1332 if node_handle: 1333 _delete_shaper(cfg, nl_shaper, node_handle['handle']) 1334 _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) 1335 1336 # ksft_raises() has already recorded the failure if nothing was raised. 1337 if cm.exception is None: 1338 return 1339 ksft_eq(cm.exception.error, errno.EINVAL) 1340 1341def main() -> None: 1342 with NetDrvEnv(__file__, queue_count=4) as cfg: 1343 cfg.queues = False 1344 cfg.netdev = False 1345 ksft_run([get_shapers, 1346 get_caps, 1347 set_qshapers, 1348 del_qshapers, 1349 set_nshapers, 1350 del_nshapers, 1351 set_all_supported_attrs, 1352 invalid_set_preserves_state, 1353 mixed_parent_group_requires_parent, 1354 recursive_empty_node_cleanup, 1355 basic_groups, 1356 basic_groups_with_rate, 1357 qgroups, 1358 set_node_shaper, 1359 group_update_rate, 1360 delegation, 1361 nested_depth_limit, 1362 delete_child_reparent, 1363 move_queue_between_nodes, 1364 reject_reparenting, 1365 dup_leaves, 1366 queue_update], 1367 args=(cfg, NetshaperFamily())) 1368 ksft_exit() 1369 1370 1371if __name__ == "__main__": 1372 main() 1373