Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'selftests-drv-net-add-ability-to-schedule-cleanup-with-defer'

Jakub Kicinski says:

====================
selftests: drv-net: add ability to schedule cleanup with defer()

Introduce a defer / cleanup mechanism for driver selftests.
More detailed info in the second patch.
====================

Link: https://patch.msgid.link/20240627185502.3069139-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+167 -141
+98 -127
tools/testing/selftests/drivers/net/hw/rss_ctx.py
··· 8 8 from lib.py import NetdevFamily 9 9 from lib.py import KsftSkipEx 10 10 from lib.py import rand_port 11 - from lib.py import ethtool, ip, GenerateTraffic, CmdExitFailure 11 + from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure 12 12 13 13 14 14 def _rss_key_str(key): ··· 127 127 128 128 # Try to allocate more queues when necessary 129 129 qcnt = len(_get_rx_cnts(cfg)) 130 - if qcnt >= 2 + 2 * ctx_cnt: 131 - qcnt = None 132 - else: 130 + if qcnt < 2 + 2 * ctx_cnt: 133 131 try: 134 132 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 135 133 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 134 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 136 135 except: 137 136 raise KsftSkipEx("Not enough queues for the test") 138 137 139 - ntuple = [] 140 - ctx_id = [] 141 138 ports = [] 142 - try: 143 - # Use queues 0 and 1 for normal traffic 144 - ethtool(f"-X {cfg.ifname} equal 2") 145 139 146 - for i in range(ctx_cnt): 147 - want_cfg = f"start {2 + i * 2} equal 2" 148 - create_cfg = want_cfg if create_with_cfg else "" 140 + # Use queues 0 and 1 for normal traffic 141 + ethtool(f"-X {cfg.ifname} equal 2") 142 + defer(ethtool, f"-X {cfg.ifname} default") 149 143 150 - try: 151 - ctx_id.append(ethtool_create(cfg, "-X", f"context new {create_cfg}")) 152 - except CmdExitFailure: 153 - # try to carry on and skip at the end 154 - if i == 0: 155 - raise 156 - ksft_pr(f"Failed to create context {i + 1}, trying to test what we got") 157 - ctx_cnt = i 158 - break 144 + for i in range(ctx_cnt): 145 + want_cfg = f"start {2 + i * 2} equal 2" 146 + create_cfg = want_cfg if create_with_cfg else "" 159 147 160 - if not create_with_cfg: 161 - ethtool(f"-X {cfg.ifname} context {ctx_id[i]} {want_cfg}") 148 + try: 149 + ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}") 150 + defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 151 + except CmdExitFailure: 152 + # try to carry on and skip at the end 153 + if i == 0: 154 + raise 155 + ksft_pr(f"Failed to create context {i + 1}, trying to test what we got") 156 + ctx_cnt = i 157 + break 162 158 163 - # Sanity check the context we just created 164 - data = get_rss(cfg, ctx_id[i]) 165 - ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data)) 166 - ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data)) 159 + if not create_with_cfg: 160 + ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}") 167 161 168 - ports.append(rand_port()) 169 - flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id[i]}" 170 - ntuple.append(ethtool_create(cfg, "-N", flow)) 162 + # Sanity check the context we just created 163 + data = get_rss(cfg, ctx_id) 164 + ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data)) 165 + ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data)) 171 166 172 - for i in range(ctx_cnt): 173 - cnts = _get_rx_cnts(cfg) 174 - GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 175 - cnts = _get_rx_cnts(cfg, prev=cnts) 167 + ports.append(rand_port()) 168 + flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}" 169 + ntuple = ethtool_create(cfg, "-N", flow) 170 + defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") 176 171 177 - ksft_lt(sum(cnts[ :2]), 10000, "traffic on main context:" + str(cnts)) 178 - ksft_ge(sum(cnts[2+i*2:4+i*2]), 20000, f"traffic on context {i}: " + str(cnts)) 179 - ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 180 - finally: 181 - for nid in ntuple: 182 - ethtool(f"-N {cfg.ifname} delete {nid}") 183 - for cid in ctx_id: 184 - ethtool(f"-X {cfg.ifname} context {cid} delete") 185 - ethtool(f"-X {cfg.ifname} default") 186 - if qcnt: 187 - ethtool(f"-L {cfg.ifname} combined {qcnt}") 172 + for i in range(ctx_cnt): 173 + cnts = _get_rx_cnts(cfg) 174 + GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 175 + cnts = _get_rx_cnts(cfg, prev=cnts) 176 + 177 + ksft_lt(sum(cnts[ :2]), 10000, "traffic on main context:" + str(cnts)) 178 + ksft_ge(sum(cnts[2+i*2:4+i*2]), 20000, f"traffic on context {i}: " + str(cnts)) 179 + ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 188 180 189 181 if requested_ctx_cnt != ctx_cnt: 190 182 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") ··· 208 216 209 217 # Try to allocate more queues when necessary 210 218 qcnt = len(_get_rx_cnts(cfg)) 211 - if qcnt >= 2 + 2 * ctx_cnt: 212 - qcnt = None 213 - else: 219 + if qcnt < 2 + 2 * ctx_cnt: 214 220 try: 215 221 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 216 222 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 223 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 217 224 except: 218 225 raise KsftSkipEx("Not enough queues for the test") 219 226 220 227 ntuple = [] 221 - ctx_id = [] 228 + ctx = [] 222 229 ports = [] 223 230 224 231 def remove_ctx(idx): 225 - ethtool(f"-N {cfg.ifname} delete {ntuple[idx]}") 232 + ntuple[idx].exec() 226 233 ntuple[idx] = None 227 - ethtool(f"-X {cfg.ifname} context {ctx_id[idx]} delete") 228 - ctx_id[idx] = None 234 + ctx[idx].exec() 235 + ctx[idx] = None 229 236 230 237 def check_traffic(): 231 238 for i in range(ctx_cnt): ··· 232 241 GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 233 242 cnts = _get_rx_cnts(cfg, prev=cnts) 234 243 235 - if ctx_id[i] is None: 244 + if ctx[i]: 236 245 ksft_lt(sum(cnts[ :2]), 10000, "traffic on main context:" + str(cnts)) 237 246 ksft_ge(sum(cnts[2+i*2:4+i*2]), 20000, f"traffic on context {i}: " + str(cnts)) 238 247 ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) ··· 240 249 ksft_ge(sum(cnts[ :2]), 20000, "traffic on main context:" + str(cnts)) 241 250 ksft_eq(sum(cnts[2: ]), 0, "traffic on other contexts: " + str(cnts)) 242 251 243 - try: 244 - # Use queues 0 and 1 for normal traffic 245 - ethtool(f"-X {cfg.ifname} equal 2") 252 + # Use queues 0 and 1 for normal traffic 253 + ethtool(f"-X {cfg.ifname} equal 2") 254 + defer(ethtool, f"-X {cfg.ifname} default") 246 255 247 - for i in range(ctx_cnt): 248 - ctx_id.append(ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")) 256 + for i in range(ctx_cnt): 257 + ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2") 258 + ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")) 249 259 250 - ports.append(rand_port()) 251 - flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id[i]}" 252 - ntuple.append(ethtool_create(cfg, "-N", flow)) 260 + ports.append(rand_port()) 261 + flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}" 262 + ntuple_id = ethtool_create(cfg, "-N", flow) 263 + ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")) 253 264 254 - check_traffic() 265 + check_traffic() 255 266 256 - # Remove middle context 257 - remove_ctx(ctx_cnt // 2) 258 - check_traffic() 267 + # Remove middle context 268 + remove_ctx(ctx_cnt // 2) 269 + check_traffic() 259 270 260 - # Remove first context 261 - remove_ctx(0) 262 - check_traffic() 271 + # Remove first context 272 + remove_ctx(0) 273 + check_traffic() 263 274 264 - # Remove last context 265 - remove_ctx(-1) 266 - check_traffic() 267 - 268 - finally: 269 - for nid in ntuple: 270 - if nid is not None: 271 - ethtool(f"-N {cfg.ifname} delete {nid}") 272 - for cid in ctx_id: 273 - if cid is not None: 274 - ethtool(f"-X {cfg.ifname} context {cid} delete") 275 - ethtool(f"-X {cfg.ifname} default") 276 - if qcnt: 277 - ethtool(f"-L {cfg.ifname} combined {qcnt}") 275 + # Remove last context 276 + remove_ctx(-1) 277 + check_traffic() 278 278 279 279 if requested_ctx_cnt != ctx_cnt: 280 280 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") ··· 280 298 require_ntuple(cfg) 281 299 282 300 queue_cnt = len(_get_rx_cnts(cfg)) 283 - if queue_cnt >= 4: 284 - queue_cnt = None 285 - else: 301 + if queue_cnt < 4: 286 302 try: 287 303 ksft_pr(f"Increasing queue count {queue_cnt} -> 4") 288 304 ethtool(f"-L {cfg.ifname} combined 4") 305 + defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}") 289 306 except: 290 307 raise KsftSkipEx("Not enough queues for the test") 291 308 292 - ctx_id = None 293 - ntuple = None 294 309 if other_ctx == 0: 295 310 ethtool(f"-X {cfg.ifname} equal 4") 311 + defer(ethtool, f"-X {cfg.ifname} default") 296 312 else: 297 313 other_ctx = ethtool_create(cfg, "-X", "context new") 298 314 ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4") 315 + defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete") 299 316 300 - try: 301 - ctx_id = ethtool_create(cfg, "-X", "context new") 302 - ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2") 317 + ctx_id = ethtool_create(cfg, "-X", "context new") 318 + ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2") 319 + defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 303 320 304 - port = rand_port() 305 - if other_ctx: 306 - flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {other_ctx}" 307 - ntuple = ethtool_create(cfg, "-N", flow) 321 + port = rand_port() 322 + if other_ctx: 323 + flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {other_ctx}" 324 + ntuple_id = ethtool_create(cfg, "-N", flow) 325 + ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 308 326 309 - # Test the main context 310 - cnts = _get_rx_cnts(cfg) 311 - GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 312 - cnts = _get_rx_cnts(cfg, prev=cnts) 327 + # Test the main context 328 + cnts = _get_rx_cnts(cfg) 329 + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 330 + cnts = _get_rx_cnts(cfg, prev=cnts) 313 331 314 - ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) 315 - ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) 316 - ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts)) 317 - if other_ctx == 0: 318 - ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 332 + ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) 333 + ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) 334 + ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts)) 335 + if other_ctx == 0: 336 + ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 319 337 320 - # Now create a rule for context 1 and make sure traffic goes to a subset 321 - if other_ctx: 322 - ethtool(f"-N {cfg.ifname} delete {ntuple}") 323 - ntuple = None 324 - flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}" 325 - ntuple = ethtool_create(cfg, "-N", flow) 338 + # Now create a rule for context 1 and make sure traffic goes to a subset 339 + if other_ctx: 340 + ntuple.exec() 341 + flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}" 342 + ntuple_id = ethtool_create(cfg, "-N", flow) 343 + defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 326 344 327 - cnts = _get_rx_cnts(cfg) 328 - GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 329 - cnts = _get_rx_cnts(cfg, prev=cnts) 345 + cnts = _get_rx_cnts(cfg) 346 + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 347 + cnts = _get_rx_cnts(cfg, prev=cnts) 330 348 331 - ksft_lt(sum(cnts[ :2]), 7000, "traffic on main context: " + str(cnts)) 332 - ksft_ge(sum(cnts[2:4]), 20000, "traffic on extra context: " + str(cnts)) 333 - if other_ctx == 0: 334 - ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 335 - finally: 336 - if ntuple is not None: 337 - ethtool(f"-N {cfg.ifname} delete {ntuple}") 338 - if ctx_id: 339 - ethtool(f"-X {cfg.ifname} context {ctx_id} delete") 340 - if other_ctx == 0: 341 - ethtool(f"-X {cfg.ifname} default") 342 - else: 343 - ethtool(f"-X {cfg.ifname} context {other_ctx} delete") 344 - if queue_cnt: 345 - ethtool(f"-L {cfg.ifname} combined {queue_cnt}") 349 + ksft_lt(sum(cnts[ :2]), 7000, "traffic on main context: " + str(cnts)) 350 + ksft_ge(sum(cnts[2:4]), 20000, "traffic on extra context: " + str(cnts)) 351 + if other_ctx == 0: 352 + ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 346 353 347 354 348 355 def test_rss_context_overlap2(cfg):
+35 -14
tools/testing/selftests/net/lib/py/ksft.py
··· 6 6 import time 7 7 import traceback 8 8 from .consts import KSFT_MAIN_NAME 9 + from .utils import global_defer_queue 9 10 10 11 KSFT_RESULT = None 11 12 KSFT_RESULT_ALL = True ··· 109 108 print(res) 110 109 111 110 111 + def ksft_flush_defer(): 112 + global KSFT_RESULT 113 + 114 + i = 0 115 + qlen_start = len(global_defer_queue) 116 + while global_defer_queue: 117 + i += 1 118 + entry = global_defer_queue.pop() 119 + try: 120 + entry.exec_only() 121 + except: 122 + ksft_pr(f"Exception while handling defer / cleanup (callback {i} of {qlen_start})!") 123 + tb = traceback.format_exc() 124 + for line in tb.strip().split('\n'): 125 + ksft_pr("Defer Exception|", line) 126 + KSFT_RESULT = False 127 + 128 + 112 129 def ksft_run(cases=None, globs=None, case_pfx=None, args=()): 113 130 cases = cases or [] 114 131 ··· 149 130 for case in cases: 150 131 KSFT_RESULT = True 151 132 cnt += 1 133 + comment = "" 134 + cnt_key = "" 135 + 152 136 try: 153 137 case(*args) 154 138 except KsftSkipEx as e: 155 - ktap_result(True, cnt, case, comment="SKIP " + str(e)) 156 - totals['skip'] += 1 157 - continue 139 + comment = "SKIP " + str(e) 140 + cnt_key = 'skip' 158 141 except KsftXfailEx as e: 159 - ktap_result(True, cnt, case, comment="XFAIL " + str(e)) 160 - totals['xfail'] += 1 161 - continue 142 + comment = "XFAIL " + str(e) 143 + cnt_key = 'xfail' 162 144 except Exception as e: 163 145 tb = traceback.format_exc() 164 146 for line in tb.strip().split('\n'): 165 147 ksft_pr("Exception|", line) 166 - ktap_result(False, cnt, case) 167 - totals['fail'] += 1 168 - continue 148 + KSFT_RESULT = False 149 + cnt_key = 'fail' 169 150 170 - ktap_result(KSFT_RESULT, cnt, case) 171 - if KSFT_RESULT: 172 - totals['pass'] += 1 173 - else: 174 - totals['fail'] += 1 151 + ksft_flush_defer() 152 + 153 + if not cnt_key: 154 + cnt_key = 'pass' if KSFT_RESULT else 'fail' 155 + 156 + ktap_result(KSFT_RESULT, cnt, case, comment=comment) 157 + totals[cnt_key] += 1 175 158 176 159 print( 177 160 f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0"
+34
tools/testing/selftests/net/lib/py/utils.py
··· 66 66 return self.process(terminate=self.terminate, fail=self.check_fail) 67 67 68 68 69 + global_defer_queue = [] 70 + 71 + 72 + class defer: 73 + def __init__(self, func, *args, **kwargs): 74 + global global_defer_queue 75 + 76 + if not callable(func): 77 + raise Exception("defer created with un-callable object, did you call the function instead of passing its name?") 78 + 79 + self.func = func 80 + self.args = args 81 + self.kwargs = kwargs 82 + 83 + self._queue = global_defer_queue 84 + self._queue.append(self) 85 + 86 + def __enter__(self): 87 + return self 88 + 89 + def __exit__(self, ex_type, ex_value, ex_tb): 90 + return self.exec() 91 + 92 + def exec_only(self): 93 + self.func(*self.args, **self.kwargs) 94 + 95 + def cancel(self): 96 + self._queue.remove(self) 97 + 98 + def exec(self): 99 + self.cancel() 100 + self.exec_only() 101 + 102 + 69 103 def tool(name, args, json=None, ns=None, host=None): 70 104 cmd_str = name + ' ' 71 105 if json: