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