Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[show][config] Add CLI support for configurable drop monitor feature #3756

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7392,6 +7392,31 @@ 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
9 changes: 9 additions & 0 deletions show/dropcounters.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ 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()
@click.option('-g', '--group', required=False)
Expand Down
17 changes: 17 additions & 0 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3179,6 +3179,23 @@ 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
38 changes: 38 additions & 0 deletions tests/dropconfig_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,44 @@ 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
}
}
Loading