diff --git a/config/chassis_modules.py b/config/chassis_modules.py new file mode 100644 index 0000000000..6e783ed856 --- /dev/null +++ b/config/chassis_modules.py @@ -0,0 +1,44 @@ +#!/usr/sbin/env python + +import click + +import utilities_common.cli as clicommon + +# +# 'chassis_modules' group ('config chassis_modules ...') +# +@click.group(cls=clicommon.AliasedGroup) +def chassis_modules(): + """Configure chassis-modules options""" + pass + +# +# 'shutdown' subcommand ('config chassis_modules shutdown ...') +# +@chassis_modules.command('shutdown') +@clicommon.pass_db +@click.argument('chassis_module_name', metavar='', required=True) +def shutdown_chassis_module(db, chassis_module_name): + """Chassis-module shutdown of module""" + config_db = db.cfgdb + ctx = click.get_current_context() + + if not chassis_module_name.startswith("SUPERVISOR") and \ + not chassis_module_name.startswith("LINE-CARD") and \ + not chassis_module_name.startswith("FABRIC-CARD"): + ctx.fail("'module_name' has to begin with 'SUPERVISOR', 'LINE-CARD' or 'FABRIC-CARD'") + + fvs = {'admin_status': 'down'} + config_db.set_entry('CHASSIS_MODULE', chassis_module_name, fvs) + +# +# 'startup' subcommand ('config chassis_modules startup ...') +# +@chassis_modules.command('startup') +@clicommon.pass_db +@click.argument('chassis_module_name', metavar='', required=True) +def startup_chassis_module(db, chassis_module_name): + """Chassis-module startup of module""" + config_db = db.cfgdb + + config_db.set_entry('CHASSIS_MODULE', chassis_module_name, None) diff --git a/config/main.py b/config/main.py index 5a1cf7dc05..b691edf151 100755 --- a/config/main.py +++ b/config/main.py @@ -32,6 +32,7 @@ import nat import vlan from config_mgmt import ConfigMgmtDPB +import chassis_modules CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) @@ -887,7 +888,7 @@ def config(ctx): config.add_command(kube.kubernetes) config.add_command(nat.nat) config.add_command(vlan.vlan) - +config.add_command(chassis_modules.chassis_modules) @config.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, diff --git a/show/chassis_modules.py b/show/chassis_modules.py new file mode 100644 index 0000000000..5a13a32419 --- /dev/null +++ b/show/chassis_modules.py @@ -0,0 +1,63 @@ +import click +from natsort import natsorted +from tabulate import tabulate +from swsssdk import SonicV2Connector + +import utilities_common.cli as clicommon + +CHASSIS_MODULE_INFO_TABLE = 'CHASSIS_MODULE_TABLE' +CHASSIS_MODULE_INFO_KEY_TEMPLATE = 'CHASSIS_MODULE {}' +CHASSIS_MODULE_INFO_DESC_FIELD = 'desc' +CHASSIS_MODULE_INFO_SLOT_FIELD = 'slot' +CHASSIS_MODULE_INFO_OPERSTATUS_FIELD = 'oper_status' +CHASSIS_MODULE_INFO_ADMINSTATUS_FIELD = 'admin_status' + +@click.group(cls=clicommon.AliasedGroup) +def chassis_modules(): + """Show chassis-modules information""" + pass + +@chassis_modules.command() +@clicommon.pass_db +@click.argument('chassis_module_name', metavar='', required=False) +def status(db, chassis_module_name): + """Show chassis-modules status""" + + header = ['Name', 'Description', 'Physical-Slot', 'Oper-Status', 'Admin-Status'] + chassis_cfg_table = db.cfgdb.get_table('CHASSIS_MODULE') + + state_db = SonicV2Connector(host="127.0.0.1") + state_db.connect(state_db.STATE_DB) + + key_pattern = '*' + if chassis_module_name: + key_pattern = '|'+chassis_module_name + + keys = state_db.keys(state_db.STATE_DB, CHASSIS_MODULE_INFO_TABLE + key_pattern) + if not keys: + print('Key {} not found in {} table'.format(key_pattern, CHASSIS_MODULE_INFO_TABLE)) + return + + table = [] + for key in natsorted(keys): + key_list = key.split('|') + if len(key_list) != 2: # error data in DB, log it and ignore + print('Warn: Invalid Key {} in {} table'.format(key, CHASSIS_MODULE_INFO_TABLE)) + continue + + data_dict = state_db.get_all(state_db.STATE_DB, key) + desc = data_dict[CHASSIS_MODULE_INFO_DESC_FIELD] + slot = data_dict[CHASSIS_MODULE_INFO_SLOT_FIELD] + oper_status = data_dict[CHASSIS_MODULE_INFO_OPERSTATUS_FIELD] + + admin_status = 'up' + config_data = chassis_cfg_table.get(key_list[1]) + if config_data is not None: + admin_status = config_data.get(CHASSIS_MODULE_INFO_ADMINSTATUS_FIELD) + + table.append((key_list[1], desc, slot, oper_status, admin_status)) + + if table: + click.echo(tabulate(table, header, tablefmt='simple', stralign='right')) + else: + click.echo('No data available in CHASSIS_MODULE_TABLE\n') diff --git a/show/main.py b/show/main.py index 956113367f..e472292797 100755 --- a/show/main.py +++ b/show/main.py @@ -19,6 +19,7 @@ import vlan import system_health import fgnhg +import chassis_modules from sonic_py_common import device_info, multi_asic from swsssdk import ConfigDBConnector @@ -133,6 +134,7 @@ def cli(ctx): cli.add_command(vlan.vlan) cli.add_command(system_health.system_health) cli.add_command(fgnhg.fgnhg) +cli.add_command(chassis_modules.chassis_modules) # # 'vrf' command ("show vrf") @@ -1780,7 +1782,6 @@ def counts(group, counter_type, verbose): run_command(cmd, display_cmd=verbose) - # # 'ecn' command ("show ecn") # diff --git a/tests/chassis_modules_test.py b/tests/chassis_modules_test.py new file mode 100644 index 0000000000..754845eba2 --- /dev/null +++ b/tests/chassis_modules_test.py @@ -0,0 +1,118 @@ +import sys +import os +from click.testing import CliRunner + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +import show.main as show +import config.main as config +import tests.mock_tables.dbconnector +from utilities_common.db import Db + +show_linecard0_shutdown_output="""\ +LINE-CARD0 line-card 1 Empty down +""" + +show_linecard0_startup_output="""\ +LINE-CARD0 line-card 1 Empty up +""" +header_lines = 2 +warning_lines = 0 + +class TestChassisModules(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def test_show_all_count_lines(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], []) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + modules = ["FABRIC-CARD0", "FABRIC-CARD1", "LINE-CARD0", "LINE-CARD1", "SUPERVISOR0"] + for i, module in enumerate(modules): + assert module in result_lines[i + warning_lines + header_lines] + assert len(result_lines) == warning_lines + header_lines + len(modules) + + def test_show_single_count_lines(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], ["LINE-CARD0"]) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + modules = ["LINE-CARD0"] + for i, module in enumerate(modules): + assert module in result_lines[i+header_lines] + assert len(result_lines) == header_lines + len(modules) + + def test_show_module_down(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], ["LINE-CARD1"]) + result_lines = result.output.strip('\n').split('\n') + assert result.exit_code == 0 + result_out = (result_lines[header_lines]).split() + assert result_out[4] == 'down' + + def test_show_incorrect_command(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"], []) + print(result.output) + print(result.exit_code) + assert result.exit_code == 0 + + def test_show_incorrect_module(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], ["TEST-CARD1"]) + print(result.output) + print(result.exit_code) + assert result.exit_code == 0 + + def test_config_shutdown_module(self): + runner = CliRunner() + db = Db() + result = runner.invoke(config.config.commands["chassis-modules"].commands["shutdown"], ["LINE-CARD0"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], ["LINE-CARD0"], obj=db) + print(result.exit_code) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + assert result.exit_code == 0 + header_lines = 2 + result_out = " ".join((result_lines[header_lines]).split()) + assert result_out.strip('\n') == show_linecard0_shutdown_output.strip('\n') + #db.cfgdb.set_entry("CHASSIS_MODULE", "LINE-CARD0", { "admin_status" : "down" }) + #db.get_data("CHASSIS_MODULE", "LINE-CARD0") + + def test_config_startup_module(self): + runner = CliRunner() + db = Db() + result = runner.invoke(config.config.commands["chassis-modules"].commands["startup"], ["LINE-CARD0"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(show.cli.commands["chassis-modules"].commands["status"], ["LINE-CARD0"], obj=db) + print(result.exit_code) + print(result.output) + result_lines = result.output.strip('\n').split('\n') + assert result.exit_code == 0 + result_out = " ".join((result_lines[header_lines]).split()) + assert result_out.strip('\n') == show_linecard0_startup_output.strip('\n') + + def test_config_incorrect_module(self): + runner = CliRunner() + db = Db() + result = runner.invoke(config.config.commands["chassis-modules"].commands["shutdown"], ["TEST-CARD0"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ["UTILITIES_UNIT_TESTING"] = "0" diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index eeb72f74f6..b50f28a3d0 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -1299,5 +1299,8 @@ "acl_entry_high_threshold": "85", "fdb_entry_low_threshold": "70", "ipv6_nexthop_high_threshold": "85" + }, + "CHASSIS_MODULE|LINE-CARD1": { + "admin_status": "down" } } diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index b635c78e2d..5a9595b09a 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -272,5 +272,33 @@ "29" : "200.200.200.5@Vlan1000", "30" : "200.200.200.5@Vlan1000", "31" : "200.200.200.5@Vlan1000" + }, + "CHASSIS_TABLE|CHASSIS 1": { + "module_num": "5" + }, + "CHASSIS_MODULE_TABLE|SUPERVISOR0": { + "desc": "supervisor-card", + "oper_status": "Online", + "slot": "16" + }, + "CHASSIS_MODULE_TABLE|LINE-CARD0": { + "desc": "line-card", + "oper_status": "Empty", + "slot": "1" + }, + "CHASSIS_MODULE_TABLE|LINE-CARD1": { + "desc": "line-card", + "oper_status": "Online", + "slot": "2" + }, + "CHASSIS_MODULE_TABLE|FABRIC-CARD0": { + "desc": "fabric-card", + "oper_status": "Online", + "slot": "17" + }, + "CHASSIS_MODULE_TABLE|FABRIC-CARD1": { + "desc": "fabric-card", + "oper_status": "Offline", + "slot": "18" } }