1#!/usr/bin/env python 2# SPDX-License-Identifier: GPL-2.0 3 4import subprocess 5import json as j 6import random 7 8 9class SkipTest(Exception): 10 pass 11 12 13class RandomValuePicker: 14 """ 15 Class for storing shared buffer configuration. Can handle 3 different 16 objects, pool, tcbind and portpool. Provide an interface to get random 17 values for a specific object type as the follow: 18 1. Pool: 19 - random size 20 21 2. TcBind: 22 - random pool number 23 - random threshold 24 25 3. PortPool: 26 - random threshold 27 """ 28 def __init__(self, pools): 29 self._pools = [] 30 for pool in pools: 31 self._pools.append(pool) 32 33 def _cell_size(self): 34 return self._pools[0]["cell_size"] 35 36 def _get_static_size(self, th): 37 # For threshold of 16, this works out to be about 12MB on Spectrum-1, 38 # and about 17MB on Spectrum-2. 39 return th * 8000 * self._cell_size() 40 41 def _get_size(self): 42 return self._get_static_size(16) 43 44 def _get_thtype(self): 45 return "static" 46 47 def _get_th(self, pool): 48 # Threshold value could be any integer between 3 to 16 49 th = random.randint(3, 16) 50 if pool["thtype"] == "dynamic": 51 return th 52 else: 53 return self._get_static_size(th) 54 55 def _get_pool(self, direction): 56 ing_pools = [] 57 egr_pools = [] 58 for pool in self._pools: 59 if pool["type"] == "ingress": 60 ing_pools.append(pool) 61 else: 62 egr_pools.append(pool) 63 if direction == "ingress": 64 arr = ing_pools 65 else: 66 arr = egr_pools 67 return arr[random.randint(0, len(arr) - 1)] 68 69 def get_value(self, objid): 70 if isinstance(objid, Pool): 71 if objid["pool"] in [4, 8, 9, 10]: 72 # The threshold type of pools 4, 8, 9 and 10 cannot be changed 73 raise SkipTest() 74 else: 75 return (self._get_size(), self._get_thtype()) 76 if isinstance(objid, TcBind): 77 if objid["tc"] >= 8: 78 # Multicast TCs cannot be changed 79 raise SkipTest() 80 else: 81 pool = self._get_pool(objid["type"]) 82 th = self._get_th(pool) 83 pool_n = pool["pool"] 84 return (pool_n, th) 85 if isinstance(objid, PortPool): 86 pool_n = objid["pool"] 87 pool = self._pools[pool_n] 88 assert pool["pool"] == pool_n 89 th = self._get_th(pool) 90 return (th,) 91 92 93class RecordValuePickerException(Exception): 94 pass 95 96 97class RecordValuePicker: 98 """ 99 Class for storing shared buffer configuration. Can handle 2 different 100 objects, pool and tcbind. Provide an interface to get the stored values per 101 object type. 102 """ 103 def __init__(self, objlist): 104 self._recs = [] 105 for item in objlist: 106 self._recs.append({"objid": item, "value": item.var_tuple()}) 107 108 def get_value(self, objid): 109 if isinstance(objid, Pool) and objid["pool"] in [4, 8, 9, 10]: 110 # The threshold type of pools 4, 8, 9 and 10 cannot be changed 111 raise SkipTest() 112 if isinstance(objid, TcBind) and objid["tc"] >= 8: 113 # Multicast TCs cannot be changed 114 raise SkipTest() 115 for rec in self._recs: 116 if rec["objid"].weak_eq(objid): 117 return rec["value"] 118 raise RecordValuePickerException() 119 120 121def run_cmd(cmd, json=False): 122 out = subprocess.check_output(cmd, shell=True) 123 if json: 124 return j.loads(out) 125 return out 126 127 128def run_json_cmd(cmd): 129 return run_cmd(cmd, json=True) 130 131 132def log_test(test_name, err_msg=None): 133 if err_msg: 134 print("\t%s" % err_msg) 135 print("TEST: %-80s [FAIL]" % test_name) 136 else: 137 print("TEST: %-80s [ OK ]" % test_name) 138 139 140class CommonItem(dict): 141 varitems = [] 142 143 def var_tuple(self): 144 ret = [] 145 self.varitems.sort() 146 for key in self.varitems: 147 ret.append(self[key]) 148 return tuple(ret) 149 150 def weak_eq(self, other): 151 for key in self: 152 if key in self.varitems: 153 continue 154 if self[key] != other[key]: 155 return False 156 return True 157 158 159class CommonList(list): 160 def get_by(self, by_obj): 161 for item in self: 162 if item.weak_eq(by_obj): 163 return item 164 return None 165 166 def del_by(self, by_obj): 167 for item in self: 168 if item.weak_eq(by_obj): 169 self.remove(item) 170 171 172class Pool(CommonItem): 173 varitems = ["size", "thtype"] 174 175 def dl_set(self, dlname, size, thtype): 176 run_cmd("devlink sb pool set {} sb {} pool {} size {} thtype {}".format(dlname, self["sb"], 177 self["pool"], 178 size, thtype)) 179 180 181class PoolList(CommonList): 182 pass 183 184 185def get_pools(dlname, direction=None): 186 d = run_json_cmd("devlink sb pool show -j") 187 pools = PoolList() 188 for pooldict in d["pool"][dlname]: 189 if not direction or direction == pooldict["type"]: 190 pools.append(Pool(pooldict)) 191 return pools 192 193 194def do_check_pools(dlname, pools, vp): 195 for pool in pools: 196 pre_pools = get_pools(dlname) 197 try: 198 (size, thtype) = vp.get_value(pool) 199 except SkipTest: 200 continue 201 pool.dl_set(dlname, size, thtype) 202 post_pools = get_pools(dlname) 203 pool = post_pools.get_by(pool) 204 205 err_msg = None 206 if pool["size"] != size: 207 err_msg = "Incorrect pool size (got {}, expected {})".format(pool["size"], size) 208 if pool["thtype"] != thtype: 209 err_msg = "Incorrect pool threshold type (got {}, expected {})".format(pool["thtype"], thtype) 210 211 pre_pools.del_by(pool) 212 post_pools.del_by(pool) 213 if pre_pools != post_pools: 214 err_msg = "Other pool setup changed as well" 215 log_test("pool {} of sb {} set verification".format(pool["pool"], 216 pool["sb"]), err_msg) 217 218 219def check_pools(dlname, pools): 220 # Save defaults 221 record_vp = RecordValuePicker(pools) 222 223 # For each pool, set random size and static threshold type 224 do_check_pools(dlname, pools, RandomValuePicker(pools)) 225 226 # Restore defaults 227 do_check_pools(dlname, pools, record_vp) 228 229 230class TcBind(CommonItem): 231 varitems = ["pool", "threshold"] 232 233 def __init__(self, port, d): 234 super(TcBind, self).__init__(d) 235 self["dlportname"] = port.name 236 237 def dl_set(self, pool, th): 238 run_cmd("devlink sb tc bind set {} sb {} tc {} type {} pool {} th {}".format(self["dlportname"], 239 self["sb"], 240 self["tc"], 241 self["type"], 242 pool, th)) 243 244 245class TcBindList(CommonList): 246 pass 247 248 249def get_tcbinds(ports, verify_existence=False): 250 d = run_json_cmd("devlink sb tc bind show -j -n") 251 tcbinds = TcBindList() 252 for port in ports: 253 err_msg = None 254 if port.name not in d["tc_bind"] or len(d["tc_bind"][port.name]) == 0: 255 err_msg = "No tc bind for port" 256 else: 257 for tcbinddict in d["tc_bind"][port.name]: 258 tcbinds.append(TcBind(port, tcbinddict)) 259 if verify_existence: 260 log_test("tc bind existence for port {} verification".format(port.name), err_msg) 261 return tcbinds 262 263 264def do_check_tcbind(ports, tcbinds, vp): 265 for tcbind in tcbinds: 266 pre_tcbinds = get_tcbinds(ports) 267 try: 268 (pool, th) = vp.get_value(tcbind) 269 except SkipTest: 270 continue 271 tcbind.dl_set(pool, th) 272 post_tcbinds = get_tcbinds(ports) 273 tcbind = post_tcbinds.get_by(tcbind) 274 275 err_msg = None 276 if tcbind["pool"] != pool: 277 err_msg = "Incorrect pool (got {}, expected {})".format(tcbind["pool"], pool) 278 if tcbind["threshold"] != th: 279 err_msg = "Incorrect threshold (got {}, expected {})".format(tcbind["threshold"], th) 280 281 pre_tcbinds.del_by(tcbind) 282 post_tcbinds.del_by(tcbind) 283 if pre_tcbinds != post_tcbinds: 284 err_msg = "Other tc bind setup changed as well" 285 log_test("tc bind {}-{} of sb {} set verification".format(tcbind["dlportname"], 286 tcbind["tc"], 287 tcbind["sb"]), err_msg) 288 289 290def check_tcbind(dlname, ports, pools): 291 tcbinds = get_tcbinds(ports, verify_existence=True) 292 293 # Save defaults 294 record_vp = RecordValuePicker(tcbinds) 295 296 # Bind each port and unicast TC (TCs < 8) to a random pool and a random 297 # threshold 298 do_check_tcbind(ports, tcbinds, RandomValuePicker(pools)) 299 300 # Restore defaults 301 do_check_tcbind(ports, tcbinds, record_vp) 302 303 304class PortPool(CommonItem): 305 varitems = ["threshold"] 306 307 def __init__(self, port, d): 308 super(PortPool, self).__init__(d) 309 self["dlportname"] = port.name 310 311 def dl_set(self, th): 312 run_cmd("devlink sb port pool set {} sb {} pool {} th {}".format(self["dlportname"], 313 self["sb"], 314 self["pool"], th)) 315 316 317class PortPoolList(CommonList): 318 pass 319 320 321def get_portpools(ports, verify_existence=False): 322 d = run_json_cmd("devlink sb port pool -j -n") 323 portpools = PortPoolList() 324 for port in ports: 325 err_msg = None 326 if port.name not in d["port_pool"] or len(d["port_pool"][port.name]) == 0: 327 err_msg = "No port pool for port" 328 else: 329 for portpooldict in d["port_pool"][port.name]: 330 portpools.append(PortPool(port, portpooldict)) 331 if verify_existence: 332 log_test("port pool existence for port {} verification".format(port.name), err_msg) 333 return portpools 334 335 336def do_check_portpool(ports, portpools, vp): 337 for portpool in portpools: 338 pre_portpools = get_portpools(ports) 339 (th,) = vp.get_value(portpool) 340 portpool.dl_set(th) 341 post_portpools = get_portpools(ports) 342 portpool = post_portpools.get_by(portpool) 343 344 err_msg = None 345 if portpool["threshold"] != th: 346 err_msg = "Incorrect threshold (got {}, expected {})".format(portpool["threshold"], th) 347 348 pre_portpools.del_by(portpool) 349 post_portpools.del_by(portpool) 350 if pre_portpools != post_portpools: 351 err_msg = "Other port pool setup changed as well" 352 log_test("port pool {}-{} of sb {} set verification".format(portpool["dlportname"], 353 portpool["pool"], 354 portpool["sb"]), err_msg) 355 356 357def check_portpool(dlname, ports, pools): 358 portpools = get_portpools(ports, verify_existence=True) 359 360 # Save defaults 361 record_vp = RecordValuePicker(portpools) 362 363 # For each port pool, set a random threshold 364 do_check_portpool(ports, portpools, RandomValuePicker(pools)) 365 366 # Restore defaults 367 do_check_portpool(ports, portpools, record_vp) 368 369 370class Port: 371 def __init__(self, name): 372 self.name = name 373 374 375class PortList(list): 376 pass 377 378 379def get_ports(dlname): 380 d = run_json_cmd("devlink port show -j") 381 ports = PortList() 382 for name in d["port"]: 383 if name.find(dlname) == 0 and d["port"][name]["flavour"] == "physical": 384 ports.append(Port(name)) 385 return ports 386 387 388def get_device(): 389 devices_info = run_json_cmd("devlink -j dev info")["info"] 390 for d in devices_info: 391 if "mlxsw_spectrum" in devices_info[d]["driver"]: 392 return d 393 return None 394 395 396class UnavailableDevlinkNameException(Exception): 397 pass 398 399 400def test_sb_configuration(): 401 # Use static seed 402 random.seed(0) 403 404 dlname = get_device() 405 if not dlname: 406 raise UnavailableDevlinkNameException() 407 408 ports = get_ports(dlname) 409 pools = get_pools(dlname) 410 411 check_pools(dlname, pools) 412 check_tcbind(dlname, ports, pools) 413 check_portpool(dlname, ports, pools) 414 415 416test_sb_configuration() 417