Skip to content

Commit

Permalink
[show][config] Add CLI support for configurable drop monitor feature
Browse files Browse the repository at this point in the history
*   Implements commands to configure persistent drop monitor thresholds.
*   Adds support for setting monitoring window size, drop count threshold, and incident count threshold.
*   Includes `show dropcounters monitor` command to display current configuration.
*   Provides `config dropcounters monitor` command to enable/disable and configure the monitor.
*   Adds unit tests to cover the new CLI functionality.
  • Loading branch information
arista-hpandya committed Feb 7, 2025
1 parent a7deb8c commit 78028a4
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 0 deletions.
22 changes: 22 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
73 changes: 73 additions & 0 deletions scripts/dropconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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()

Expand All @@ -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()
Expand Down Expand Up @@ -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")

Expand Down
8 changes: 8 additions & 0 deletions show/dropcounters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
13 changes: 13 additions & 0 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
32 changes: 32 additions & 0 deletions tests/dropconfig_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

13 changes: 13 additions & 0 deletions tests/drops_group_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
6 changes: 6 additions & 0 deletions tests/mock_tables/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit 78028a4

Please sign in to comment.