diff --git a/config/main.py b/config/main.py index 98e1f1d09c..e6af54268a 100644 --- a/config/main.py +++ b/config/main.py @@ -7391,6 +7391,28 @@ def install(counter_name, alias, group, counter_type, desc, reasons, verbose): clicommon.run_command(command, display_cmd=verbose) +# +# 'monitor' subcommand ('config dropcounters monitor') +# +@dropcounters.command() +@click.option("-s", "--status", type=str, help="Status can be set to enabled/disabled") +@click.option("-w", "--window", type=int, help="Window size in seconds") +@click.option("-dct", "--drop-count-threshold", type=int, help="Minimum threshold for drop counts to be classified as an incident") +@click.option('-ict', '--incident-count-threshold', type=int, help="Minimum number of incidents to trigger a syslog entry") +@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") +def monitor(status, window, drop_count_threshold, incident_count_threshold, verbose): + """Update configurations of drop counter monitor""" + command = ['dropconfig', '-c', 'config_monitor'] + if status: + command += ['-s', str(status)] + if window: + command += ['-w', str(window)] + if drop_count_threshold: + command += ['-dct', str(drop_count_threshold)] + if incident_count_threshold: + command += ['-ict', str(incident_count_threshold)] + + clicommon.run_command(command, display_cmd=verbose) # # 'delete' subcommand ('config dropcounters delete') diff --git a/scripts/dropconfig b/scripts/dropconfig index 1fc812a474..d308d84a09 100755 --- a/scripts/dropconfig +++ b/scripts/dropconfig @@ -33,6 +33,7 @@ from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector # CONFIG_DB Tables DEBUG_COUNTER_CONFIG_TABLE = 'DEBUG_COUNTER' DROP_REASON_CONFIG_TABLE = 'DEBUG_COUNTER_DROP_REASON' +DEBUG_DROP_MONITOR_TABLE = 'DEBUG_DROP_MONITOR' # STATE_DB Tables DEBUG_COUNTER_CAPABILITY_TABLE = 'DEBUG_COUNTER_CAPABILITIES' @@ -60,6 +61,59 @@ class DropConfig(object): self.state_db = SonicV2Connector(use_unix_socket_path=False) self.state_db.connect(self.state_db.STATE_DB) + def show_drop_monitor(self): + """ + The function to show the current configuration of DEBUG_DROP_MONITOR table in CONFIG_DB + """ + current_config = self.config_db.get_entry(DEBUG_DROP_MONITOR_TABLE, "CONFIG") + if current_config == {}: + print("Debug drop monitoring has not been configured. Use 'config dropcounters monitor' CLI to configure it.") + return + + print("Current configuration of debug drop monitor") + print("The status: ", current_config.get("status", None)) + print("The window size: ", current_config.get("window", None)) + print("The drop_count_threshold: ", current_config.get("drop_count_threshold", None)) + print("The incident_count_threshold: ", current_config.get("incident_count_threshold", None)) + + def configure_drop_monitor(self, status, window, drop_count_threshold, incident_count_threshold): + """ + The function to configure Debug Drop Monitor feature. + """ + config = self.config_db.get_entry(DEBUG_DROP_MONITOR_TABLE, "CONFIG") + + if status: + if status not in ['enabled', 'disabled']: + raise InvalidArgumentError('Invalid status: {}, expected: enabled/disabled'.format(status)) + config["status"] = status + elif "status" not in config.keys(): + config["status"] = "disabled" + + if window: + if window <= 0: + raise InvalidArgumentError('Invalid window size. Window size should be positive, received: {}'.format(window)) + config["window"] = str(window) + elif "window" not in config.keys(): + config["window"] = "900" + + if drop_count_threshold: + if drop_count_threshold < 0: + raise InvalidArgumentError('Invalid drop count threshold. Drop count threshold should be positive, received: {}'.format(drop_count_threshold)) + config["drop_count_threshold"] = str(drop_count_threshold) + elif "drop_count_threshold" not in config.keys(): + config["drop_count_threshold"] = "100" + + if incident_count_threshold: + if incident_count_threshold < 0: + raise InvalidArgumentError('Invalid incident count threshold. Incident count threshold should be positive, received: {}'.format(incident_count_threshold)) + config["incident_count_threshold"] = str(incident_count_threshold) + elif "incident_count_threshold" not in config.keys(): + config["incident_count_threshold"] = "2" + + # Update the DEBUG_DROP_MONITOR table + self.config_db.set_entry(DEBUG_DROP_MONITOR_TABLE, ("CONFIG"), config) + print(f"Successfully updated the DEBUG_DROP_MONITOR config") + # -c show_config def print_counter_config(self, group): """ @@ -349,6 +403,10 @@ Examples: parser.add_argument('-t', '--type', type=str, help='The type of the target drop counter', default=None) parser.add_argument('-d', '--desc', type=str, help='The description for the target drop counter', default=None) parser.add_argument('-r', '--reasons', type=str, help='The list of drop reasons for the target drop counter', default=None) + parser.add_argument('-s', '--status', type=str, help='To set the status of drop counter monitoring feature (enabled/disabled)', default=None) + parser.add_argument('-w', '--window', type=int, help='The window for drop counter monitor in seconds', default=None) + parser.add_argument('-dct', '--drop-count-threshold', type=int, help='The minimum drops needed to classify a drop as an incident by the drop count monitor', default=None) + parser.add_argument('-ict', '--incident-count-threshold', type=int, help='The minimum number of incidents needed to register a syslog by drop count monitor', default=None) args = parser.parse_args() @@ -361,6 +419,13 @@ Examples: description = args.desc drop_reasons = args.reasons + # These arguments are used to control the Drop Counter Monitor feature + # This is an optional feature and more details on its usage can be found at: https://github.com/sonic-net/SONiC/blob/master/doc/drop_counters/drop_counters_HLD.md + status = args.status + window = args.window + drop_count_threshold = args.drop_count_threshold + incident_count_threshold = args.incident_count_threshold + reasons = deserialize_reason_list(drop_reasons) dconfig = DropConfig() @@ -398,6 +463,14 @@ Examples: dconfig.print_counter_config(group) elif command == 'show_capabilities': dconfig.print_device_capabilities() + elif command == 'config_monitor': + try: + dconfig.configure_drop_monitor(status, window, drop_count_threshold, incident_count_threshold) + except InvalidArgumentError as err: + print('Encountered error trying to configure drop monitor: {}'.format(err.message)) + sys.exit(1) + elif command == 'show_monitor': + dconfig.show_drop_monitor() else: print("Command not recognized") diff --git a/show/dropcounters.py b/show/dropcounters.py index 9bb988fc5b..73206909ca 100644 --- a/show/dropcounters.py +++ b/show/dropcounters.py @@ -36,6 +36,14 @@ def capabilities(verbose): clicommon.run_command(cmd, display_cmd=verbose) +# 'monitor' subcommand ("show dropcounters monitor") +@dropcounters.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def monitor(verbose): + """Show device drop counter capabilities""" + cmd = ['dropconfig', '-c', 'show_monitor'] + + clicommon.run_command(cmd, display_cmd=verbose) # 'counts' subcommand ("show dropcounters counts") @dropcounters.command() diff --git a/tests/config_test.py b/tests/config_test.py index 627c0ce833..ab02718ffb 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -3179,6 +3179,19 @@ def test_remove_reasons(self, mock_run_command): assert result.exit_code == 0 mock_run_command.assert_called_once_with(['dropconfig', '-c', 'remove', '-n', str(counter_name), '-r', str(reasons)], display_cmd=True) + @patch('utilities_common.cli.run_command') + def test_configure_drop_monitor(self, mock_run_command): + status = 'enabled' + window = '600' + drop_count_threshold = '5' + incident_count_threshold = '3' + runner = CliRunner() + result = runner.invoke(config.config.commands['dropcounters'].commands['monitor'], ['-s', status, '-w', window, '-dct', drop_count_threshold, '-ict', incident_count_threshold, '-v']) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + mock_run_command.assert_called_once_with(['dropconfig', '-c', 'config_monitor', '-s', status, '-w', window, '-dct', drop_count_threshold, '-ict', incident_count_threshold], display_cmd=True) + def teardown(self): print("TEARDOWN") diff --git a/tests/dropconfig_test.py b/tests/dropconfig_test.py index 1c2dc4b678..89221e9444 100644 --- a/tests/dropconfig_test.py +++ b/tests/dropconfig_test.py @@ -46,6 +46,38 @@ def test_remove_error(self, mock_print): mock_print.assert_called_once_with('Encountered error trying to remove reasons: No counter name provided') assert e.value.code == 1 + @patch('builtins.print') + @patch('sys.argv', ['dropconfig', '-c', 'config_monitor', '-s', 'off']) + def test_config_monitor_status_error(self, mock_print): + with pytest.raises(SystemExit) as e: + dropconfig.main() + mock_print.assert_called_once_with('Encountered error trying to configure drop monitor: Invalid status: off, expected: enabled/disabled') + assert e.value.code == 1 + + @patch('builtins.print') + @patch('sys.argv', ['dropconfig', '-c', 'config_monitor', '-w', '-1']) + def test_config_monitor_window_error(self, mock_print): + with pytest.raises(SystemExit) as e: + dropconfig.main() + mock_print.assert_called_once_with('Encountered error trying to configure drop monitor: Invalid window size. Window size should be positive, received: -1') + assert e.value.code == 1 + + @patch('builtins.print') + @patch('sys.argv', ['dropconfig', '-c', 'config_monitor', '-dct', '-1']) + def test_config_monitor_dct_error(self, mock_print): + with pytest.raises(SystemExit) as e: + dropconfig.main() + mock_print.assert_called_once_with('Encountered error trying to configure drop monitor: Invalid drop count threshold. Drop count threshold should be positive, received: -1') + assert e.value.code == 1 + + @patch('builtins.print') + @patch('sys.argv', ['dropconfig', '-c', 'config_monitor', '-ict', '-1']) + def test_config_monitor_ict_error(self, mock_print): + with pytest.raises(SystemExit) as e: + dropconfig.main() + mock_print.assert_called_once_with('Encountered error trying to configure drop monitor: Invalid incident count threshold. Incident count threshold should be positive, received: -1') + assert e.value.code == 1 + def teardown(self): print('TEARDOWN') diff --git a/tests/drops_group_test.py b/tests/drops_group_test.py index 93f99e3f1b..3e6432f786 100644 --- a/tests/drops_group_test.py +++ b/tests/drops_group_test.py @@ -98,6 +98,13 @@ sonic_drops_test 0 0 """ +expected_drop_monitor_config = """\ +Current configuration of debug drop monitor +The status: disabled +The window size: 900 +The drop_count_threshold: 100 +The incident_count_threshold: 2 +""" def remove_tmp_dropstat_file(): # remove the tmp portstat @@ -163,6 +170,12 @@ def test_show_counts_with_clear(self): print(result.output) assert result.output == expected_counts_with_clear + def test_show_drop_monitor_config(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["dropcounters"].commands["monitor"], []) + print(result.output) + assert result.output == expected_drop_monitor_config + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 2f12201b10..8fc59246f1 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -2837,5 +2837,11 @@ "hello_time": "2", "max_age": "20", "priority": "32768" + }, + "DEBUG_DROP_MONITOR|CONFIG": { + "status": "disabled", + "window": 900, + "drop_count_threshold": 100, + "incident_count_threshold": 2 } }