| #!/usr/bin/env python | |
| # SPDX-License-Identifier: GPL-2.0 | |
| import subprocess | |
| import json as j | |
| import random | |
| class SkipTest(Exception): | |
| pass | |
| class RandomValuePicker: | |
| """ | |
| Class for storing shared buffer configuration. Can handle 3 different | |
| objects, pool, tcbind and portpool. Provide an interface to get random | |
| values for a specific object type as the follow: | |
| 1. Pool: | |
| - random size | |
| 2. TcBind: | |
| - random pool number | |
| - random threshold | |
| 3. PortPool: | |
| - random threshold | |
| """ | |
| def __init__(self, pools): | |
| self._pools = [] | |
| for pool in pools: | |
| self._pools.append(pool) | |
| def _cell_size(self): | |
| return self._pools[0]["cell_size"] | |
| def _get_static_size(self, th): | |
| # For threshold of 16, this works out to be about 12MB on Spectrum-1, | |
| # and about 17MB on Spectrum-2. | |
| return th * 8000 * self._cell_size() | |
| def _get_size(self): | |
| return self._get_static_size(16) | |
| def _get_thtype(self): | |
| return "static" | |
| def _get_th(self, pool): | |
| # Threshold value could be any integer between 3 to 16 | |
| th = random.randint(3, 16) | |
| if pool["thtype"] == "dynamic": | |
| return th | |
| else: | |
| return self._get_static_size(th) | |
| def _get_pool(self, direction): | |
| ing_pools = [] | |
| egr_pools = [] | |
| for pool in self._pools: | |
| if pool["type"] == "ingress": | |
| ing_pools.append(pool) | |
| else: | |
| egr_pools.append(pool) | |
| if direction == "ingress": | |
| arr = ing_pools | |
| else: | |
| arr = egr_pools | |
| return arr[random.randint(0, len(arr) - 1)] | |
| def get_value(self, objid): | |
| if isinstance(objid, Pool): | |
| if objid["pool"] in [4, 8, 9, 10]: | |
| # The threshold type of pools 4, 8, 9 and 10 cannot be changed | |
| raise SkipTest() | |
| else: | |
| return (self._get_size(), self._get_thtype()) | |
| if isinstance(objid, TcBind): | |
| if objid["tc"] >= 8: | |
| # Multicast TCs cannot be changed | |
| raise SkipTest() | |
| else: | |
| pool = self._get_pool(objid["type"]) | |
| th = self._get_th(pool) | |
| pool_n = pool["pool"] | |
| return (pool_n, th) | |
| if isinstance(objid, PortPool): | |
| pool_n = objid["pool"] | |
| pool = self._pools[pool_n] | |
| assert pool["pool"] == pool_n | |
| th = self._get_th(pool) | |
| return (th,) | |
| class RecordValuePickerException(Exception): | |
| pass | |
| class RecordValuePicker: | |
| """ | |
| Class for storing shared buffer configuration. Can handle 2 different | |
| objects, pool and tcbind. Provide an interface to get the stored values per | |
| object type. | |
| """ | |
| def __init__(self, objlist): | |
| self._recs = [] | |
| for item in objlist: | |
| self._recs.append({"objid": item, "value": item.var_tuple()}) | |
| def get_value(self, objid): | |
| if isinstance(objid, Pool) and objid["pool"] in [4, 8, 9, 10]: | |
| # The threshold type of pools 4, 8, 9 and 10 cannot be changed | |
| raise SkipTest() | |
| if isinstance(objid, TcBind) and objid["tc"] >= 8: | |
| # Multicast TCs cannot be changed | |
| raise SkipTest() | |
| for rec in self._recs: | |
| if rec["objid"].weak_eq(objid): | |
| return rec["value"] | |
| raise RecordValuePickerException() | |
| def run_cmd(cmd, json=False): | |
| out = subprocess.check_output(cmd, shell=True) | |
| if json: | |
| return j.loads(out) | |
| return out | |
| def run_json_cmd(cmd): | |
| return run_cmd(cmd, json=True) | |
| def log_test(test_name, err_msg=None): | |
| if err_msg: | |
| print("\t%s" % err_msg) | |
| print("TEST: %-80s [FAIL]" % test_name) | |
| else: | |
| print("TEST: %-80s [ OK ]" % test_name) | |
| class CommonItem(dict): | |
| varitems = [] | |
| def var_tuple(self): | |
| ret = [] | |
| self.varitems.sort() | |
| for key in self.varitems: | |
| ret.append(self[key]) | |
| return tuple(ret) | |
| def weak_eq(self, other): | |
| for key in self: | |
| if key in self.varitems: | |
| continue | |
| if self[key] != other[key]: | |
| return False | |
| return True | |
| class CommonList(list): | |
| def get_by(self, by_obj): | |
| for item in self: | |
| if item.weak_eq(by_obj): | |
| return item | |
| return None | |
| def del_by(self, by_obj): | |
| for item in self: | |
| if item.weak_eq(by_obj): | |
| self.remove(item) | |
| class Pool(CommonItem): | |
| varitems = ["size", "thtype"] | |
| def dl_set(self, dlname, size, thtype): | |
| run_cmd("devlink sb pool set {} sb {} pool {} size {} thtype {}".format(dlname, self["sb"], | |
| self["pool"], | |
| size, thtype)) | |
| class PoolList(CommonList): | |
| pass | |
| def get_pools(dlname, direction=None): | |
| d = run_json_cmd("devlink sb pool show -j") | |
| pools = PoolList() | |
| for pooldict in d["pool"][dlname]: | |
| if not direction or direction == pooldict["type"]: | |
| pools.append(Pool(pooldict)) | |
| return pools | |
| def do_check_pools(dlname, pools, vp): | |
| for pool in pools: | |
| pre_pools = get_pools(dlname) | |
| try: | |
| (size, thtype) = vp.get_value(pool) | |
| except SkipTest: | |
| continue | |
| pool.dl_set(dlname, size, thtype) | |
| post_pools = get_pools(dlname) | |
| pool = post_pools.get_by(pool) | |
| err_msg = None | |
| if pool["size"] != size: | |
| err_msg = "Incorrect pool size (got {}, expected {})".format(pool["size"], size) | |
| if pool["thtype"] != thtype: | |
| err_msg = "Incorrect pool threshold type (got {}, expected {})".format(pool["thtype"], thtype) | |
| pre_pools.del_by(pool) | |
| post_pools.del_by(pool) | |
| if pre_pools != post_pools: | |
| err_msg = "Other pool setup changed as well" | |
| log_test("pool {} of sb {} set verification".format(pool["pool"], | |
| pool["sb"]), err_msg) | |
| def check_pools(dlname, pools): | |
| # Save defaults | |
| record_vp = RecordValuePicker(pools) | |
| # For each pool, set random size and static threshold type | |
| do_check_pools(dlname, pools, RandomValuePicker(pools)) | |
| # Restore defaults | |
| do_check_pools(dlname, pools, record_vp) | |
| class TcBind(CommonItem): | |
| varitems = ["pool", "threshold"] | |
| def __init__(self, port, d): | |
| super(TcBind, self).__init__(d) | |
| self["dlportname"] = port.name | |
| def dl_set(self, pool, th): | |
| run_cmd("devlink sb tc bind set {} sb {} tc {} type {} pool {} th {}".format(self["dlportname"], | |
| self["sb"], | |
| self["tc"], | |
| self["type"], | |
| pool, th)) | |
| class TcBindList(CommonList): | |
| pass | |
| def get_tcbinds(ports, verify_existence=False): | |
| d = run_json_cmd("devlink sb tc bind show -j -n") | |
| tcbinds = TcBindList() | |
| for port in ports: | |
| err_msg = None | |
| if port.name not in d["tc_bind"] or len(d["tc_bind"][port.name]) == 0: | |
| err_msg = "No tc bind for port" | |
| else: | |
| for tcbinddict in d["tc_bind"][port.name]: | |
| tcbinds.append(TcBind(port, tcbinddict)) | |
| if verify_existence: | |
| log_test("tc bind existence for port {} verification".format(port.name), err_msg) | |
| return tcbinds | |
| def do_check_tcbind(ports, tcbinds, vp): | |
| for tcbind in tcbinds: | |
| pre_tcbinds = get_tcbinds(ports) | |
| try: | |
| (pool, th) = vp.get_value(tcbind) | |
| except SkipTest: | |
| continue | |
| tcbind.dl_set(pool, th) | |
| post_tcbinds = get_tcbinds(ports) | |
| tcbind = post_tcbinds.get_by(tcbind) | |
| err_msg = None | |
| if tcbind["pool"] != pool: | |
| err_msg = "Incorrect pool (got {}, expected {})".format(tcbind["pool"], pool) | |
| if tcbind["threshold"] != th: | |
| err_msg = "Incorrect threshold (got {}, expected {})".format(tcbind["threshold"], th) | |
| pre_tcbinds.del_by(tcbind) | |
| post_tcbinds.del_by(tcbind) | |
| if pre_tcbinds != post_tcbinds: | |
| err_msg = "Other tc bind setup changed as well" | |
| log_test("tc bind {}-{} of sb {} set verification".format(tcbind["dlportname"], | |
| tcbind["tc"], | |
| tcbind["sb"]), err_msg) | |
| def check_tcbind(dlname, ports, pools): | |
| tcbinds = get_tcbinds(ports, verify_existence=True) | |
| # Save defaults | |
| record_vp = RecordValuePicker(tcbinds) | |
| # Bind each port and unicast TC (TCs < 8) to a random pool and a random | |
| # threshold | |
| do_check_tcbind(ports, tcbinds, RandomValuePicker(pools)) | |
| # Restore defaults | |
| do_check_tcbind(ports, tcbinds, record_vp) | |
| class PortPool(CommonItem): | |
| varitems = ["threshold"] | |
| def __init__(self, port, d): | |
| super(PortPool, self).__init__(d) | |
| self["dlportname"] = port.name | |
| def dl_set(self, th): | |
| run_cmd("devlink sb port pool set {} sb {} pool {} th {}".format(self["dlportname"], | |
| self["sb"], | |
| self["pool"], th)) | |
| class PortPoolList(CommonList): | |
| pass | |
| def get_portpools(ports, verify_existence=False): | |
| d = run_json_cmd("devlink sb port pool -j -n") | |
| portpools = PortPoolList() | |
| for port in ports: | |
| err_msg = None | |
| if port.name not in d["port_pool"] or len(d["port_pool"][port.name]) == 0: | |
| err_msg = "No port pool for port" | |
| else: | |
| for portpooldict in d["port_pool"][port.name]: | |
| portpools.append(PortPool(port, portpooldict)) | |
| if verify_existence: | |
| log_test("port pool existence for port {} verification".format(port.name), err_msg) | |
| return portpools | |
| def do_check_portpool(ports, portpools, vp): | |
| for portpool in portpools: | |
| pre_portpools = get_portpools(ports) | |
| (th,) = vp.get_value(portpool) | |
| portpool.dl_set(th) | |
| post_portpools = get_portpools(ports) | |
| portpool = post_portpools.get_by(portpool) | |
| err_msg = None | |
| if portpool["threshold"] != th: | |
| err_msg = "Incorrect threshold (got {}, expected {})".format(portpool["threshold"], th) | |
| pre_portpools.del_by(portpool) | |
| post_portpools.del_by(portpool) | |
| if pre_portpools != post_portpools: | |
| err_msg = "Other port pool setup changed as well" | |
| log_test("port pool {}-{} of sb {} set verification".format(portpool["dlportname"], | |
| portpool["pool"], | |
| portpool["sb"]), err_msg) | |
| def check_portpool(dlname, ports, pools): | |
| portpools = get_portpools(ports, verify_existence=True) | |
| # Save defaults | |
| record_vp = RecordValuePicker(portpools) | |
| # For each port pool, set a random threshold | |
| do_check_portpool(ports, portpools, RandomValuePicker(pools)) | |
| # Restore defaults | |
| do_check_portpool(ports, portpools, record_vp) | |
| class Port: | |
| def __init__(self, name): | |
| self.name = name | |
| class PortList(list): | |
| pass | |
| def get_ports(dlname): | |
| d = run_json_cmd("devlink port show -j") | |
| ports = PortList() | |
| for name in d["port"]: | |
| if name.find(dlname) == 0 and d["port"][name]["flavour"] == "physical": | |
| ports.append(Port(name)) | |
| return ports | |
| def get_device(): | |
| devices_info = run_json_cmd("devlink -j dev info")["info"] | |
| for d in devices_info: | |
| if "mlxsw_spectrum" in devices_info[d]["driver"]: | |
| return d | |
| return None | |
| class UnavailableDevlinkNameException(Exception): | |
| pass | |
| def test_sb_configuration(): | |
| # Use static seed | |
| random.seed(0) | |
| dlname = get_device() | |
| if not dlname: | |
| raise UnavailableDevlinkNameException() | |
| ports = get_ports(dlname) | |
| pools = get_pools(dlname) | |
| check_pools(dlname, pools) | |
| check_tcbind(dlname, ports, pools) | |
| check_portpool(dlname, ports, pools) | |
| test_sb_configuration() |