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