Skip to content

Commit 9196818

Browse files
committed
utilities: Changes to support SONiC Gearbox Manager
* add and modify command line utilities to support gearbox phy * added build time mock unit tests HLD is located at https://github.com/Azure/SONiC/blob/b817a12fd89520d3fd26bbc5897487928e7f6de7/doc/gearbox/gearbox_mgr_design.md Signed-off-by: syd.logan@broadcom.com
1 parent f7b7fcb commit 9196818

File tree

8 files changed

+409
-1
lines changed

8 files changed

+409
-1
lines changed

scripts/gearboxutil

+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
#! /usr/bin/python
2+
3+
import swsssdk
4+
import sys
5+
from tabulate import tabulate
6+
from natsort import natsorted
7+
8+
import os
9+
10+
# mock the redis for unit test purposes #
11+
try:
12+
if os.environ["UTILITIES_UNIT_TESTING"] == "1":
13+
modules_path = os.path.join(os.path.dirname(__file__), "..")
14+
tests_path = os.path.join(modules_path, "sonic-utilities-tests")
15+
sys.path.insert(0, modules_path)
16+
sys.path.insert(0, tests_path)
17+
import mock_tables.dbconnector # required by sonic-utilities-tests
18+
except KeyError:
19+
pass
20+
21+
# ========================== Common gearbox-utils logic ==========================
22+
23+
GEARBOX_TABLE_PHY_PREFIX = "_GEARBOX_TABLE:phy:{}"
24+
GEARBOX_TABLE_INTERFACE_PREFIX = "_GEARBOX_TABLE:interface:{}"
25+
GEARBOX_TABLE_PORT_PREFIX = "_GEARBOX_TABLE:phy:{}:ports:{}"
26+
27+
PORT_TABLE_ETHERNET_PREFIX = "PORT_TABLE:{}"
28+
29+
ASIC_STATE_TABLE_PORT_PREFIX = "ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:{}"
30+
ASIC_STATE_TABLE_SWITCH_PREFIX = "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:{}"
31+
ASIC_GEARBOX_TABLE_SWITCH_PREFIX = "ASIC_GEARBOX|MISC_SAI_SWITCH_ATTR:{}"
32+
33+
PHY_NAME = "name"
34+
PHY_ID = "phy_id"
35+
PHY_OID = "phy_oid"
36+
PHY_FIRMWARE_MAJOR_VERSION = "SAI_SWITCH_ATTR_FIRMWARE_MAJOR_VERSION"
37+
PHY_LINE_LANES = "line_lanes"
38+
PHY_SYSTEM_LANES = "system_lanes"
39+
40+
PORT_OPER_STATUS = "oper_status"
41+
PORT_ADMIN_STATUS = "admin_status"
42+
PORT_SYSTEM_SPEED = "system_speed"
43+
PORT_LINE_SPEED = "line_speed"
44+
45+
INTF_NAME = "name"
46+
INTF_LANES = "lanes"
47+
INTF_SPEED = "speed"
48+
49+
def get_appl_key_attr(db, key, attr, lane_count=1):
50+
"""
51+
Get APPL_DB key attribute
52+
"""
53+
54+
val = db.get(db.APPL_DB, key, attr)
55+
if val is None:
56+
return "N/A"
57+
58+
if "speed" in attr:
59+
60+
if val == "0":
61+
return "N/A"
62+
63+
speed = int(val[:-3])
64+
65+
if (speed % lane_count == 0):
66+
speed = speed // lane_count
67+
else:
68+
return "N/A"
69+
70+
val = '{}G'.format(str(speed))
71+
72+
return val
73+
74+
def get_asic2_key_attr(asic_db, phy_oid, attr):
75+
"""
76+
Get the phy attribute
77+
"""
78+
79+
full_table_id = ASIC_GEARBOX_TABLE_SWITCH_PREFIX.format(phy_oid)
80+
# Replace needed for mock_table unit testing
81+
full_table_id = full_table_id.replace("ATTR:0", "ATTR:oid:0")
82+
val = asic_db.get(asic_db.ASIC_DB2, full_table_id, attr)
83+
if val is None:
84+
return "N/A"
85+
86+
return val
87+
88+
def db_connect_asic2():
89+
asic_db = swsssdk.SonicV2Connector(host='127.0.0.1')
90+
if asic_db is None:
91+
return None
92+
asic_db.connect(asic_db.ASIC_DB2)
93+
return asic_db
94+
95+
def db_connect_appl():
96+
appl_db = swsssdk.SonicV2Connector(host='127.0.0.1')
97+
if appl_db is None:
98+
return None
99+
appl_db.connect(appl_db.APPL_DB)
100+
return appl_db
101+
102+
def db_connect_state():
103+
"""
104+
Connect to REDIS STATE DB and get optics info
105+
"""
106+
state_db = swsssdk.SonicV2Connector(host='127.0.0.1')
107+
if state_db is None:
108+
return None
109+
state_db.connect(state_db.STATE_DB, False) # Make one attempt only
110+
return state_db
111+
112+
def appl_db_keys_get(appl_db):
113+
"""
114+
Get APPL_DB Keys
115+
"""
116+
return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_PHY_PREFIX.format("*"))
117+
118+
def appl_db_interface_keys_get(appl_db):
119+
"""
120+
Get ASIC_DB Keys
121+
"""
122+
return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_INTERFACE_PREFIX.format("*"))
123+
124+
def asic2_db_keys_get(asic_db):
125+
"""
126+
Get ASIC_DB Keys
127+
"""
128+
return asic_db.keys(asic_db.ASIC_DB2, ASIC_GEARBOX_TABLE_SWITCH_PREFIX.format("*"))
129+
130+
# ========================== phy-status logic ==========================
131+
132+
phy_header_status = ['PHY Id', 'Name', 'Firmware']
133+
134+
class PhyStatus(object):
135+
136+
def display_phy_status(self, appl_db_keys, asic_db_keys):
137+
"""
138+
Generate phy status output
139+
"""
140+
table = []
141+
key = []
142+
143+
for key in appl_db_keys:
144+
if 'lanes' in key or 'ports' in key:
145+
continue
146+
list_items = key.split(':')
147+
phy_id = list_items[2]
148+
phy_oid = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_OID)
149+
data_row = (
150+
phy_id,
151+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_NAME),
152+
get_asic2_key_attr(self.asic_db, phy_oid, PHY_FIRMWARE_MAJOR_VERSION))
153+
154+
table.append(data_row)
155+
156+
# Sorting and tabulating the result table.
157+
sorted_table = natsorted(table)
158+
print tabulate(sorted_table, phy_header_status, tablefmt="simple", stralign='right')
159+
160+
def __init__(self):
161+
162+
self.asic_db = db_connect_asic2()
163+
self.appl_db = db_connect_appl()
164+
165+
if self.asic_db is None:
166+
return
167+
if self.appl_db is None:
168+
return
169+
170+
appl_db_keys = appl_db_keys_get(self.appl_db)
171+
if appl_db_keys is None:
172+
return
173+
174+
asic_db_keys = asic2_db_keys_get(self.asic_db)
175+
if asic_db_keys is None:
176+
return
177+
178+
self.display_phy_status(appl_db_keys, asic_db_keys)
179+
180+
# ========================== interface-status logic ==========================
181+
182+
intf_header_status = ['PHY Id', 'Interface', 'MAC Lanes', 'MAC Lane Speed', 'PHY Lanes', 'PHY Lane Speed', 'Line Lanes', 'Line Lane Speed', 'Oper', 'Admin']
183+
184+
class InterfaceStatus(object):
185+
186+
def display_intf_status(self, appl_db_keys, asic_db_keys):
187+
"""
188+
Generate phy status output
189+
"""
190+
table = []
191+
key = []
192+
193+
for key in appl_db_keys:
194+
list_items = key.split(':')
195+
index = list_items[2]
196+
197+
name = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), INTF_NAME),
198+
name = name[0]
199+
200+
mac_lanes = get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), INTF_LANES)
201+
lanes = mac_lanes.split(',')
202+
lane_count = 0
203+
for lane in lanes:
204+
lane_count += 1
205+
206+
phy_id = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_ID)
207+
208+
data_row = (
209+
phy_id,
210+
name,
211+
mac_lanes,
212+
get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), INTF_SPEED, lane_count),
213+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_SYSTEM_LANES),
214+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PORT_PREFIX.format(phy_id, index), PORT_SYSTEM_SPEED),
215+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_LINE_LANES),
216+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PORT_PREFIX.format(phy_id, index), PORT_LINE_SPEED),
217+
get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), PORT_OPER_STATUS),
218+
get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), PORT_ADMIN_STATUS))
219+
220+
table.append(data_row)
221+
222+
# Sorting and tabulating the result table.
223+
sorted_table = natsorted(table)
224+
print tabulate(sorted_table, intf_header_status, tablefmt="simple", stralign='right')
225+
226+
def __init__(self):
227+
228+
self.asic_db = db_connect_asic2()
229+
self.appl_db = db_connect_appl()
230+
231+
if self.asic_db is None:
232+
return
233+
if self.appl_db is None:
234+
return
235+
236+
appl_db_keys = appl_db_interface_keys_get(self.appl_db)
237+
if appl_db_keys is None:
238+
return
239+
240+
asic_db_keys = asic2_db_keys_get(self.asic_db)
241+
if asic_db_keys is None:
242+
return
243+
244+
self.display_intf_status(appl_db_keys, asic_db_keys)
245+
246+
def main(args):
247+
"""
248+
phy status
249+
interfaces status
250+
interfaces counters
251+
"""
252+
253+
if len(args) == 0:
254+
print "No valid arguments provided"
255+
return
256+
257+
cmd1 = args[0]
258+
if cmd1 != "phys" and cmd1 != "interfaces":
259+
print "No valid command provided"
260+
return
261+
262+
cmd2 = args[1]
263+
if cmd2 != "status" and cmd2 != "counters":
264+
print "No valid command provided"
265+
return
266+
267+
if cmd1 == "phys" and cmd2 == "status":
268+
PhyStatus()
269+
elif cmd1 == "interfaces" and cmd2 == "status":
270+
InterfaceStatus()
271+
272+
sys.exit(0)
273+
274+
if __name__ == "__main__":
275+
main(sys.argv[1:])

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
'scripts/fdbclear',
7777
'scripts/fdbshow',
7878
'scripts/filter_fdb_entries.py',
79+
'scripts/gearboxutil',
7980
'scripts/generate_dump',
8081
'scripts/intfutil',
8182
'scripts/intfstat',

show/main.py

+37
Original file line numberDiff line numberDiff line change
@@ -2839,6 +2839,43 @@ def pool(verbose):
28392839
cmd = "sudo natconfig -p"
28402840
run_command(cmd, display_cmd=verbose)
28412841

2842+
# Define GEARBOX commands only if GEARBOX is configured
2843+
app_db = SonicV2Connector(host='127.0.0.1')
2844+
app_db.connect(app_db.APPL_DB)
2845+
if app_db.keys(app_db.APPL_DB, '_GEARBOX_TABLE:phy:*'):
2846+
2847+
@cli.group(cls=AliasedGroup)
2848+
def gearbox():
2849+
"""Show gearbox info"""
2850+
pass
2851+
2852+
# 'phys' subcommand ("show gearbox phys")
2853+
@gearbox.group(cls=AliasedGroup)
2854+
def phys():
2855+
"""Show external PHY information"""
2856+
pass
2857+
2858+
# 'status' subcommand ("show gearbox phys status")
2859+
@phys.command()
2860+
@click.pass_context
2861+
def status(ctx):
2862+
"""Show gearbox phys status"""
2863+
run_command("gearboxutil phys status")
2864+
return
2865+
2866+
# 'interfaces' subcommand ("show gearbox interfaces")
2867+
@gearbox.group(cls=AliasedGroup)
2868+
def interfaces():
2869+
"""Show gearbox interfaces information"""
2870+
pass
2871+
2872+
# 'status' subcommand ("show gearbox interfaces status")
2873+
@interfaces.command()
2874+
@click.pass_context
2875+
def status(ctx):
2876+
"""Show gearbox interfaces status"""
2877+
run_command("gearboxutil interfaces status")
2878+
return
28422879

28432880
# 'bindings' subcommand ("show nat config bindings")
28442881
@config.command()

sonic-utilities-tests/gearbox_test.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import sys
2+
import os
3+
from click.testing import CliRunner
4+
from unittest import TestCase
5+
6+
test_path = os.path.dirname(os.path.abspath(__file__))
7+
modules_path = os.path.dirname(test_path)
8+
scripts_path = os.path.join(modules_path, "scripts")
9+
sys.path.insert(0, test_path)
10+
sys.path.insert(0, modules_path)
11+
12+
import mock_tables.dbconnector # required by sonic-utilities-tests
13+
14+
import show.main as show
15+
16+
class TestGearbox(TestCase):
17+
@classmethod
18+
def setup_class(cls):
19+
print("SETUP")
20+
os.environ["PATH"] += os.pathsep + scripts_path
21+
os.environ["UTILITIES_UNIT_TESTING"] = "1"
22+
23+
def setUp(self):
24+
self.runner = CliRunner()
25+
26+
def test_gearbox_phys_status_validation(self):
27+
result = self.runner.invoke(show.cli.commands["gearbox"].commands["phys"].commands["status"], [])
28+
print >> sys.stderr, result.output
29+
expected_output = (
30+
"PHY Id Name Firmware\n"
31+
"-------- ------- ----------\n"
32+
" 1 sesto-1 v0.2\n"
33+
" 2 sesto-2 v0.3"
34+
)
35+
self.assertEqual(result.output.strip(), expected_output)
36+
37+
def test_gearbox_interfaces_status_validation(self):
38+
result = self.runner.invoke(show.cli.commands["gearbox"].commands["interfaces"].commands["status"], [])
39+
print >> sys.stderr, result.output
40+
expected_output = (
41+
"PHY Id Interface MAC Lanes MAC Lane Speed PHY Lanes PHY Lane Speed Line Lanes Line Lane Speed Oper Admin\n"
42+
"-------- ----------- --------------- ---------------- --------------- ---------------- ------------ ----------------- ------ -------\n"
43+
" 1 Ethernet200 200,201,202,203 25G 300,301,302,303 25G 304,305 50G down up"
44+
)
45+
self.assertEqual(result.output.strip(), expected_output)
46+
47+
@classmethod
48+
def teardown_class(cls):
49+
print("TEARDOWN")
50+
os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1])
51+
os.environ["UTILITIES_UNIT_TESTING"] = "0"

0 commit comments

Comments
 (0)