5
5
import random
6
6
from lib .py import ksft_run , ksft_pr , ksft_exit , ksft_eq , ksft_ge , ksft_lt
7
7
from lib .py import NetDrvEpEnv
8
- from lib .py import NetdevFamily
8
+ from lib .py import EthtoolFamily , NetdevFamily
9
9
from lib .py import KsftSkipEx
10
10
from lib .py import rand_port
11
11
from lib .py import ethtool , ip , defer , GenerateTraffic , CmdExitFailure
@@ -63,10 +63,33 @@ def _get_rx_cnts(cfg, prev=None):
63
63
return queue_stats
64
64
65
65
66
+ def _send_traffic_check (cfg , port , name , params ):
67
+ # params is a dict with 3 possible keys:
68
+ # - "target": required, which queues we expect to get iperf traffic
69
+ # - "empty": optional, which queues should see no traffic at all
70
+ # - "noise": optional, which queues we expect to see low traffic;
71
+ # used for queues of the main context, since some background
72
+ # OS activity may use those queues while we're testing
73
+ # the value for each is a list, or some other iterable containing queue ids.
74
+
75
+ cnts = _get_rx_cnts (cfg )
76
+ GenerateTraffic (cfg , port = port ).wait_pkts_and_stop (20000 )
77
+ cnts = _get_rx_cnts (cfg , prev = cnts )
78
+
79
+ directed = sum (cnts [i ] for i in params ['target' ])
80
+
81
+ ksft_ge (directed , 20000 , f"traffic on { name } : " + str (cnts ))
82
+ if params .get ('noise' ):
83
+ ksft_lt (sum (cnts [i ] for i in params ['noise' ]), directed / 2 ,
84
+ "traffic on other queues:" + str (cnts ))
85
+ if params .get ('empty' ):
86
+ ksft_eq (sum (cnts [i ] for i in params ['empty' ]), 0 ,
87
+ "traffic on inactive queues: " + str (cnts ))
88
+
89
+
66
90
def test_rss_key_indir (cfg ):
67
- """
68
- Test basics like updating the main RSS key and indirection table.
69
- """
91
+ """Test basics like updating the main RSS key and indirection table."""
92
+
70
93
if len (_get_rx_cnts (cfg )) < 2 :
71
94
KsftSkipEx ("Device has only one queue (or doesn't support queue stats)" )
72
95
@@ -89,6 +112,7 @@ def test_rss_key_indir(cfg):
89
112
90
113
# Set the indirection table
91
114
ethtool (f"-X { cfg .ifname } equal 2" )
115
+ reset_indir = defer (ethtool , f"-X { cfg .ifname } default" )
92
116
data = get_rss (cfg )
93
117
ksft_eq (0 , min (data ['rss-indirection-table' ]))
94
118
ksft_eq (1 , max (data ['rss-indirection-table' ]))
@@ -104,7 +128,7 @@ def test_rss_key_indir(cfg):
104
128
ksft_eq (sum (cnts [2 :]), 0 , "traffic on unused queues: " + str (cnts ))
105
129
106
130
# Restore, and check traffic gets spread again
107
- ethtool ( f"-X { cfg . ifname } default" )
131
+ reset_indir . exec ( )
108
132
109
133
cnts = _get_rx_cnts (cfg )
110
134
GenerateTraffic (cfg ).wait_pkts_and_stop (20000 )
@@ -113,6 +137,143 @@ def test_rss_key_indir(cfg):
113
137
ksft_lt (sum (cnts [:2 ]), sum (cnts [2 :]), "traffic distributed: " + str (cnts ))
114
138
115
139
140
+ def test_rss_queue_reconfigure (cfg , main_ctx = True ):
141
+ """Make sure queue changes can't override requested RSS config.
142
+
143
+ By default main RSS table should change to include all queues.
144
+ When user sets a specific RSS config the driver should preserve it,
145
+ even when queue count changes. Driver should refuse to deactivate
146
+ queues used in the user-set RSS config.
147
+ """
148
+
149
+ if not main_ctx :
150
+ require_ntuple (cfg )
151
+
152
+ # Start with 4 queues, an arbitrary known number.
153
+ try :
154
+ qcnt = len (_get_rx_cnts (cfg ))
155
+ ethtool (f"-L { cfg .ifname } combined 4" )
156
+ defer (ethtool , f"-L { cfg .ifname } combined { qcnt } " )
157
+ except :
158
+ raise KsftSkipEx ("Not enough queues for the test or qstat not supported" )
159
+
160
+ if main_ctx :
161
+ ctx_id = 0
162
+ ctx_ref = ""
163
+ else :
164
+ ctx_id = ethtool_create (cfg , "-X" , "context new" )
165
+ ctx_ref = f"context { ctx_id } "
166
+ defer (ethtool , f"-X { cfg .ifname } { ctx_ref } delete" )
167
+
168
+ # Indirection table should be distributing to all queues.
169
+ data = get_rss (cfg , context = ctx_id )
170
+ ksft_eq (0 , min (data ['rss-indirection-table' ]))
171
+ ksft_eq (3 , max (data ['rss-indirection-table' ]))
172
+
173
+ # Increase queues, indirection table should be distributing to all queues.
174
+ # It's unclear whether tables of additional contexts should be reset, too.
175
+ if main_ctx :
176
+ ethtool (f"-L { cfg .ifname } combined 5" )
177
+ data = get_rss (cfg )
178
+ ksft_eq (0 , min (data ['rss-indirection-table' ]))
179
+ ksft_eq (4 , max (data ['rss-indirection-table' ]))
180
+ ethtool (f"-L { cfg .ifname } combined 4" )
181
+
182
+ # Configure the table explicitly
183
+ port = rand_port ()
184
+ ethtool (f"-X { cfg .ifname } { ctx_ref } weight 1 0 0 1" )
185
+ if main_ctx :
186
+ other_key = 'empty'
187
+ defer (ethtool , f"-X { cfg .ifname } default" )
188
+ else :
189
+ other_key = 'noise'
190
+ flow = f"flow-type tcp{ cfg .addr_ipver } dst-port { port } context { ctx_id } "
191
+ ntuple = ethtool_create (cfg , "-N" , flow )
192
+ defer (ethtool , f"-N { cfg .ifname } delete { ntuple } " )
193
+
194
+ _send_traffic_check (cfg , port , ctx_ref , { 'target' : (0 , 3 ),
195
+ other_key : (1 , 2 ) })
196
+
197
+ # We should be able to increase queues, but table should be left untouched
198
+ ethtool (f"-L { cfg .ifname } combined 5" )
199
+ data = get_rss (cfg , context = ctx_id )
200
+ ksft_eq ({0 , 3 }, set (data ['rss-indirection-table' ]))
201
+
202
+ _send_traffic_check (cfg , port , ctx_ref , { 'target' : (0 , 3 ),
203
+ other_key : (1 , 2 , 4 ) })
204
+
205
+ # Setting queue count to 3 should fail, queue 3 is used
206
+ try :
207
+ ethtool (f"-L { cfg .ifname } combined 3" )
208
+ except CmdExitFailure :
209
+ pass
210
+ else :
211
+ raise Exception (f"Driver didn't prevent us from deactivating a used queue (context { ctx_id } )" )
212
+
213
+
214
+ def test_rss_resize (cfg ):
215
+ """Test resizing of the RSS table.
216
+
217
+ Some devices dynamically increase and decrease the size of the RSS
218
+ indirection table based on the number of enabled queues.
219
+ When that happens driver must maintain the balance of entries
220
+ (preferably duplicating the smaller table).
221
+ """
222
+
223
+ channels = cfg .ethnl .channels_get ({'header' : {'dev-index' : cfg .ifindex }})
224
+ ch_max = channels ['combined-max' ]
225
+ qcnt = channels ['combined-count' ]
226
+
227
+ if ch_max < 2 :
228
+ raise KsftSkipEx (f"Not enough queues for the test: { ch_max } " )
229
+
230
+ ethtool (f"-L { cfg .ifname } combined 2" )
231
+ defer (ethtool , f"-L { cfg .ifname } combined { qcnt } " )
232
+
233
+ ethtool (f"-X { cfg .ifname } weight 1 7" )
234
+ defer (ethtool , f"-X { cfg .ifname } default" )
235
+
236
+ ethtool (f"-L { cfg .ifname } combined { ch_max } " )
237
+ data = get_rss (cfg )
238
+ ksft_eq (0 , min (data ['rss-indirection-table' ]))
239
+ ksft_eq (1 , max (data ['rss-indirection-table' ]))
240
+
241
+ ksft_eq (7 ,
242
+ data ['rss-indirection-table' ].count (1 ) /
243
+ data ['rss-indirection-table' ].count (0 ),
244
+ f"Table imbalance after resize: { data ['rss-indirection-table' ]} " )
245
+
246
+
247
+ def test_hitless_key_update (cfg ):
248
+ """Test that flows may be rehashed without impacting traffic.
249
+
250
+ Some workloads may want to rehash the flows in response to an imbalance.
251
+ Most effective way to do that is changing the RSS key. Check that changing
252
+ the key does not cause link flaps or traffic disruption.
253
+
254
+ Disrupting traffic for key update is not a bug, but makes the key
255
+ update unusable for rehashing under load.
256
+ """
257
+ data = get_rss (cfg )
258
+ key_len = len (data ['rss-hash-key' ])
259
+
260
+ key = _rss_key_rand (key_len )
261
+
262
+ tgen = GenerateTraffic (cfg )
263
+ try :
264
+ errors0 , carrier0 = get_drop_err_sum (cfg )
265
+ t0 = datetime .datetime .now ()
266
+ ethtool (f"-X { cfg .ifname } hkey " + _rss_key_str (key ))
267
+ t1 = datetime .datetime .now ()
268
+ errors1 , carrier1 = get_drop_err_sum (cfg )
269
+ finally :
270
+ tgen .wait_pkts_and_stop (5000 )
271
+
272
+ ksft_lt ((t1 - t0 ).total_seconds (), 0.2 )
273
+ ksft_eq (errors1 - errors1 , 0 )
274
+ ksft_eq (carrier1 - carrier0 , 0 )
275
+
276
+
116
277
def test_rss_context (cfg , ctx_cnt = 1 , create_with_cfg = None ):
117
278
"""
118
279
Test separating traffic into RSS contexts.
@@ -170,15 +331,10 @@ def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
170
331
defer (ethtool , f"-N { cfg .ifname } delete { ntuple } " )
171
332
172
333
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
- directed = sum (cnts [2 + i * 2 :4 + i * 2 ])
178
-
179
- ksft_lt (sum (cnts [ :2 ]), directed / 2 , "traffic on main context:" + str (cnts ))
180
- ksft_ge (directed , 20000 , f"traffic on context { i } : " + str (cnts ))
181
- ksft_eq (sum (cnts [2 :2 + i * 2 ] + cnts [4 + i * 2 :]), 0 , "traffic on other contexts: " + str (cnts ))
334
+ _send_traffic_check (cfg , ports [i ], f"context { i } " ,
335
+ { 'target' : (2 + i * 2 , 3 + i * 2 ),
336
+ 'noise' : (0 , 1 ),
337
+ 'empty' : list (range (2 , 2 + i * 2 )) + list (range (4 + i * 2 , 2 + 2 * ctx_cnt )) })
182
338
183
339
if requested_ctx_cnt != ctx_cnt :
184
340
raise KsftSkipEx (f"Tested only { ctx_cnt } contexts, wanted { requested_ctx_cnt } " )
@@ -196,6 +352,10 @@ def test_rss_context4_create_with_cfg(cfg):
196
352
test_rss_context (cfg , 4 , create_with_cfg = True )
197
353
198
354
355
+ def test_rss_context_queue_reconfigure (cfg ):
356
+ test_rss_queue_reconfigure (cfg , main_ctx = False )
357
+
358
+
199
359
def test_rss_context_out_of_order (cfg , ctx_cnt = 4 ):
200
360
"""
201
361
Test separating traffic into RSS contexts.
@@ -230,18 +390,19 @@ def remove_ctx(idx):
230
390
231
391
def check_traffic ():
232
392
for i in range (ctx_cnt ):
233
- cnts = _get_rx_cnts (cfg )
234
- GenerateTraffic (cfg , port = ports [i ]).wait_pkts_and_stop (20000 )
235
- cnts = _get_rx_cnts (cfg , prev = cnts )
236
-
237
393
if ctx [i ]:
238
- directed = sum (cnts [2 + i * 2 :4 + i * 2 ])
239
- ksft_lt (sum (cnts [ :2 ]), directed / 2 , "traffic on main context:" + str (cnts ))
240
- ksft_ge (directed , 20000 , f"traffic on context { i } : " + str (cnts ))
241
- ksft_eq (sum (cnts [2 :2 + i * 2 ] + cnts [4 + i * 2 :]), 0 , "traffic on other contexts: " + str (cnts ))
394
+ expected = {
395
+ 'target' : (2 + i * 2 , 3 + i * 2 ),
396
+ 'noise' : (0 , 1 ),
397
+ 'empty' : list (range (2 , 2 + i * 2 )) + list (range (4 + i * 2 , 2 + 2 * ctx_cnt ))
398
+ }
242
399
else :
243
- ksft_ge (sum (cnts [ :2 ]), 20000 , "traffic on main context:" + str (cnts ))
244
- ksft_eq (sum (cnts [2 : ]), 0 , "traffic on other contexts: " + str (cnts ))
400
+ expected = {
401
+ 'target' : (0 , 1 ),
402
+ 'empty' : range (2 , 2 + 2 * ctx_cnt )
403
+ }
404
+
405
+ _send_traffic_check (cfg , ports [i ], f"context { i } " , expected )
245
406
246
407
# Use queues 0 and 1 for normal traffic
247
408
ethtool (f"-X { cfg .ifname } equal 2" )
@@ -344,10 +505,13 @@ def test_rss_context_overlap2(cfg):
344
505
345
506
def main () -> None :
346
507
with NetDrvEpEnv (__file__ , nsim_test = False ) as cfg :
508
+ cfg .ethnl = EthtoolFamily ()
347
509
cfg .netdevnl = NetdevFamily ()
348
510
349
- ksft_run ([test_rss_key_indir ,
511
+ ksft_run ([test_rss_key_indir , test_rss_queue_reconfigure ,
512
+ test_rss_resize , test_hitless_key_update ,
350
513
test_rss_context , test_rss_context4 , test_rss_context32 ,
514
+ test_rss_context_queue_reconfigure ,
351
515
test_rss_context_overlap , test_rss_context_overlap2 ,
352
516
test_rss_context_out_of_order , test_rss_context4_create_with_cfg ],
353
517
args = (cfg , ))
0 commit comments