diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index e6bb9ba6dfea..9ee91a88cd87 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -43,6 +43,8 @@ * [ECN](#ecn) * [ECN show commands](#ecn-show-commands) * [ECN config commands](#ecn-config-commands) +* [Gearbox](#gearbox) + * [Gearbox show commands](#gearbox-show-commands) * [Interfaces](#interfaces) * [Interface Show Commands](#interface-show-commands) * [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 Go Back To [Beginning of the document](#) or [Beginning of this section](#ecn) +## Gearbox + +This section explains all the Gearbox PHY show commands that are supported in SONiC. + +### Gearbox show commands +This sub-section contains the show commands that are supported for gearbox phy. + +**show gearbox interfaces status** + +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 + +- Usage: + ``` + show gearbox interfaces status + ``` + +- Example: + +``` +home/admin# show gearbox interfaces status + PHY Id Interface MAC Lanes MAC Lane Speed PHY Lanes PHY Lane Speed Line Lanes Line Lane Speed Oper Admin +-------- ----------- ----------- ---------------- ----------- ---------------- ------------ ----------------- ------ ------- + 1 Ethernet0 25,26,27,28 10G 200,201 20G 206 40G up up + 1 Ethernet4 29,30,31,32 10G 202,203 20G 207 40G up up + 1 Ethernet8 33,34,35,36 10G 204,205 20G 208 40G up up + + ``` + +**show gearbox phys status** + +This command displays basic information about the gearbox phys configured on the switch. + +- Usage: + ``` + show gearbox phys status + ``` + +- Example: + +``` +/home/admin# show gearbox phys status + PHY Id Name Firmware +-------- ------- ---------- + 1 sesto-1 v0.1 + + ``` + +Go Back To [Beginning of the document](#) or [Beginning of this section](#gearbox) + + ## Update Device Hostname Configuration Commands This sub-section of commands is used to change device hostname without traffic being impacted. diff --git a/scripts/gearboxutil b/scripts/gearboxutil new file mode 100755 index 000000000000..b6b2818deecb --- /dev/null +++ b/scripts/gearboxutil @@ -0,0 +1,225 @@ +#! /usr/bin/python + +import swsssdk +import sys +from tabulate import tabulate +from natsort import natsorted + +import os + +# mock the redis for unit test purposes # +try: + if os.environ["UTILITIES_UNIT_TESTING"] == "1": + modules_path = os.path.join(os.path.dirname(__file__), "..") + tests_path = os.path.join(modules_path, "sonic-utilities-tests") + sys.path.insert(0, modules_path) + sys.path.insert(0, tests_path) + import mock_tables.dbconnector + client = mock_tables.dbconnector.redis.StrictRedis() + if client.keys() is None: + raise Exception("Invalid mock_table keys") +except KeyError: + pass + +# ========================== Common gearbox-utils logic ========================== + +GEARBOX_TABLE_PHY_PREFIX = "_GEARBOX_TABLE:phy:{}" +GEARBOX_TABLE_INTERFACE_PREFIX = "_GEARBOX_TABLE:interface:{}" +GEARBOX_TABLE_PORT_PREFIX = "_GEARBOX_TABLE:phy:{}:ports:{}" + +PORT_TABLE_ETHERNET_PREFIX = "PORT_TABLE:{}" + +PHY_NAME = "name" +PHY_ID = "phy_id" +PHY_FIRMWARE_MAJOR_VERSION = "firmware_major_version" +PHY_LINE_LANES = "line_lanes" +PHY_SYSTEM_LANES = "system_lanes" + +PORT_OPER_STATUS = "oper_status" +PORT_ADMIN_STATUS = "admin_status" +PORT_SYSTEM_SPEED = "system_speed" +PORT_LINE_SPEED = "line_speed" + +INTF_NAME = "name" +INTF_LANES = "lanes" +INTF_SPEED = "speed" + +def get_appl_key_attr(db, key, attr, lane_count=1): + """ + Get APPL_DB key attribute + """ + + val = db.get(db.APPL_DB, key, attr) + if val is None: + return "N/A" + + if "speed" in attr: + if val == "0": + return "N/A" + + speed = int(val[:-3]) + + if (speed % lane_count == 0): + speed = speed // lane_count + else: + return "N/A" + + val = '{}G'.format(str(speed)) + + return val + +def db_connect_appl(): + appl_db = swsssdk.SonicV2Connector(host='127.0.0.1') + if appl_db is None: + return None + appl_db.connect(appl_db.APPL_DB) + return appl_db + +def db_connect_state(): + """ + Connect to REDIS STATE DB and get optics info + """ + state_db = swsssdk.SonicV2Connector(host='127.0.0.1') + if state_db is None: + return None + state_db.connect(state_db.STATE_DB, False) # Make one attempt only + return state_db + +def appl_db_keys_get(appl_db): + """ + Get APPL_DB Keys + """ + return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_PHY_PREFIX.format("*")) + +def appl_db_interface_keys_get(appl_db): + """ + Get APPL_DB Keys + """ + return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_INTERFACE_PREFIX.format("*")) + +# ========================== phy-status logic ========================== + +phy_header_status = ['PHY Id', 'Name', 'Firmware'] + +class PhyStatus(object): + + def display_phy_status(self, appl_db_keys): + """ + Generate phy status output + """ + table = [] + key = [] + + for key in appl_db_keys: + if 'lanes' in key or 'ports' in key: + continue + list_items = key.split(':') + phy_id = list_items[2] + data_row = ( + phy_id, + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_NAME), + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_FIRMWARE_MAJOR_VERSION)) + table.append(data_row) + + # Sorting and tabulating the result table. + sorted_table = natsorted(table) + print tabulate(sorted_table, phy_header_status, tablefmt="simple", stralign='right') + + def __init__(self): + self.appl_db = db_connect_appl() + if self.appl_db is None: + return + + appl_db_keys = appl_db_keys_get(self.appl_db) + if appl_db_keys is None: + return + + self.display_phy_status(appl_db_keys) + +# ========================== interface-status logic ========================== + +intf_header_status = ['PHY Id', 'Interface', 'MAC Lanes', 'MAC Lane Speed', 'PHY Lanes', 'PHY Lane Speed', 'Line Lanes', 'Line Lane Speed', 'Oper', 'Admin'] + +class InterfaceStatus(object): + + def display_intf_status(self, appl_db_keys): + """ + Generate phy status output + """ + table = [] + key = [] + + for key in appl_db_keys: + list_items = key.split(':') + index = list_items[2] + + name = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), INTF_NAME), + name = name[0] + + mac_lanes = get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), INTF_LANES) + lanes = mac_lanes.split(',') + lane_count = 0 + for lane in lanes: + lane_count += 1 + + phy_id = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_ID) + + data_row = ( + phy_id, + name, + mac_lanes, + get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), INTF_SPEED, lane_count), + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_SYSTEM_LANES), + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PORT_PREFIX.format(phy_id, index), PORT_SYSTEM_SPEED), + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_INTERFACE_PREFIX.format(index), PHY_LINE_LANES), + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PORT_PREFIX.format(phy_id, index), PORT_LINE_SPEED), + get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), PORT_OPER_STATUS), + get_appl_key_attr(self.appl_db, PORT_TABLE_ETHERNET_PREFIX.format(name), PORT_ADMIN_STATUS)) + + table.append(data_row) + + # Sorting and tabulating the result table. + sorted_table = natsorted(table) + print tabulate(sorted_table, intf_header_status, tablefmt="simple", stralign='right') + + def __init__(self): + self.appl_db = db_connect_appl() + if self.appl_db is None: + return + + appl_db_keys = appl_db_interface_keys_get(self.appl_db) + if appl_db_keys is None: + return + + self.display_intf_status(appl_db_keys) + +def main(args): + """ + phy status + interfaces status + interfaces counters + """ + + if len(args) == 0: + print "No valid arguments provided" + return + + cmd1 = args[0] + if cmd1 != "phys" and cmd1 != "interfaces": + print "No valid command provided" + return + + cmd2 = args[1] + if cmd2 != "status" and cmd2 != "counters": + print "No valid command provided" + return + + if cmd1 == "phys" and cmd2 == "status": + PhyStatus() + elif cmd1 == "interfaces" and cmd2 == "status": + InterfaceStatus() + + sys.exit(0) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/setup.py b/setup.py index 09e893e3f348..edffa77cd06a 100644 --- a/setup.py +++ b/setup.py @@ -76,6 +76,7 @@ 'scripts/fdbclear', 'scripts/fdbshow', 'scripts/filter_fdb_entries.py', + 'scripts/gearboxutil', 'scripts/generate_dump', 'scripts/intfutil', 'scripts/intfstat', diff --git a/show/main.py b/show/main.py index 6fdc243da9a3..981673910dd6 100755 --- a/show/main.py +++ b/show/main.py @@ -2842,6 +2842,43 @@ def pool(verbose): cmd = "sudo natconfig -p" run_command(cmd, display_cmd=verbose) +# Define GEARBOX commands only if GEARBOX is configured +app_db = SonicV2Connector(host='127.0.0.1') +app_db.connect(app_db.APPL_DB) +if app_db.keys(app_db.APPL_DB, '_GEARBOX_TABLE:phy:*'): + + @cli.group(cls=AliasedGroup) + def gearbox(): + """Show gearbox info""" + pass + + # 'phys' subcommand ("show gearbox phys") + @gearbox.group(cls=AliasedGroup) + def phys(): + """Show external PHY information""" + pass + + # 'status' subcommand ("show gearbox phys status") + @phys.command() + @click.pass_context + def status(ctx): + """Show gearbox phys status""" + run_command("gearboxutil phys status") + return + + # 'interfaces' subcommand ("show gearbox interfaces") + @gearbox.group(cls=AliasedGroup) + def interfaces(): + """Show gearbox interfaces information""" + pass + + # 'status' subcommand ("show gearbox interfaces status") + @interfaces.command() + @click.pass_context + def status(ctx): + """Show gearbox interfaces status""" + run_command("gearboxutil interfaces status") + return # 'bindings' subcommand ("show nat config bindings") @config.command() diff --git a/sonic-utilities-tests/gearbox_test.py b/sonic-utilities-tests/gearbox_test.py new file mode 100644 index 000000000000..2d0cef041e78 --- /dev/null +++ b/sonic-utilities-tests/gearbox_test.py @@ -0,0 +1,51 @@ +import sys +import os +from click.testing import CliRunner +from unittest import TestCase + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, test_path) +sys.path.insert(0, modules_path) + +import mock_tables.dbconnector # required by sonic-utilities-tests + +import show.main as show + +class TestGearbox(TestCase): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def setUp(self): + self.runner = CliRunner() + + def test_gearbox_phys_status_validation(self): + result = self.runner.invoke(show.cli.commands["gearbox"].commands["phys"].commands["status"], []) + print >> sys.stderr, result.output + expected_output = ( + "PHY Id Name Firmware\n" + "-------- ------- ----------\n" + " 1 sesto-1 v0.2\n" + " 2 sesto-2 v0.3" + ) + self.assertEqual(result.output.strip(), expected_output) + + def test_gearbox_interfaces_status_validation(self): + result = self.runner.invoke(show.cli.commands["gearbox"].commands["interfaces"].commands["status"], []) + print >> sys.stderr, result.output + expected_output = ( + "PHY Id Interface MAC Lanes MAC Lane Speed PHY Lanes PHY Lane Speed Line Lanes Line Lane Speed Oper Admin\n" + "-------- ----------- --------------- ---------------- --------------- ---------------- ------------ ----------------- ------ -------\n" + " 1 Ethernet200 200,201,202,203 25G 300,301,302,303 25G 304,305 50G down up" + ) + self.assertEqual(result.output.strip(), expected_output) + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0" diff --git a/sonic-utilities-tests/mock_tables/appl_db.json b/sonic-utilities-tests/mock_tables/appl_db.json index 96c071e3a2cc..4239cf949d0c 100644 --- a/sonic-utilities-tests/mock_tables/appl_db.json +++ b/sonic-utilities-tests/mock_tables/appl_db.json @@ -1,5 +1,6 @@ { "PORT_TABLE:Ethernet0": { + "index": "0", "lanes": "0", "alias": "Ethernet0", "description": "ARISTA01T2:Ethernet1", @@ -11,6 +12,7 @@ "admin_status": "up" }, "PORT_TABLE:Ethernet200": { + "index": "200", "lanes": "200,201,202,203", "alias": "Ethernet200", "description": "Ethernet200", @@ -18,9 +20,34 @@ "oper_status": "down", "fec": "rs", "mtu": "9100", - "pfc_asym": "off" + "pfc_asym": "off", + "admin_status": "up" }, "INTF_TABLE:Ethernet0.10": { "admin_status": "up" + }, + "_GEARBOX_TABLE:phy:1": { + "name": "sesto-1", + "phy_id": "1", + "phy_oid": "0x21000000000002", + "firmware_major_version": "v0.2" + }, + "_GEARBOX_TABLE:phy:2": { + "name": "sesto-2", + "phy_id": "2", + "phy_oid": "0x21000000000003", + "firmware_major_version": "v0.3" + }, + "_GEARBOX_TABLE:interface:200": { + "name": "Ethernet200", + "index": "200", + "line_lanes": "304,305", + "phy_id": "1", + "system_lanes": "300,301,302,303" + }, + "_GEARBOX_TABLE:phy:1:ports:200": { + "index": "200", + "line_speed": "50000", + "system_speed": "25000" } }