417 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/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()
 |