55import random
66from lib .py import ksft_run , ksft_pr , ksft_exit , ksft_eq , ksft_ge , ksft_lt
77from lib .py import NetDrvEpEnv
8- from lib .py import NetdevFamily
8+ from lib .py import EthtoolFamily , NetdevFamily
99from lib .py import KsftSkipEx
1010from lib .py import rand_port
1111from lib .py import ethtool , ip , defer , GenerateTraffic , CmdExitFailure
@@ -63,10 +63,33 @@ def _get_rx_cnts(cfg, prev=None):
6363 return queue_stats
6464
6565
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+
6690def 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+
7093 if len (_get_rx_cnts (cfg )) < 2 :
7194 KsftSkipEx ("Device has only one queue (or doesn't support queue stats)" )
7295
@@ -89,6 +112,7 @@ def test_rss_key_indir(cfg):
89112
90113 # Set the indirection table
91114 ethtool (f"-X { cfg .ifname } equal 2" )
115+ reset_indir = defer (ethtool , f"-X { cfg .ifname } default" )
92116 data = get_rss (cfg )
93117 ksft_eq (0 , min (data ['rss-indirection-table' ]))
94118 ksft_eq (1 , max (data ['rss-indirection-table' ]))
@@ -104,7 +128,7 @@ def test_rss_key_indir(cfg):
104128 ksft_eq (sum (cnts [2 :]), 0 , "traffic on unused queues: " + str (cnts ))
105129
106130 # Restore, and check traffic gets spread again
107- ethtool ( f"-X { cfg . ifname } default" )
131+ reset_indir . exec ( )
108132
109133 cnts = _get_rx_cnts (cfg )
110134 GenerateTraffic (cfg ).wait_pkts_and_stop (20000 )
@@ -113,6 +137,143 @@ def test_rss_key_indir(cfg):
113137 ksft_lt (sum (cnts [:2 ]), sum (cnts [2 :]), "traffic distributed: " + str (cnts ))
114138
115139
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+
116277def test_rss_context (cfg , ctx_cnt = 1 , create_with_cfg = None ):
117278 """
118279 Test separating traffic into RSS contexts.
@@ -170,15 +331,10 @@ def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
170331 defer (ethtool , f"-N { cfg .ifname } delete { ntuple } " )
171332
172333 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 )) })
182338
183339 if requested_ctx_cnt != ctx_cnt :
184340 raise KsftSkipEx (f"Tested only { ctx_cnt } contexts, wanted { requested_ctx_cnt } " )
@@ -196,6 +352,10 @@ def test_rss_context4_create_with_cfg(cfg):
196352 test_rss_context (cfg , 4 , create_with_cfg = True )
197353
198354
355+ def test_rss_context_queue_reconfigure (cfg ):
356+ test_rss_queue_reconfigure (cfg , main_ctx = False )
357+
358+
199359def test_rss_context_out_of_order (cfg , ctx_cnt = 4 ):
200360 """
201361 Test separating traffic into RSS contexts.
@@ -230,18 +390,19 @@ def remove_ctx(idx):
230390
231391 def check_traffic ():
232392 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-
237393 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+ }
242399 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 )
245406
246407 # Use queues 0 and 1 for normal traffic
247408 ethtool (f"-X { cfg .ifname } equal 2" )
@@ -344,10 +505,13 @@ def test_rss_context_overlap2(cfg):
344505
345506def main () -> None :
346507 with NetDrvEpEnv (__file__ , nsim_test = False ) as cfg :
508+ cfg .ethnl = EthtoolFamily ()
347509 cfg .netdevnl = NetdevFamily ()
348510
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 ,
350513 test_rss_context , test_rss_context4 , test_rss_context32 ,
514+ test_rss_context_queue_reconfigure ,
351515 test_rss_context_overlap , test_rss_context_overlap2 ,
352516 test_rss_context_out_of_order , test_rss_context4_create_with_cfg ],
353517 args = (cfg , ))
0 commit comments