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