shaper.py 19 KB

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