From 9196818f39fbf4560a6f91475f98623631360111 Mon Sep 17 00:00:00 2001 From: Syd Logan Date: Tue, 2 Jun 2020 18:59:45 -0700 Subject: [PATCH] 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 --- scripts/gearboxutil | 275 ++++++++++++++++++ setup.py | 1 + show/main.py | 37 +++ sonic-utilities-tests/gearbox_test.py | 51 ++++ .../mock_tables/appl_db.json | 27 +- .../mock_tables/asic_db.json | 1 + .../mock_tables/asic_db2.json | 16 + .../mock_tables/dbconnector.py | 2 + 8 files changed, 409 insertions(+), 1 deletion(-) create mode 100755 scripts/gearboxutil create mode 100644 sonic-utilities-tests/gearbox_test.py create mode 100644 sonic-utilities-tests/mock_tables/asic_db2.json diff --git a/scripts/gearboxutil b/scripts/gearboxutil new file mode 100755 index 0000000000..27408742b0 --- /dev/null +++ b/scripts/gearboxutil @@ -0,0 +1,275 @@ +#! /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 # required by sonic-utilities-tests +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:{}" + +ASIC_STATE_TABLE_PORT_PREFIX = "ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:{}" +ASIC_STATE_TABLE_SWITCH_PREFIX = "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:{}" +ASIC_GEARBOX_TABLE_SWITCH_PREFIX = "ASIC_GEARBOX|MISC_SAI_SWITCH_ATTR:{}" + +PHY_NAME = "name" +PHY_ID = "phy_id" +PHY_OID = "phy_oid" +PHY_FIRMWARE_MAJOR_VERSION = "SAI_SWITCH_ATTR_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 get_asic2_key_attr(asic_db, phy_oid, attr): + """ + Get the phy attribute + """ + + full_table_id = ASIC_GEARBOX_TABLE_SWITCH_PREFIX.format(phy_oid) + # Replace needed for mock_table unit testing + full_table_id = full_table_id.replace("ATTR:0", "ATTR:oid:0") + val = asic_db.get(asic_db.ASIC_DB2, full_table_id, attr) + if val is None: + return "N/A" + + return val + +def db_connect_asic2(): + asic_db = swsssdk.SonicV2Connector(host='127.0.0.1') + if asic_db is None: + return None + asic_db.connect(asic_db.ASIC_DB2) + return asic_db + +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 ASIC_DB Keys + """ + return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_INTERFACE_PREFIX.format("*")) + +def asic2_db_keys_get(asic_db): + """ + Get ASIC_DB Keys + """ + return asic_db.keys(asic_db.ASIC_DB2, ASIC_GEARBOX_TABLE_SWITCH_PREFIX.format("*")) + +# ========================== phy-status logic ========================== + +phy_header_status = ['PHY Id', 'Name', 'Firmware'] + +class PhyStatus(object): + + def display_phy_status(self, appl_db_keys, asic_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] + phy_oid = get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_OID) + data_row = ( + phy_id, + get_appl_key_attr(self.appl_db, GEARBOX_TABLE_PHY_PREFIX.format(phy_id), PHY_NAME), + get_asic2_key_attr(self.asic_db, phy_oid, 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.asic_db = db_connect_asic2() + self.appl_db = db_connect_appl() + + if self.asic_db is None: + return + if self.appl_db is None: + return + + appl_db_keys = appl_db_keys_get(self.appl_db) + if appl_db_keys is None: + return + + asic_db_keys = asic2_db_keys_get(self.asic_db) + if asic_db_keys is None: + return + + self.display_phy_status(appl_db_keys, asic_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, asic_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.asic_db = db_connect_asic2() + self.appl_db = db_connect_appl() + + if self.asic_db is None: + return + 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 + + asic_db_keys = asic2_db_keys_get(self.asic_db) + if asic_db_keys is None: + return + + self.display_intf_status(appl_db_keys, asic_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 09e893e3f3..edffa77cd0 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 f2d4df5308..5e740670ba 100755 --- a/show/main.py +++ b/show/main.py @@ -2839,6 +2839,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 0000000000..2d0cef041e --- /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 96c071e3a2..b9ca17bd43 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,32 @@ "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" + }, + "_GEARBOX_TABLE:phy:2": { + "name": "sesto-2", + "phy_id": "2", + "phy_oid": "0x21000000000003" + }, + "_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" } } diff --git a/sonic-utilities-tests/mock_tables/asic_db.json b/sonic-utilities-tests/mock_tables/asic_db.json index 1a769b82b5..e2364559ee 100644 --- a/sonic-utilities-tests/mock_tables/asic_db.json +++ b/sonic-utilities-tests/mock_tables/asic_db.json @@ -4,3 +4,4 @@ "SAI_SWITCH_ATTR_SRC_MAC_ADDRESS": "DE:AD:BE:EF:CA:FE" } } + diff --git a/sonic-utilities-tests/mock_tables/asic_db2.json b/sonic-utilities-tests/mock_tables/asic_db2.json new file mode 100644 index 0000000000..15c8dab838 --- /dev/null +++ b/sonic-utilities-tests/mock_tables/asic_db2.json @@ -0,0 +1,16 @@ +{ + "ASIC_GEARBOX|MISC_SAI_SWITCH_ATTR:oid:0x21000000000002": { + "SAI_SWITCH_ATTR_FIRMWARE_MAJOR_VERSION": "v0.2" + }, + "ASIC_GEARBOX|MISC_SAI_SWITCH_ATTR:oid:0x21000000000003": { + "SAI_SWITCH_ATTR_FIRMWARE_MAJOR_VERSION": "v0.3" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000002": { + "SAI_SWITCH_ATTR_INIT_SWITCH": "true", + "SAI_SWITCH_ATTR_TYPE": "SAI_SWITCH_TYPE_PHY" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000003": { + "SAI_SWITCH_ATTR_INIT_SWITCH": "true", + "SAI_SWITCH_ATTR_TYPE": "SAI_SWITCH_TYPE_PHY" + } +} \ No newline at end of file diff --git a/sonic-utilities-tests/mock_tables/dbconnector.py b/sonic-utilities-tests/mock_tables/dbconnector.py index 5a67337e6d..5bda541ca5 100644 --- a/sonic-utilities-tests/mock_tables/dbconnector.py +++ b/sonic-utilities-tests/mock_tables/dbconnector.py @@ -50,6 +50,8 @@ def __init__(self, *args, **kwargs): fname = 'config_db.json' elif db == 6: fname = 'state_db.json' + elif db == 8: + fname = 'asic_db2.json' else: raise ValueError("Invalid db") self.pubsub = MockPubSub()