From 149bc69959c07dbb09c16ecba3e07342ca0d9457 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Tue, 17 Nov 2020 23:37:01 +0000 Subject: [PATCH 1/7] add pfcwd config unit tests, update CLI to take Db object as a paramter --- pfcwd/main.py | 54 ++++++---- tests/pfcwd_input/pfcwd_test_vectors.py | 34 +++++++ tests/pfcwd_test.py | 126 ++++++++++++++++++++++-- utilities_common/db.py | 18 ++++ utilities_common/multi_asic.py | 21 +++- 5 files changed, 223 insertions(+), 30 deletions(-) diff --git a/pfcwd/main.py b/pfcwd/main.py index 6c96c3b749..ecfb50b07d 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -11,6 +11,7 @@ from utilities_common import multi_asic as multi_asic_util from utilities_common import constants + # mock the redis for unit test purposes # try: if os.environ["UTILITIES_UNIT_TESTING"] == "2": @@ -55,7 +56,8 @@ # Main entrypoint @click.group() -def cli(): +@click.pass_context +def cli(ctx): """ SONiC PFC Watchdog """ @@ -100,12 +102,14 @@ def get_server_facing_ports(db): class PfcwdCli(object): - def __init__(self, namespace=None, display=constants.DISPLAY_ALL): + def __init__(self, ctx, namespace=None, display=constants.DISPLAY_ALL): self.db = None self.config_db = None self.multi_asic = multi_asic_util.MultiAsic(display, namespace) self.table = [] self.all_ports = [] + if ctx.obj and isinstance(ctx.obj, dict): + self.db_clients = ctx.obj.get("db_clients", None) @multi_asic_util.run_on_multi_asic def collect_stats(self, empty, queues): @@ -403,26 +407,29 @@ def big_red_switch(self, big_red_switch): class Show(object): # Show commands @cli.group() - def show(): + @click.pass_context + def show(ctx): """ Show PFC Watchdog information""" @show.command() @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): + @click.pass_context + def stats(ctx, 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(ctx, 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): + @click.pass_context + def config(ctx, namespace, display, ports): """ Show PFC Watchdog configuration """ - PfcwdCli(namespace, display).config(ports) + PfcwdCli(ctx, namespace, display).config(ports) # Start WD @@ -434,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): + @click.pass_context + def start(ctx, action, restoration_time, ports, detection_time): """ Start PFC watchdog on port(s). To config all ports, use all as input. @@ -443,51 +451,57 @@ 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(ctx).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): + @click.pass_context + def interval(ctx, poll_interval): """ Set PFC watchdog counter polling interval """ - PfcwdCli().interval(poll_interval) + PfcwdCli(ctx).interval(poll_interval) # Stop WD class Stop(object): @cli.command() @click.argument('ports', nargs=-1) - def stop(ports): + @click.pass_context + def stop(ctx, ports): """ Stop PFC watchdog on port(s) """ - PfcwdCli().stop(ports) + PfcwdCli(ctx).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(): + @click.pass_context + def start_default(ctx): """ Start PFC WD by default configurations """ - PfcwdCli().start_default() + PfcwdCli(ctx).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): + @click.pass_context + def counter_poll(ctx, counter_poll): """ Enable/disable counter polling """ - PfcwdCli().counter_poll(counter_poll) + PfcwdCli(ctx).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): + @click.pass_context + def big_red_switch(ctx, big_red_switch): """ Enable/disable BIG_RED_SWITCH mode """ - PfcwdCli().big_red_switch(big_red_switch) + PfcwdCli(ctx).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 41ad34e9d7..f27aae6b91 100644 --- a/tests/pfcwd_input/pfcwd_test_vectors.py +++ b/tests/pfcwd_input/pfcwd_test_vectors.py @@ -7,6 +7,20 @@ 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_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 +236,26 @@ 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_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 4fdafc9aee..23c030480f 100644 --- a/tests/pfcwd_test.py +++ b/tests/pfcwd_test.py @@ -3,12 +3,9 @@ import sys from click.testing import CliRunner -from utilities_common.db import Db +from utilities_common.db import MasicDb -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) @@ -51,7 +48,7 @@ def test_pfcwd_show_stats_invalid_queue(self): def executor(self, testcase): import pfcwd.main as pfcwd runner = CliRunner() - db = Db() + db_clients = MasicDb() for input in testcase: exec_cmd = "" @@ -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_clients": db_clients} + ) else: result = runner.invoke(exec_cmd, input['args']) @@ -79,6 +78,58 @@ 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_clients = MasicDb() + + # get initial config + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj={"db_clients": db_clients} + ) + 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_clients": db_clients} + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj={"db_clients": db_clients} + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == pfcwd_show_start_config_output_pass + + def test_pfcwd_start_ports_invalid(self): + # pfcwd start --action drop --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db_clients = MasicDb() + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "101", + "Ethernet1000", "102" + ], + obj={"db_clients": db_clients} + ) + 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 +193,67 @@ 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 drop --restoration-time 200 Ethernet0 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db_clients = MasicDb() + # get initial config + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj={"db_clients": db_clients} + ) + 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_clients": db_clients} + ) + print(result.output) + assert result.exit_code == 0 + + # get config after the change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj={"db_clients": db_clients} + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_pass + + def test_pfcwd_start_ports_masic_invalid(self): + # --action drop --restoration-time 200 Ethernet0 Ethernet500 200 + import pfcwd.main as pfcwd + runner = CliRunner() + db_clients = MasicDb() + + result = runner.invoke( + pfcwd.cli.commands["start"], + [ + "--action", "forward", "--restoration-time", "101", + "Ethernet0", "Ethernet-500", "102" + ], + obj={"db_clients": db_clients} + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_pfc_config_start_fail + + # get config after the command, config shoudln't change + result = runner.invoke( + pfcwd.cli.commands["show"].commands["config"], + obj={"db_clients": db_clients} + ) + 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 bab45d7969..d3c2dd6b32 100644 --- a/utilities_common/db.py +++ b/utilities_common/db.py @@ -1,4 +1,7 @@ +from sonic_py_common import multi_asic from swsssdk import ConfigDBConnector, SonicV2Connector +from utilities_common.multi_asic import multi_asic_ns_choices + class Db(object): def __init__(self): @@ -12,3 +15,18 @@ def __init__(self): def get_data(self, table, key): data = self.cfgdb.get_table(table) return data[key] if key in data else None + + +class MasicDb(object): + """ + Multi ASIC DB object - creates config DB and other redis DB client + connections for all namespaces. In case of single ASIC platform, DB + client connections are created for default namespace + """" + def __init__(self): + self.ns_list = multi_asic_ns_choices() + self.config_db_clients = {} + self.db_clients = {} + for ns in self.ns_list: + self.config_db_clients[ns] = multi_asic.connect_config_db_for_ns(ns) + self.db_clients[ns] = multi_asic.connect_to_all_dbs_for_ns(ns) diff --git a/utilities_common/multi_asic.py b/utilities_common/multi_asic.py index 99096bb0b7..2eb7c37e21 100644 --- a/utilities_common/multi_asic.py +++ b/utilities_common/multi_asic.py @@ -18,7 +18,7 @@ def __init__(self, display_option=constants.DISPLAY_ALL, 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 +120,23 @@ 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 (hasattr(self, 'db_clients') and + self.db_clients and + ns in self.db_clients.config_db_clients and + self.db_clients.config_db_clients[ns]): + self.config_db = self.db_clients.config_db_clients[ns] + else: + self.config_db = multi_asic.connect_config_db_for_ns(ns) + + if (hasattr(self, 'db_clients') and + self.db_clients and + ns in self.db_clients.db_clients and + self.db_clients.db_clients[ns]): + self.db = self.db_clients.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 From e06d68bf9a0a7cb7c5e1216a825bb904029f63a6 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Wed, 18 Nov 2020 00:10:35 +0000 Subject: [PATCH 2/7] fix typo --- pfcwd/main.py | 1 - utilities_common/db.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pfcwd/main.py b/pfcwd/main.py index d81a517bed..1968794965 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -9,7 +9,6 @@ from utilities_common import multi_asic as multi_asic_util from utilities_common import constants - # mock the redis for unit test purposes # try: if os.environ["UTILITIES_UNIT_TESTING"] == "2": diff --git a/utilities_common/db.py b/utilities_common/db.py index d3c2dd6b32..15baa0c775 100644 --- a/utilities_common/db.py +++ b/utilities_common/db.py @@ -22,7 +22,7 @@ class MasicDb(object): Multi ASIC DB object - creates config DB and other redis DB client connections for all namespaces. In case of single ASIC platform, DB client connections are created for default namespace - """" + """ def __init__(self): self.ns_list = multi_asic_ns_choices() self.config_db_clients = {} From cc8d1d03e8d55af897d0e5576ca09a50fd8e69b9 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Sat, 5 Dec 2020 02:25:35 +0000 Subject: [PATCH 3/7] using Db object instead of creating MasicDb --- pfcwd/main.py | 62 +++++++++++++++++++++------------- tests/pfcwd_test.py | 34 +++++++++---------- utilities_common/db.py | 34 +++++++++---------- utilities_common/multi_asic.py | 21 +++++------- 4 files changed, 80 insertions(+), 71 deletions(-) diff --git a/pfcwd/main.py b/pfcwd/main.py index b2af79aa47..6a9bf358ba 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 @@ -53,8 +55,7 @@ # Main entrypoint @click.group() -@click.pass_context -def cli(ctx): +def cli(): """ SONiC PFC Watchdog """ @@ -99,14 +100,18 @@ def get_server_facing_ports(db): class PfcwdCli(object): - def __init__(self, ctx, namespace=None, display=constants.DISPLAY_ALL): + def __init__( + self, ctx, db=None, namespace=None, display=constants.DISPLAY_ALL + ): self.db = None self.config_db = None - self.multi_asic = multi_asic_util.MultiAsic(display, namespace) + # ctx.obj contains the Db object set by clicommon.pass_db decorator + # Unit tests over write the ctx.obj with test Db object, using ctx.obj + self.multi_asic = multi_asic_util.MultiAsic( + display, namespace, ctx.obj + ) self.table = [] self.all_ports = [] - if ctx.obj and isinstance(ctx.obj, dict): - self.db_clients = ctx.obj.get("db_clients", None) @multi_asic_util.run_on_multi_asic def collect_stats(self, empty, queues): @@ -400,33 +405,35 @@ def big_red_switch(self, big_red_switch): pfcwd_info ) + # Show stats class Show(object): # Show commands @cli.group() - @click.pass_context - def show(ctx): + def show(): """ Show PFC Watchdog information""" @show.command() @multi_asic_util.multi_asic_click_options @click.option('-e', '--empty', is_flag=True) @click.argument('queues', nargs=-1) + @clicommon.pass_db @click.pass_context - def stats(ctx, namespace, display, empty, queues): + def stats(ctx, db, namespace, display, empty, queues): """ Show PFC Watchdog stats per queue """ if (len(queues)): display = constants.DISPLAY_ALL - PfcwdCli(ctx, namespace, display).show_stats(empty, queues) + PfcwdCli(ctx, db, namespace, display).show_stats(empty, queues) # Show config @show.command() @multi_asic_util.multi_asic_click_options @click.argument('ports', nargs=-1) + @clicommon.pass_db @click.pass_context - def config(ctx, namespace, display, ports): + def config(ctx, db, namespace, display, ports): """ Show PFC Watchdog configuration """ - PfcwdCli(ctx, namespace, display).config(ports) + PfcwdCli(ctx, db, namespace, display).config(ports) # Start WD @@ -438,8 +445,9 @@ 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)) + @clicommon.pass_db @click.pass_context - def start(ctx, action, restoration_time, ports, detection_time): + def start(ctx, db, action, restoration_time, ports, detection_time): """ Start PFC watchdog on port(s). To config all ports, use all as input. @@ -448,57 +456,63 @@ def start(ctx, action, restoration_time, ports, detection_time): sudo pfcwd start --action drop ports all detection-time 400 --restoration-time 400 """ - PfcwdCli(ctx).start( + PfcwdCli(ctx, 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)) + @clicommon.pass_db @click.pass_context - def interval(ctx, poll_interval): + def interval(ctx, db, poll_interval): """ Set PFC watchdog counter polling interval """ - PfcwdCli(ctx).interval(poll_interval) + PfcwdCli(ctx, db).interval(poll_interval) # Stop WD class Stop(object): @cli.command() @click.argument('ports', nargs=-1) + @clicommon.pass_db @click.pass_context - def stop(ctx, ports): + def stop(ctx, db, ports): """ Stop PFC watchdog on port(s) """ - PfcwdCli(ctx).stop(ports) + PfcwdCli(ctx, db).stop(ports) # Set WD default configuration on server facing ports when enable flag is on class StartDefault(object): @cli.command("start_default") + @clicommon.pass_db @click.pass_context - def start_default(ctx): + def start_default(ctx, db): """ Start PFC WD by default configurations """ - PfcwdCli(ctx).start_default() + PfcwdCli(ctx, 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'])) + @clicommon.pass_db @click.pass_context - def counter_poll(ctx, counter_poll): + def counter_poll(ctx, db, counter_poll): """ Enable/disable counter polling """ - PfcwdCli(ctx).counter_poll(counter_poll) + PfcwdCli(ctx, 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'])) + @clicommon.pass_db @click.pass_context - def big_red_switch(ctx, big_red_switch): + def big_red_switch(ctx, db, big_red_switch): """ Enable/disable BIG_RED_SWITCH mode """ - PfcwdCli(ctx).big_red_switch(big_red_switch) + PfcwdCli(ctx, db).big_red_switch(big_red_switch) def get_pfcwd_clis(): diff --git a/tests/pfcwd_test.py b/tests/pfcwd_test.py index 23c030480f..b99efe4d97 100644 --- a/tests/pfcwd_test.py +++ b/tests/pfcwd_test.py @@ -3,9 +3,9 @@ import sys from click.testing import CliRunner -from utilities_common.db import MasicDb +from utilities_common.db import Db -from pfcwd_input.pfcwd_test_vectors import * +from .pfcwd_input.pfcwd_test_vectors import * test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) @@ -48,7 +48,7 @@ def test_pfcwd_show_stats_invalid_queue(self): def executor(self, testcase): import pfcwd.main as pfcwd runner = CliRunner() - db_clients = MasicDb() + db_clients = Db() for input in testcase: exec_cmd = "" @@ -59,7 +59,7 @@ def executor(self, testcase): if 'db' in input and input['db']: result = runner.invoke( - exec_cmd, input['args'], obj={"db_clients": db_clients} + exec_cmd, input['args'], obj=db_clients ) else: result = runner.invoke(exec_cmd, input['args']) @@ -82,12 +82,12 @@ def test_pfcwd_start_ports_valid(self): # pfcwd start --action drop --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = MasicDb() + db_clients = Db() # get initial config result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.output == pfcwd_show_config_output @@ -98,7 +98,7 @@ def test_pfcwd_start_ports_valid(self): "--action", "forward", "--restoration-time", "101", "Ethernet0", "102" ], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 @@ -106,7 +106,7 @@ def test_pfcwd_start_ports_valid(self): # get config after the change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 @@ -116,7 +116,7 @@ def test_pfcwd_start_ports_invalid(self): # pfcwd start --action drop --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = MasicDb() + db_clients = Db() result = runner.invoke( pfcwd.cli.commands["start"], @@ -124,7 +124,7 @@ def test_pfcwd_start_ports_invalid(self): "--action", "forward", "--restoration-time", "101", "Ethernet1000", "102" ], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 @@ -197,11 +197,11 @@ def test_pfcwd_start_ports_masic_valid(self): # pfcwd start --action drop --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = MasicDb() + db_clients = Db() # get initial config result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.output == show_pfc_config_all @@ -212,7 +212,7 @@ def test_pfcwd_start_ports_masic_valid(self): "--action", "forward", "--restoration-time", "101", "Ethernet0", "Ethernet-BP4", "102" ], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 @@ -220,7 +220,7 @@ def test_pfcwd_start_ports_masic_valid(self): # get config after the change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 @@ -230,7 +230,7 @@ def test_pfcwd_start_ports_masic_invalid(self): # --action drop --restoration-time 200 Ethernet0 Ethernet500 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = MasicDb() + db_clients = Db() result = runner.invoke( pfcwd.cli.commands["start"], @@ -238,7 +238,7 @@ def test_pfcwd_start_ports_masic_invalid(self): "--action", "forward", "--restoration-time", "101", "Ethernet0", "Ethernet-500", "102" ], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 @@ -247,7 +247,7 @@ def test_pfcwd_start_ports_masic_invalid(self): # get config after the command, config shoudln't change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj={"db_clients": db_clients} + obj=db_clients ) print(result.output) assert result.exit_code == 0 diff --git a/utilities_common/db.py b/utilities_common/db.py index 15baa0c775..f6b4189190 100644 --- a/utilities_common/db.py +++ b/utilities_common/db.py @@ -1,32 +1,30 @@ 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) return data[key] if key in data else None - - -class MasicDb(object): - """ - Multi ASIC DB object - creates config DB and other redis DB client - connections for all namespaces. In case of single ASIC platform, DB - client connections are created for default namespace - """ - def __init__(self): - self.ns_list = multi_asic_ns_choices() - self.config_db_clients = {} - self.db_clients = {} - for ns in self.ns_list: - self.config_db_clients[ns] = multi_asic.connect_config_db_for_ns(ns) - self.db_clients[ns] = multi_asic.connect_to_all_dbs_for_ns(ns) diff --git a/utilities_common/multi_asic.py b/utilities_common/multi_asic.py index 2eb7c37e21..51a6a18b65 100644 --- a/utilities_common/multi_asic.py +++ b/utilities_common/multi_asic.py @@ -8,12 +8,15 @@ 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): ''' @@ -121,19 +124,13 @@ def wrapped_run_on_all_asics(self, *args, **kwargs): for ns in ns_list: self.multi_asic.current_namespace = ns # if object instance already has db connections, use them - if (hasattr(self, 'db_clients') and - self.db_clients and - ns in self.db_clients.config_db_clients and - self.db_clients.config_db_clients[ns]): - self.config_db = self.db_clients.config_db_clients[ns] + 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 (hasattr(self, 'db_clients') and - self.db_clients and - ns in self.db_clients.db_clients and - self.db_clients.db_clients[ns]): - self.db = self.db_clients.db_clients[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) From 37ec9383f19e62b4e4ca180b7df657b009413535 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Sat, 5 Dec 2020 02:32:52 +0000 Subject: [PATCH 4/7] variable name change --- tests/pfcwd_test.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/pfcwd_test.py b/tests/pfcwd_test.py index b99efe4d97..d7adc439ca 100644 --- a/tests/pfcwd_test.py +++ b/tests/pfcwd_test.py @@ -48,7 +48,7 @@ def test_pfcwd_show_stats_invalid_queue(self): def executor(self, testcase): import pfcwd.main as pfcwd runner = CliRunner() - db_clients = Db() + db = Db() for input in testcase: exec_cmd = "" @@ -59,7 +59,7 @@ def executor(self, testcase): if 'db' in input and input['db']: result = runner.invoke( - exec_cmd, input['args'], obj=db_clients + exec_cmd, input['args'], obj=db ) else: result = runner.invoke(exec_cmd, input['args']) @@ -82,12 +82,12 @@ def test_pfcwd_start_ports_valid(self): # pfcwd start --action drop --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = Db() + db = Db() # get initial config result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj=db_clients + obj=db ) print(result.output) assert result.output == pfcwd_show_config_output @@ -98,7 +98,7 @@ def test_pfcwd_start_ports_valid(self): "--action", "forward", "--restoration-time", "101", "Ethernet0", "102" ], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 @@ -106,7 +106,7 @@ def test_pfcwd_start_ports_valid(self): # get config after the change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 @@ -116,7 +116,7 @@ def test_pfcwd_start_ports_invalid(self): # pfcwd start --action drop --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = Db() + db = Db() result = runner.invoke( pfcwd.cli.commands["start"], @@ -124,7 +124,7 @@ def test_pfcwd_start_ports_invalid(self): "--action", "forward", "--restoration-time", "101", "Ethernet1000", "102" ], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 @@ -197,11 +197,11 @@ def test_pfcwd_start_ports_masic_valid(self): # pfcwd start --action drop --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = Db() + db = Db() # get initial config result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj=db_clients + obj=db ) print(result.output) assert result.output == show_pfc_config_all @@ -212,7 +212,7 @@ def test_pfcwd_start_ports_masic_valid(self): "--action", "forward", "--restoration-time", "101", "Ethernet0", "Ethernet-BP4", "102" ], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 @@ -220,7 +220,7 @@ def test_pfcwd_start_ports_masic_valid(self): # get config after the change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 @@ -230,7 +230,7 @@ def test_pfcwd_start_ports_masic_invalid(self): # --action drop --restoration-time 200 Ethernet0 Ethernet500 200 import pfcwd.main as pfcwd runner = CliRunner() - db_clients = Db() + db = Db() result = runner.invoke( pfcwd.cli.commands["start"], @@ -238,7 +238,7 @@ def test_pfcwd_start_ports_masic_invalid(self): "--action", "forward", "--restoration-time", "101", "Ethernet0", "Ethernet-500", "102" ], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 @@ -247,7 +247,7 @@ def test_pfcwd_start_ports_masic_invalid(self): # get config after the command, config shoudln't change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], - obj=db_clients + obj=db ) print(result.output) assert result.exit_code == 0 From d4f29602b0b9b887b97a94ddd4fac09c42da3bc5 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Mon, 7 Dec 2020 16:20:23 +0000 Subject: [PATCH 5/7] no need for ctx, db variable contains right value during unit tests --- pfcwd/main.py | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/pfcwd/main.py b/pfcwd/main.py index 6a9bf358ba..c55038b9b2 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -101,14 +101,12 @@ def get_server_facing_ports(db): class PfcwdCli(object): def __init__( - self, ctx, db=None, namespace=None, display=constants.DISPLAY_ALL + self, db=None, namespace=None, display=constants.DISPLAY_ALL ): self.db = None self.config_db = None - # ctx.obj contains the Db object set by clicommon.pass_db decorator - # Unit tests over write the ctx.obj with test Db object, using ctx.obj self.multi_asic = multi_asic_util.MultiAsic( - display, namespace, ctx.obj + display, namespace, db ) self.table = [] self.all_ports = [] @@ -418,22 +416,20 @@ def show(): @click.option('-e', '--empty', is_flag=True) @click.argument('queues', nargs=-1) @clicommon.pass_db - @click.pass_context - def stats(ctx, db, namespace, display, empty, queues): + def stats(db, namespace, display, empty, queues): """ Show PFC Watchdog stats per queue """ if (len(queues)): display = constants.DISPLAY_ALL - PfcwdCli(ctx, db, 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) @clicommon.pass_db - @click.pass_context - def config(ctx, db, namespace, display, ports): + def config(db, namespace, display, ports): """ Show PFC Watchdog configuration """ - PfcwdCli(ctx, db, namespace, display).config(ports) + PfcwdCli(db, namespace, display).config(ports) # Start WD @@ -446,8 +442,7 @@ class Start(object): @click.argument('ports', nargs=-1) @click.argument('detection-time', type=click.IntRange(100, 5000)) @clicommon.pass_db - @click.pass_context - def start(ctx, db, action, restoration_time, ports, detection_time): + def start(db, action, restoration_time, ports, detection_time): """ Start PFC watchdog on port(s). To config all ports, use all as input. @@ -456,7 +451,7 @@ def start(ctx, db, action, restoration_time, ports, detection_time): sudo pfcwd start --action drop ports all detection-time 400 --restoration-time 400 """ - PfcwdCli(ctx, db).start( + PfcwdCli(db).start( action, restoration_time, ports, detection_time ) @@ -466,10 +461,9 @@ class Interval(object): @cli.command() @click.argument('poll_interval', type=click.IntRange(100, 3000)) @clicommon.pass_db - @click.pass_context - def interval(ctx, db, poll_interval): + def interval(db, poll_interval): """ Set PFC watchdog counter polling interval """ - PfcwdCli(ctx, db).interval(poll_interval) + PfcwdCli(db).interval(poll_interval) # Stop WD @@ -477,20 +471,18 @@ class Stop(object): @cli.command() @click.argument('ports', nargs=-1) @clicommon.pass_db - @click.pass_context - def stop(ctx, db, ports): + def stop(db, ports): """ Stop PFC watchdog on port(s) """ - PfcwdCli(ctx, db).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") @clicommon.pass_db - @click.pass_context - def start_default(ctx, db): + def start_default(db): """ Start PFC WD by default configurations """ - PfcwdCli(ctx, db).start_default() + PfcwdCli(db).start_default() # Enable/disable PFC WD counter polling @@ -498,10 +490,9 @@ class CounterPoll(object): @cli.command('counter_poll') @click.argument('counter_poll', type=click.Choice(['enable', 'disable'])) @clicommon.pass_db - @click.pass_context - def counter_poll(ctx, db, counter_poll): + def counter_poll(db, counter_poll): """ Enable/disable counter polling """ - PfcwdCli(ctx, db).counter_poll(counter_poll) + PfcwdCli(db).counter_poll(counter_poll) # Enable/disable PFC WD BIG_RED_SWITCH mode @@ -509,10 +500,9 @@ class BigRedSwitch(object): @cli.command('big_red_switch') @click.argument('big_red_switch', type=click.Choice(['enable', 'disable'])) @clicommon.pass_db - @click.pass_context - def big_red_switch(ctx, db, big_red_switch): + def big_red_switch(db, big_red_switch): """ Enable/disable BIG_RED_SWITCH mode """ - PfcwdCli(ctx, db).big_red_switch(big_red_switch) + PfcwdCli(db).big_red_switch(big_red_switch) def get_pfcwd_clis(): From 2780f4891abdc468fa34b3e3002fc2f9eabf0369 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Tue, 8 Dec 2020 17:31:07 +0000 Subject: [PATCH 6/7] correct typo --- tests/pfcwd_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pfcwd_test.py b/tests/pfcwd_test.py index d7adc439ca..31bffabd28 100644 --- a/tests/pfcwd_test.py +++ b/tests/pfcwd_test.py @@ -244,7 +244,7 @@ def test_pfcwd_start_ports_masic_invalid(self): assert result.exit_code == 0 assert result.output == show_pfc_config_start_fail - # get config after the command, config shoudln't change + # get config after the command, config shouldn't change result = runner.invoke( pfcwd.cli.commands["show"].commands["config"], obj=db From 44bd19b2159be7122ac8ccfb1d628a6cec2d799d Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala Date: Tue, 8 Dec 2020 18:23:10 +0000 Subject: [PATCH 7/7] add pfcwd action test for all actions --- tests/pfcwd_input/pfcwd_test_vectors.py | 74 +++++++++++- tests/pfcwd_test.py | 149 +++++++++++++++++++++++- 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/tests/pfcwd_input/pfcwd_test_vectors.py b/tests/pfcwd_input/pfcwd_test_vectors.py index f27aae6b91..9b6b7488a2 100644 --- a/tests/pfcwd_input/pfcwd_test_vectors.py +++ b/tests/pfcwd_input/pfcwd_test_vectors.py @@ -7,7 +7,7 @@ Ethernet8 drop 600 600 """ -pfcwd_show_start_config_output_pass= """\ +pfcwd_show_start_config_output_pass = """\ Changed polling interval to 600ms PORT ACTION DETECTION TIME RESTORATION TIME --------- -------- ---------------- ------------------ @@ -16,6 +16,33 @@ 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 @@ -251,6 +278,51 @@ 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 diff --git a/tests/pfcwd_test.py b/tests/pfcwd_test.py index 31bffabd28..afbd1d0a74 100644 --- a/tests/pfcwd_test.py +++ b/tests/pfcwd_test.py @@ -112,6 +112,80 @@ def test_pfcwd_start_ports_valid(self): 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 @@ -194,7 +268,7 @@ def test_pfcwd_config_with_ports(self): assert result.output == show_pfcwd_config_with_ports def test_pfcwd_start_ports_masic_valid(self): - # pfcwd start --action drop --restoration-time 200 Ethernet0 200 + # pfcwd start --action forward --restoration-time 200 Ethernet0 200 import pfcwd.main as pfcwd runner = CliRunner() db = Db() @@ -226,6 +300,79 @@ def test_pfcwd_start_ports_masic_valid(self): 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