diff --git a/pfcwd/main.py b/pfcwd/main.py index c12c93a58c40..c55038b9b21a 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -3,6 +3,8 @@ import click +import utilities_common.cli as clicommon + from natsort import natsorted from sonic_py_common.multi_asic import get_external_ports from tabulate import tabulate @@ -98,10 +100,14 @@ def get_server_facing_ports(db): class PfcwdCli(object): - def __init__(self, namespace=None, display=constants.DISPLAY_ALL): + def __init__( + self, db=None, namespace=None, display=constants.DISPLAY_ALL + ): self.db = None self.config_db = None - self.multi_asic = multi_asic_util.MultiAsic(display, namespace) + self.multi_asic = multi_asic_util.MultiAsic( + display, namespace, db + ) self.table = [] self.all_ports = [] @@ -397,6 +403,7 @@ def big_red_switch(self, big_red_switch): pfcwd_info ) + # Show stats class Show(object): # Show commands @@ -408,19 +415,21 @@ def show(): @multi_asic_util.multi_asic_click_options @click.option('-e', '--empty', is_flag=True) @click.argument('queues', nargs=-1) - def stats(namespace, display, empty, queues): + @clicommon.pass_db + def stats(db, namespace, display, empty, queues): """ Show PFC Watchdog stats per queue """ if (len(queues)): display = constants.DISPLAY_ALL - PfcwdCli(namespace, display).show_stats(empty, queues) + PfcwdCli(db, namespace, display).show_stats(empty, queues) # Show config @show.command() @multi_asic_util.multi_asic_click_options @click.argument('ports', nargs=-1) - def config(namespace, display, ports): + @clicommon.pass_db + def config(db, namespace, display, ports): """ Show PFC Watchdog configuration """ - PfcwdCli(namespace, display).config(ports) + PfcwdCli(db, namespace, display).config(ports) # Start WD @@ -432,7 +441,8 @@ class Start(object): @click.option('--restoration-time', '-r', type=click.IntRange(100, 60000)) @click.argument('ports', nargs=-1) @click.argument('detection-time', type=click.IntRange(100, 5000)) - def start(action, restoration_time, ports, detection_time): + @clicommon.pass_db + def start(db, action, restoration_time, ports, detection_time): """ Start PFC watchdog on port(s). To config all ports, use all as input. @@ -441,51 +451,58 @@ def start(action, restoration_time, ports, detection_time): sudo pfcwd start --action drop ports all detection-time 400 --restoration-time 400 """ - PfcwdCli().start(action, restoration_time, ports, detection_time) + PfcwdCli(db).start( + action, restoration_time, ports, detection_time + ) # Set WD poll interval class Interval(object): @cli.command() @click.argument('poll_interval', type=click.IntRange(100, 3000)) - def interval(poll_interval): + @clicommon.pass_db + def interval(db, poll_interval): """ Set PFC watchdog counter polling interval """ - PfcwdCli().interval(poll_interval) + PfcwdCli(db).interval(poll_interval) # Stop WD class Stop(object): @cli.command() @click.argument('ports', nargs=-1) - def stop(ports): + @clicommon.pass_db + def stop(db, ports): """ Stop PFC watchdog on port(s) """ - PfcwdCli().stop(ports) + PfcwdCli(db).stop(ports) # Set WD default configuration on server facing ports when enable flag is on class StartDefault(object): @cli.command("start_default") - def start_default(): + @clicommon.pass_db + def start_default(db): """ Start PFC WD by default configurations """ - PfcwdCli().start_default() + PfcwdCli(db).start_default() # Enable/disable PFC WD counter polling class CounterPoll(object): @cli.command('counter_poll') @click.argument('counter_poll', type=click.Choice(['enable', 'disable'])) - def counter_poll(counter_poll): + @clicommon.pass_db + def counter_poll(db, counter_poll): """ Enable/disable counter polling """ - PfcwdCli().counter_poll(counter_poll) + PfcwdCli(db).counter_poll(counter_poll) # Enable/disable PFC WD BIG_RED_SWITCH mode class BigRedSwitch(object): @cli.command('big_red_switch') @click.argument('big_red_switch', type=click.Choice(['enable', 'disable'])) - def big_red_switch(big_red_switch): + @clicommon.pass_db + def big_red_switch(db, big_red_switch): """ Enable/disable BIG_RED_SWITCH mode """ - PfcwdCli().big_red_switch(big_red_switch) + PfcwdCli(db).big_red_switch(big_red_switch) def get_pfcwd_clis(): diff --git a/tests/pfcwd_input/pfcwd_test_vectors.py b/tests/pfcwd_input/pfcwd_test_vectors.py index 41ad34e9d704..9b6b7488a214 100644 --- a/tests/pfcwd_input/pfcwd_test_vectors.py +++ b/tests/pfcwd_input/pfcwd_test_vectors.py @@ -7,6 +7,47 @@ Ethernet8 drop 600 600 """ +pfcwd_show_start_config_output_pass = """\ +Changed polling interval to 600ms + PORT ACTION DETECTION TIME RESTORATION TIME +--------- -------- ---------------- ------------------ +Ethernet0 forward 102 101 +Ethernet4 drop 600 600 +Ethernet8 drop 600 600 +""" + +pfcwd_show_start_action_forward_output = """\ +Changed polling interval to 600ms + PORT ACTION DETECTION TIME RESTORATION TIME +--------- -------- ---------------- ------------------ +Ethernet0 forward 302 301 +Ethernet4 forward 302 301 +Ethernet8 forward 302 301 +""" + +pfcwd_show_start_action_alert_output = """\ +Changed polling interval to 600ms + PORT ACTION DETECTION TIME RESTORATION TIME +--------- -------- ---------------- ------------------ +Ethernet0 alert 502 501 +Ethernet4 alert 502 501 +Ethernet8 alert 502 501 +""" + +pfcwd_show_start_action_drop_output = """\ +Changed polling interval to 600ms + PORT ACTION DETECTION TIME RESTORATION TIME +--------- -------- ---------------- ------------------ +Ethernet0 drop 602 601 +Ethernet4 drop 602 601 +Ethernet8 drop 602 601 +""" + +pfcwd_show_start_config_output_fail = """\ +Failed to run command, invalid options: +Ethernet1000 +""" + pfcwd_show_config_single_port_output="""\ Changed polling interval to 600ms PORT ACTION DETECTION TIME RESTORATION TIME @@ -222,6 +263,71 @@ Ethernet-BP260 drop 200 200 """ +show_pfc_config_start_pass = """\ +Changed polling interval to 199ms on asic0 +BIG_RED_SWITCH status is enable on asic0 +Changed polling interval to 199ms on asic1 +BIG_RED_SWITCH status is enable on asic1 + PORT ACTION DETECTION TIME RESTORATION TIME +-------------- -------- ---------------- ------------------ + Ethernet0 forward 102 101 + Ethernet4 drop 200 200 + Ethernet-BP0 drop 200 200 + Ethernet-BP4 forward 102 101 +Ethernet-BP256 drop 200 200 +Ethernet-BP260 drop 200 200 +""" + +show_pfc_config_start_action_drop_masic = """\ +Changed polling interval to 199ms on asic0 +BIG_RED_SWITCH status is enable on asic0 +Changed polling interval to 199ms on asic1 +BIG_RED_SWITCH status is enable on asic1 + PORT ACTION DETECTION TIME RESTORATION TIME +-------------- -------- ---------------- ------------------ + Ethernet0 drop 302 301 + Ethernet4 drop 302 301 + Ethernet-BP0 drop 302 301 + Ethernet-BP4 drop 302 301 +Ethernet-BP256 drop 302 301 +Ethernet-BP260 drop 302 301 +""" + +show_pfc_config_start_action_alert_masic = """\ +Changed polling interval to 199ms on asic0 +BIG_RED_SWITCH status is enable on asic0 +Changed polling interval to 199ms on asic1 +BIG_RED_SWITCH status is enable on asic1 + PORT ACTION DETECTION TIME RESTORATION TIME +-------------- -------- ---------------- ------------------ + Ethernet0 alert 402 401 + Ethernet4 alert 402 401 + Ethernet-BP0 alert 402 401 + Ethernet-BP4 alert 402 401 +Ethernet-BP256 alert 402 401 +Ethernet-BP260 alert 402 401 +""" + +show_pfc_config_start_action_forward_masic = """\ +Changed polling interval to 199ms on asic0 +BIG_RED_SWITCH status is enable on asic0 +Changed polling interval to 199ms on asic1 +BIG_RED_SWITCH status is enable on asic1 + PORT ACTION DETECTION TIME RESTORATION TIME +-------------- -------- ---------------- ------------------ + Ethernet0 forward 702 701 + Ethernet4 forward 702 701 + Ethernet-BP0 forward 702 701 + Ethernet-BP4 forward 702 701 +Ethernet-BP256 forward 702 701 +Ethernet-BP260 forward 702 701 +""" + +show_pfc_config_start_fail = """\ +Failed to run command, invalid options: +Ethernet-500 +""" + show_pfcwd_stats_with_queues = """\ QUEUE STATUS STORM DETECTED/RESTORED TX OK/DROP RX OK/DROP TX LAST OK/DROP RX LAST OK/DROP ----------------- -------- ------------------------- ------------ ------------ ----------------- ----------------- diff --git a/tests/pfcwd_test.py b/tests/pfcwd_test.py index e96d5b7e0c30..afbd1d0a7483 100644 --- a/tests/pfcwd_test.py +++ b/tests/pfcwd_test.py @@ -5,10 +5,7 @@ from click.testing import CliRunner from utilities_common.db import Db -from .pfcwd_input.pfcwd_test_vectors import ( - testData, show_pfcwd_stats_all, show_pfc_config_all, - show_pfcwd_stats_with_queues, show_pfcwd_config_with_ports -) +from .pfcwd_input.pfcwd_test_vectors import * test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) @@ -61,7 +58,9 @@ def executor(self, testcase): exec_cmd = pfcwd.cli.commands[input['cmd'][0]].commands[input['cmd'][1]] if 'db' in input and input['db']: - result = runner.invoke(exec_cmd, input['args'], obj=db) + result = runner.invoke( + exec_cmd, input['args'], obj=db + ) else: result = runner.invoke(exec_cmd, input['args']) @@ -79,6 +78,132 @@ def executor(self, testcase): if 'rc_output' in input: assert result.output == input['rc_output'] + def test_pfcwd_start_ports_valid(self): + # pfcwd start --action drop --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db = Db() + + # get initial config + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.output == pfcwd_show_config_output + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "101", + "Ethernet0", "102" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == pfcwd_show_start_config_output_pass + + def test_pfcwd_start_actions(self): + # pfcwd start --action fwd --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db = Db() + + # get initial config + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.output == pfcwd_show_config_output + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "301", + "all", "302" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == pfcwd_show_start_action_forward_output + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "alert", "--restoration-time", "501", + "all", "502" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == pfcwd_show_start_action_alert_output + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "drop", "--restoration-time", "601", + "all", "602" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == pfcwd_show_start_action_drop_output + + def test_pfcwd_start_ports_invalid(self): + # pfcwd start --action drop --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db = Db() + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "101", + "Ethernet1000", "102" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == pfcwd_show_start_config_output_fail + @classmethod def teardown_class(cls): os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) @@ -142,6 +267,140 @@ def test_pfcwd_config_with_ports(self): assert result.exit_code == 0 assert result.output == show_pfcwd_config_with_ports + def test_pfcwd_start_ports_masic_valid(self): + # pfcwd start --action forward --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db = Db() + # get initial config + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.output == show_pfc_config_all + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "101", + "Ethernet0", "Ethernet-BP4", "102" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_pass + + def test_pfcwd_start_actions_masic(self): + # pfcwd start --action drop --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db = Db() + # get initial config + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.output == show_pfc_config_all + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "drop", "--restoration-time", "301", + "all", "302" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_action_drop_masic + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "alert", "--restoration-time", "401", + "all", "402" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_action_alert_masic + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "701", + "all", "702" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_action_forward_masic + + def test_pfcwd_start_ports_masic_invalid(self): + # --action drop --restoration-time 200 Ethernet0 Ethernet500 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db = Db() + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "101", + "Ethernet0", "Ethernet-500", "102" + ], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_fail + + # get config after the command, config shouldn't change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj=db + ) + print(result.output) + assert result.exit_code == 0 + # same as original config + assert result.output == show_pfc_config_all + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/utilities_common/db.py b/utilities_common/db.py index bab45d7969a8..f6b4189190c4 100644 --- a/utilities_common/db.py +++ b/utilities_common/db.py @@ -1,13 +1,29 @@ +from sonic_py_common import multi_asic from swsssdk import ConfigDBConnector, SonicV2Connector +from utilities_common import constants +from utilities_common.multi_asic import multi_asic_ns_choices + class Db(object): def __init__(self): + self.cfgdb_clients = {} + self.db_clients = {} self.cfgdb = ConfigDBConnector() self.cfgdb.connect() self.db = SonicV2Connector(host="127.0.0.1") - self.db.connect(self.db.APPL_DB) - self.db.connect(self.db.CONFIG_DB) - self.db.connect(self.db.STATE_DB) + for db_id in self.db.get_db_list(): + self.db.connect(db_id) + + self.cfgdb_clients[constants.DEFAULT_NAMESPACE] = self.cfgdb + self.db_clients[constants.DEFAULT_NAMESPACE] = self.db + + if multi_asic.is_multi_asic(): + self.ns_list = multi_asic_ns_choices() + for ns in self.ns_list: + self.cfgdb_clients[ns] = ( + multi_asic.connect_config_db_for_ns(ns) + ) + self.db_clients[ns] = multi_asic.connect_to_all_dbs_for_ns(ns) def get_data(self, table, key): data = self.cfgdb.get_table(table) diff --git a/utilities_common/multi_asic.py b/utilities_common/multi_asic.py index 99096bb0b748..51a6a18b65a7 100644 --- a/utilities_common/multi_asic.py +++ b/utilities_common/multi_asic.py @@ -8,17 +8,20 @@ class MultiAsic(object): - def __init__(self, display_option=constants.DISPLAY_ALL, - namespace_option=None): + def __init__( + self, display_option=constants.DISPLAY_ALL, namespace_option=None, + db=None + ): self.namespace_option = namespace_option self.display_option = display_option self.current_namespace = None self.is_multi_asic = multi_asic.is_multi_asic() + self.db = db def is_object_internal(self, object_type, cli_object): ''' The function checks if a CLI object is internal and returns true or false. - Internal objects are port or portchannel which are connected to other + Internal objects are port or portchannel which are connected to other ports or portchannels within a multi ASIC device. For single asic, this function is not applicable @@ -120,8 +123,17 @@ def wrapped_run_on_all_asics(self, *args, **kwargs): ns_list = self.multi_asic.get_ns_list_based_on_options() for ns in ns_list: self.multi_asic.current_namespace = ns - self.db = multi_asic.connect_to_all_dbs_for_ns(ns) - self.config_db = multi_asic.connect_config_db_for_ns(ns) + # if object instance already has db connections, use them + if self.multi_asic.db and self.multi_asic.db.cfgdb_clients.get(ns): + self.config_db = self.multi_asic.db.cfgdb_clients[ns] + else: + self.config_db = multi_asic.connect_config_db_for_ns(ns) + + if self.multi_asic.db and self.multi_asic.db.db_clients.get(ns): + self.db = self.multi_asic.db.db_clients[ns] + else: + self.db = multi_asic.connect_to_all_dbs_for_ns(ns) + func(self, *args, **kwargs) return wrapped_run_on_all_asics