Skip to content

Commit a6c4456

Browse files
authored
[show] Add support for SONiC Gearbox Manager via new gearboxutil utility (#931)
* 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: [email protected]
1 parent 1ddd3f2 commit a6c4456

File tree

6 files changed

+394
-1
lines changed

6 files changed

+394
-1
lines changed

doc/Command-Reference.md

+52
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
* [ECN](#ecn)
4444
* [ECN show commands](#ecn-show-commands)
4545
* [ECN config commands](#ecn-config-commands)
46+
* [Gearbox](#gearbox)
47+
* [Gearbox show commands](#gearbox-show-commands)
4648
* [Interfaces](#interfaces)
4749
* [Interface Show Commands](#interface-show-commands)
4850
* [Interface Config Commands](#interface-config-commands)
@@ -2257,6 +2259,56 @@ The list of the WRED profile fields that are configurable is listed in the below
22572259
22582260
Go Back To [Beginning of the document](#) or [Beginning of this section](#ecn)
22592261
2262+
## Gearbox
2263+
2264+
This section explains all the Gearbox PHY show commands that are supported in SONiC.
2265+
2266+
### Gearbox show commands
2267+
This sub-section contains the show commands that are supported for gearbox phy.
2268+
2269+
**show gearbox interfaces status**
2270+
2271+
This command displays information about the gearbox phy interface lanes, speeds and status. Data is displayed for both MAC side and line side of the gearbox phy
2272+
2273+
- Usage:
2274+
```
2275+
show gearbox interfaces status
2276+
```
2277+
2278+
- Example:
2279+
2280+
```
2281+
home/admin# show gearbox interfaces status
2282+
PHY Id Interface MAC Lanes MAC Lane Speed PHY Lanes PHY Lane Speed Line Lanes Line Lane Speed Oper Admin
2283+
-------- ----------- ----------- ---------------- ----------- ---------------- ------------ ----------------- ------ -------
2284+
1 Ethernet0 25,26,27,28 10G 200,201 20G 206 40G up up
2285+
1 Ethernet4 29,30,31,32 10G 202,203 20G 207 40G up up
2286+
1 Ethernet8 33,34,35,36 10G 204,205 20G 208 40G up up
2287+
2288+
```
2289+
2290+
**show gearbox phys status**
2291+
2292+
This command displays basic information about the gearbox phys configured on the switch.
2293+
2294+
- Usage:
2295+
```
2296+
show gearbox phys status
2297+
```
2298+
2299+
- Example:
2300+
2301+
```
2302+
/home/admin# show gearbox phys status
2303+
PHY Id Name Firmware
2304+
-------- ------- ----------
2305+
1 sesto-1 v0.1
2306+
2307+
```
2308+
2309+
Go Back To [Beginning of the document](#) or [Beginning of this section](#gearbox)
2310+
2311+
22602312
## Update Device Hostname Configuration Commands
22612313
22622314
This sub-section of commands is used to change device hostname without traffic being impacted.

scripts/gearboxutil

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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
18+
client = mock_tables.dbconnector.redis.StrictRedis()
19+
if client.keys() is None:
20+
raise Exception("Invalid mock_table keys")
21+
except KeyError:
22+
pass
23+
24+
# ========================== Common gearbox-utils logic ==========================
25+
26+
GEARBOX_TABLE_PHY_PREFIX = "_GEARBOX_TABLE:phy:{}"
27+
GEARBOX_TABLE_INTERFACE_PREFIX = "_GEARBOX_TABLE:interface:{}"
28+
GEARBOX_TABLE_PORT_PREFIX = "_GEARBOX_TABLE:phy:{}:ports:{}"
29+
30+
PORT_TABLE_ETHERNET_PREFIX = "PORT_TABLE:{}"
31+
32+
PHY_NAME = "name"
33+
PHY_ID = "phy_id"
34+
PHY_FIRMWARE_MAJOR_VERSION = "firmware_major_version"
35+
PHY_LINE_LANES = "line_lanes"
36+
PHY_SYSTEM_LANES = "system_lanes"
37+
38+
PORT_OPER_STATUS = "oper_status"
39+
PORT_ADMIN_STATUS = "admin_status"
40+
PORT_SYSTEM_SPEED = "system_speed"
41+
PORT_LINE_SPEED = "line_speed"
42+
43+
INTF_NAME = "name"
44+
INTF_LANES = "lanes"
45+
INTF_SPEED = "speed"
46+
47+
def get_appl_key_attr(db, key, attr, lane_count=1):
48+
"""
49+
Get APPL_DB key attribute
50+
"""
51+
52+
val = db.get(db.APPL_DB, key, attr)
53+
if val is None:
54+
return "N/A"
55+
56+
if "speed" in attr:
57+
if val == "0":
58+
return "N/A"
59+
60+
speed = int(val[:-3])
61+
62+
if (speed % lane_count == 0):
63+
speed = speed // lane_count
64+
else:
65+
return "N/A"
66+
67+
val = '{}G'.format(str(speed))
68+
69+
return val
70+
71+
def db_connect_appl():
72+
appl_db = swsssdk.SonicV2Connector(host='127.0.0.1')
73+
if appl_db is None:
74+
return None
75+
appl_db.connect(appl_db.APPL_DB)
76+
return appl_db
77+
78+
def db_connect_state():
79+
"""
80+
Connect to REDIS STATE DB and get optics info
81+
"""
82+
state_db = swsssdk.SonicV2Connector(host='127.0.0.1')
83+
if state_db is None:
84+
return None
85+
state_db.connect(state_db.STATE_DB, False) # Make one attempt only
86+
return state_db
87+
88+
def appl_db_keys_get(appl_db):
89+
"""
90+
Get APPL_DB Keys
91+
"""
92+
return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_PHY_PREFIX.format("*"))
93+
94+
def appl_db_interface_keys_get(appl_db):
95+
"""
96+
Get APPL_DB Keys
97+
"""
98+
return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_INTERFACE_PREFIX.format("*"))
99+
100+
# ========================== phy-status logic ==========================
101+
102+
phy_header_status = ['PHY Id', 'Name', 'Firmware']
103+
104+
class PhyStatus(object):
105+
106+
def display_phy_status(self, appl_db_keys):
107+
"""
108+
Generate phy status output
109+
"""
110+
table = []
111+
key = []
112+
113+
for key in appl_db_keys:
114+
if 'lanes' in key or 'ports' in key:
115+
continue
116+
list_items = key.split(':')
117+
phy_id = list_items[2]
118+
data_row = (
119+
phy_id,
120+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_NAME),
121+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_FIRMWARE_MAJOR_VERSION))
122+
table.append(data_row)
123+
124+
# Sorting and tabulating the result table.
125+
sorted_table = natsorted(table)
126+
print tabulate(sorted_table, phy_header_status, tablefmt="simple", stralign='right')
127+
128+
def __init__(self):
129+
self.appl_db = db_connect_appl()
130+
if self.appl_db is None:
131+
return
132+
133+
appl_db_keys = appl_db_keys_get(self.appl_db)
134+
if appl_db_keys is None:
135+
return
136+
137+
self.display_phy_status(appl_db_keys)
138+
139+
# ========================== interface-status logic ==========================
140+
141+
intf_header_status = ['PHY Id', 'Interface', 'MAC Lanes', 'MAC Lane Speed', 'PHY Lanes', 'PHY Lane Speed', 'Line Lanes', 'Line Lane Speed', 'Oper', 'Admin']
142+
143+
class InterfaceStatus(object):
144+
145+
def display_intf_status(self, appl_db_keys):
146+
"""
147+
Generate phy status output
148+
"""
149+
table = []
150+
key = []
151+
152+
for key in appl_db_keys:
153+
list_items = key.split(':')
154+
index = list_items[2]
155+
156+
name = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), INTF_NAME),
157+
name = name[0]
158+
159+
mac_lanes = get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), INTF_LANES)
160+
lanes = mac_lanes.split(',')
161+
lane_count = 0
162+
for lane in lanes:
163+
lane_count += 1
164+
165+
phy_id = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_ID)
166+
167+
data_row = (
168+
phy_id,
169+
name,
170+
mac_lanes,
171+
get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), INTF_SPEED, lane_count),
172+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_SYSTEM_LANES),
173+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PORT_PREFIX.format(phy_id, index), PORT_SYSTEM_SPEED),
174+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_LINE_LANES),
175+
get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PORT_PREFIX.format(phy_id, index), PORT_LINE_SPEED),
176+
get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), PORT_OPER_STATUS),
177+
get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), PORT_ADMIN_STATUS))
178+
179+
table.append(data_row)
180+
181+
# Sorting and tabulating the result table.
182+
sorted_table = natsorted(table)
183+
print tabulate(sorted_table, intf_header_status, tablefmt="simple", stralign='right')
184+
185+
def __init__(self):
186+
self.appl_db = db_connect_appl()
187+
if self.appl_db is None:
188+
return
189+
190+
appl_db_keys = appl_db_interface_keys_get(self.appl_db)
191+
if appl_db_keys is None:
192+
return
193+
194+
self.display_intf_status(appl_db_keys)
195+
196+
def main(args):
197+
"""
198+
phy status
199+
interfaces status
200+
interfaces counters
201+
"""
202+
203+
if len(args) == 0:
204+
print "No valid arguments provided"
205+
return
206+
207+
cmd1 = args[0]
208+
if cmd1 != "phys" and cmd1 != "interfaces":
209+
print "No valid command provided"
210+
return
211+
212+
cmd2 = args[1]
213+
if cmd2 != "status" and cmd2 != "counters":
214+
print "No valid command provided"
215+
return
216+
217+
if cmd1 == "phys" and cmd2 == "status":
218+
PhyStatus()
219+
elif cmd1 == "interfaces" and cmd2 == "status":
220+
InterfaceStatus()
221+
222+
sys.exit(0)
223+
224+
if __name__ == "__main__":
225+
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
@@ -2842,6 +2842,43 @@ def pool(verbose):
28422842
cmd = "sudo natconfig -p"
28432843
run_command(cmd, display_cmd=verbose)
28442844

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

28462883
# 'bindings' subcommand ("show nat config bindings")
28472884
@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)