diff --git a/scripts/fanshow b/scripts/fanshow index 856f8d663c59..cdf750ab168f 100755 --- a/scripts/fanshow +++ b/scripts/fanshow @@ -65,7 +65,6 @@ class FanShow(object): presence = data_dict[PRESENCE_FIELD_NAME].lower() presence = 'Present' if presence == 'true' else 'Not Present' - status = data_dict[STATUS_FIELD_NAME] status_lower = status.lower() if status_lower == 'true': @@ -73,9 +72,8 @@ class FanShow(object): elif status_lower == 'false': status = 'Not OK' - table.append((data_dict[DRAWER_FIELD_NAME], data_dict[LED_STATUS_FIELD_NAME], name, speed, data_dict[DIRECTION_FIELD_NAME], presence, status, + table.append((data_dict[DRAWER_FIELD_NAME], data_dict[LED_STATUS_FIELD_NAME], name, speed, data_dict[DIRECTION_FIELD_NAME], presence, status, data_dict[TIMESTAMP_FIELD_NAME])) - if table: print(tabulate(table, header, tablefmt='simple', stralign='right')) else: diff --git a/scripts/fibshow b/scripts/fibshow new file mode 100755 index 000000000000..2865615e36a2 --- /dev/null +++ b/scripts/fibshow @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +""" + Script to show dataplane/FIB entries + + usage: fibshow [-ip IPADDR] v + optional arguments: + -ip IPADDR, --ipaddr IPADDR + dataplane/FIB entry for a specific address + + Example of the output: + admin@str~$ fibshow -4 + No. Vrf Route Nexthop Ifname + ----- ------- ------------------ --------------------------------------- ----------------------------------------------------------- + 1 Red 192.181.8.0/25 10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63 PortChannel101,PortChannel102,PortChannel103,PortChannel104 + 2 192.184.56.0/25 10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63 PortChannel101,PortChannel102,PortChannel103,PortChannel104 + ... + Total number of entries 19 + + admin@str:~$ fibshow -6 -ip 20c0:b560:0:80:: + No. Vrf Route Nexthop Ifname + ----- ------- ------------------- ----------------------------------- ----------------------------------------------------------- + 1 20c0:b560:0:80::/64 fc00::72,fc00::76,fc00::7a,fc00::7e PortChannel101,PortChannel102,PortChannel103,PortChannel104 + Total number of entries 1 +""" +import argparse +import sys +import os +import re + +# mock the redis for unit test purposes # +try: # pragma: no cover + if os.environ["UTILITIES_UNIT_TESTING"] == "1": + modules_path = os.path.join(os.path.dirname(__file__), "..") + test_path = os.path.join(modules_path, "tests") + sys.path.insert(0, modules_path) + sys.path.insert(0, test_path) + import mock_tables.dbconnector + mock_variants = { "1": 'appl_db'} + mock_db_path = os.path.join(test_path, "fibshow_input") + file_name = mock_variants[os.environ["FIBSHOW_MOCK"]] + jsonfile_asic = os.path.join(mock_db_path, file_name) + mock_tables.dbconnector.dedicated_dbs['APPL_DB'] = jsonfile_asic +except KeyError: # pragma: no cover + pass + +import ipaddress + +from swsscommon.swsscommon import SonicV2Connector +from tabulate import tabulate + +""" + Base class for v4 and v6 FIB entries. +""" + + +class FibBase(object): + + HEADER = ["No.", "Vrf", "Route", "Nexthop", "Ifname"] + + def __init__(self): + super(FibBase, self).__init__() + self.db = SonicV2Connector(host="127.0.0.1") + self.fetch_fib_data() + + def fetch_fib_data(self): + """ + Fetch FIB entries from APPL_DB + """ + self.db.connect(self.db.APPL_DB) + self.fib_entry_list = [] + + fib_str = self.db.keys(self.db.APPL_DB, "ROUTE_TABLE:*") + if not fib_str: + return + + for s in fib_str: + fib_entry = s + fib = fib_entry.split(":", 1)[-1] + if not fib: + continue + + ent = self.db.get_all(self.db.APPL_DB, s) + if not ent: + continue + + self.fib_entry_list.append((fib,) + (ent["nexthop"],) + (ent["ifname"],) ) + self.fib_entry_list.sort(key=lambda x: x[0]) + return + + def display(self, version, address): + """ + Display FIB entries from APPL_DB + """ + output = [] + fdb_index = 1 + for fib in self.fib_entry_list: + prefix = fib[0] + + if 'VRF' in fib[0]: + vrf = re.match(r"VRF-(.*)",fib[0].split(":")[0]).group(1) + prefix = fib[0].split(":")[1] + else: + vrf = "" + ip = ipaddress.ip_address(prefix.split("/")[0]) + + if address is not None: + if fib[0] == address: + if ip.version == 4 and version == "-4": + output.append([fdb_index, vrf, prefix, fib[1], fib[2]]) + fdb_index += 1 + elif ip.version == 6 and version == "-6": + output.append([fdb_index, vrf, prefix, fib[1], fib[2]]) + fdb_index += 1 + break + else: + continue + else: + if ip.version == 4 and version == "-4": + output.append([fdb_index, vrf, prefix, fib[1], fib[2]]) + fdb_index += 1 + elif ip.version == 6 and version == "-6": + output.append([fdb_index, vrf, prefix, fib[1], fib[2]]) + fdb_index += 1 + print(tabulate(output, self.HEADER)) + print("Total number of entries {0}".format(len(output))) + +def main(): + + parser = argparse.ArgumentParser(description='Show dataplane/FIB entries', + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('-ip', '--ipaddr', type=str, + help='dataplane/FIB route for a specific address', default=None) + parser.add_argument('v', help='IP Version -4 or -6') + + args = parser.parse_args() + + try: + fib = FibBase() + fib.display(args.v, args.ipaddr) + except Exception as e: + print(str(e)) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 37a4769734c4..a8bf39170c4e 100644 --- a/setup.py +++ b/setup.py @@ -101,6 +101,7 @@ 'scripts/fast-reboot-dump.py', 'scripts/fdbclear', 'scripts/fdbshow', + 'scripts/fibshow', 'scripts/flow_counters_stat', 'scripts/gearboxutil', 'scripts/generate_dump', diff --git a/show/main.py b/show/main.py index 528ad81e84a3..410f64a9a7d0 100755 --- a/show/main.py +++ b/show/main.py @@ -854,6 +854,19 @@ def protocol(verbose): cmd = 'sudo {} -c "show ip protocol"'.format(constants.RVTYSH_COMMAND) run_command(cmd, display_cmd=verbose) +# +# 'fib' subcommand ("show ip fib") +# +@ip.command() +@click.argument('ipaddress', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def fib(ipaddress, verbose): + """Show IP FIB table""" + cmd = "fibshow -4" + if ipaddress is not None: + cmd += " -ip {}".format(ipaddress) + run_command(cmd, display_cmd=verbose) + # # 'ipv6' group ("show ipv6 ...") @@ -983,6 +996,19 @@ def link_local_mode(verbose): click.echo(tabulate(body, header, tablefmt="grid")) +# +# 'fib' subcommand ("show ipv6 fib") +# +@ipv6.command() +@click.argument('ipaddress', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def fib(ipaddress, verbose): + """Show IP FIB table""" + cmd = "fibshow -6" + if ipaddress is not None: + cmd += " -ip {}".format(ipaddress) + run_command(cmd, display_cmd=verbose) + # # 'lldp' group ("show lldp ...") # diff --git a/tests/conftest.py b/tests/conftest.py index ecf32ffa0534..b48d0ef93f70 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -313,3 +313,8 @@ def setup_ip_route_commands(): return show +@pytest.fixture +def setup_fib_commands(): + import show.main as show + return show + diff --git a/tests/fibshow_input/__init__.py b/tests/fibshow_input/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/fibshow_input/appl_db.json b/tests/fibshow_input/appl_db.json new file mode 100644 index 000000000000..450040886f44 --- /dev/null +++ b/tests/fibshow_input/appl_db.json @@ -0,0 +1,34 @@ +{ + "ROUTE_TABLE:192.168.104.0/25": { + "nexthop": "10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + }, + "ROUTE_TABLE:192.168.104.128/25": { + "nexthop": "", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + }, + "ROUTE_TABLE:192.168.112.0/25": { + "nexthop": "10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63", + "ifname" : "" + }, + "ROUTE_TABLE:VRF-Red:192.168.112.128/25": { + "nexthop": "10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + }, + "ROUTE_TABLE:192.168.120.0/25": { + "nexthop": "10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + }, + "ROUTE_TABLE:20c0:fe28:0:80::/64": { + "nexthop": "fc00::72,fc00::76,fc00::7a,fc00::7e", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + }, + "ROUTE_TABLE:20c0:fe28::/64": { + "nexthop": "fc00::72,fc00::76,fc00::7a,fc00::7e", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + }, + "ROUTE_TABLE:20c0:fe30:0:80::/64": { + "nexthop": "", + "ifname" : "PortChannel101,PortChannel102,PortChannel103,PortChannel104" + } +} diff --git a/tests/fibshow_test.py b/tests/fibshow_test.py new file mode 100644 index 000000000000..9593d0f884f5 --- /dev/null +++ b/tests/fibshow_test.py @@ -0,0 +1,92 @@ +import os + +import pytest + +from click.testing import CliRunner + +from utilities_common import multi_asic +from utilities_common import constants +from .utils import get_result_and_return_code +import show.main as show + +from unittest.mock import patch + +from sonic_py_common import device_info + +show_ip_fib_v4 = """\ + No. Vrf Route Nexthop Ifname +----- ----- ------------------ --------------------------------------- ----------------------------------------------------------- + 1 192.168.104.0/25 10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63 PortChannel101,PortChannel102,PortChannel103,PortChannel104 + 2 192.168.104.128/25 PortChannel101,PortChannel102,PortChannel103,PortChannel104 + 3 192.168.112.0/25 10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63 + 4 192.168.120.0/25 10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63 PortChannel101,PortChannel102,PortChannel103,PortChannel104 + 5 Red 192.168.112.128/25 10.0.0.57,10.0.0.59,10.0.0.61,10.0.0.63 PortChannel101,PortChannel102,PortChannel103,PortChannel104 +Total number of entries 5 +""" + +show_ip_fib_v6 = """\ + No. Vrf Route Nexthop Ifname +----- ----- ------------------- ----------------------------------- ----------------------------------------------------------- + 1 20c0:fe28:0:80::/64 fc00::72,fc00::76,fc00::7a,fc00::7e PortChannel101,PortChannel102,PortChannel103,PortChannel104 + 2 20c0:fe28::/64 fc00::72,fc00::76,fc00::7a,fc00::7e PortChannel101,PortChannel102,PortChannel103,PortChannel104 + 3 20c0:fe30:0:80::/64 PortChannel101,PortChannel102,PortChannel103,PortChannel104 +Total number of entries 3 +""" + +root_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(root_path) +scripts_path = os.path.join(modules_path, "scripts") + + +class TestFibshow(): + @pytest.fixture(scope="class", autouse=True) + def setup_class(cls): + print("SETUP") + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "1" + yield + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0" + + @pytest.fixture(scope="function", autouse=True) + def setUp(self): + self.runner = CliRunner() + yield + del os.environ["FIBSHOW_MOCK"] + + def set_mock_variant(self, variant: str): + os.environ["FIBSHOW_MOCK"] = variant + + def test_show_ip_fib(self): + self.set_mock_variant("1") + from .mock_tables import dbconnector + modules_path = os.path.join(os.path.dirname(__file__), "..") + test_path = os.path.join(modules_path, "tests") + mock_db_path = os.path.join(test_path, "fibshow_input") + jsonfile_appl = os.path.join(mock_db_path, 'appl_db') + dbconnector.dedicated_dbs['APPL_DB'] = jsonfile_appl + print(dbconnector.load_database_config()) + result = self.runner.invoke(show.cli.commands["ip"].commands["fib"], []) + dbconnector.dedicated_dbs['APPL_DB'] = None + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_ip_fib_v4 + + def test_show_ipv6_fib(self): + self.set_mock_variant("1") + from .mock_tables import dbconnector + modules_path = os.path.join(os.path.dirname(__file__), "..") + test_path = os.path.join(modules_path, "tests") + mock_db_path = os.path.join(test_path, "fibshow_input") + jsonfile_appl = os.path.join(mock_db_path, 'appl_db') + dbconnector.dedicated_dbs['APPL_DB'] = jsonfile_appl + print(dbconnector.load_database_config()) + result = self.runner.invoke(show.cli.commands["ipv6"].commands["fib"], []) + dbconnector.dedicated_dbs['APPL_DB'] = None + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_ip_fib_v6 + diff --git a/tests/ip_config_test.py b/tests/ip_config_test.py index 6372d5ab7617..47f82fb9597f 100644 --- a/tests/ip_config_test.py +++ b/tests/ip_config_test.py @@ -15,44 +15,44 @@ class TestConfigIP(object): def setup_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "1" print("SETUP") - + ''' Tests for IPv4 ''' - + def test_add_del_interface_valid_ipv4(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet64 10.10.10.1/24 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet64", "10.10.10.1/24"], obj=obj) print(result.exit_code, result.output) assert result.exit_code == 0 assert ('Ethernet64', '10.10.10.1/24') in db.cfgdb.get_table('INTERFACE') - + # config int ip remove Ethernet64 10.10.10.1/24 - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet64", "10.10.10.1/24"], obj=obj) + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet64", "10.10.10.1/24"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ('Ethernet64', '10.10.10.1/24') not in db.cfgdb.get_table('INTERFACE') - + def test_add_interface_invalid_ipv4(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet64 10000.10.10.1/24 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet64", "10000.10.10.1/24"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ERROR_MSG in result.output - + def test_add_interface_ipv4_invalid_mask(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet64 10.10.10.1/37 - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet64", "10.10.10.1/37"], obj=obj) + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet64", "10.10.10.1/37"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ERROR_MSG in result.output @@ -61,26 +61,26 @@ def test_add_interface_ipv4_with_leading_zeros(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet68 10.10.10.002/24 - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet68", "10.10.10.0002/24"], obj=obj) + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet68", "10.10.10.0002/24"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ERROR_MSG in result.output ''' Tests for IPv6 ''' - + def test_add_del_interface_valid_ipv6(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet72 2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet72", "2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) print(result.exit_code, result.output) assert result.exit_code == 0 assert ('Ethernet72', '2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('INTERFACE') - + # config int ip remove Ethernet72 2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet72", "2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) print(result.exit_code, result.output) @@ -105,18 +105,18 @@ def test_add_interface_invalid_ipv6(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet72 20001:0db8:11a3:09d7:1f34:8a2e:07a0:765d/34 - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet72", "20001:0db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet72", "20001:0db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj) print(result.exit_code, result.output) assert result.exit_code != 0 assert ERROR_MSG in result.output - + def test_add_interface_ipv6_invalid_mask(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet72 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d/200 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet72", "2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d/200"], obj=obj) print(result.exit_code, result.output) @@ -127,7 +127,7 @@ def test_add_del_interface_ipv6_with_leading_zeros(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet68 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d/34 result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet68", "2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d/34"], obj=obj) print(result.exit_code, result.output) @@ -144,9 +144,9 @@ def test_add_del_interface_shortened_ipv6_with_leading_zeros(self): db = Db() runner = CliRunner() obj = {'config_db':db.cfgdb} - + # config int ip add Ethernet68 3000::001/64 - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet68", "3000::001/64"], obj=obj) + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet68", "3000::001/64"], obj=obj) print(result.exit_code, result.output) assert result.exit_code == 0 assert ('Ethernet68', '3000::1/64') in db.cfgdb.get_table('INTERFACE')