diff --git a/config/main.py b/config/main.py index f48c446adf..6d34abb003 100644 --- a/config/main.py +++ b/config/main.py @@ -7905,5 +7905,82 @@ def notice(db, category_list, max_events, namespace): handle_asic_sdk_health_suppress(db, 'notice', category_list, max_events, namespace) +# +# 'logrotate' group ('config logrotate ...') +# +@config.group(invoke_without_command=True) +@click.pass_context +@click.argument('file', required=True, type=click.Choice(['syslog', 'debug'])) +def logrotate(ctx, file): + """Configuring logrotate""" + # If invoking subcomand, no need to do anything + if ctx.invoked_subcommand is not None: + return + + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, None) + + +@logrotate.command() +@click.pass_context +@click.argument('disk_percentage', metavar='', + required=True, type=float) +def disk_percentage(ctx, disk_percentage): + """Configuring logrotate disk-precentage""" + file = ctx.parent.params.get('file') + if 0 > disk_percentage > 100: + click.echo(f'Disk percentage {disk_percentage} is not in range [0 - 100]') + pass + + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, + {'disk_percentage': disk_percentage}) + + +@logrotate.command(name='frequency') +@click.pass_context +@click.argument('frequency', metavar='', + required=True, + type=click.Choice(['daily', 'weekly', 'monthly', 'yearly'])) +def logrotate_frequency(ctx, frequency): + """Configuring logrotate rotation frequency""" + file = ctx.parent.params.get('file') + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, + {'frequency': frequency}) + + +@logrotate.command() +@click.pass_context +@click.argument('max_number', metavar='', + type=click.IntRange(0, 999999), required=True) +def max_number(ctx, max_number): + """Configuring logrotate max-number of files""" + file = ctx.parent.params.get('file') + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, + {'max_number': max_number}) + + +@logrotate.command(name='size') +@click.pass_context +@click.argument('size', metavar='', type=float, required=True) +def logrotate_size(ctx, size): + """Configuring logrotate size of file""" + file = ctx.parent.params.get('file') + if 0.001 > size > 3500.0: + click.echo(f'Size {size} is not in range [0.001 - 3500.0]') + pass + + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, + {'size': size}) + + if __name__ == '__main__': config() diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 689ca23b73..ab144fc090 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -111,7 +111,10 @@ * [Reloading Configuration](#reloading-configuration) * [Loading Management Configuration](#loading-management-configuration) * [Saving Configuration to a File for Persistence](#saving-configuration-to-a-file-for-persistence) - * [Loopback Interfaces](#loopback-interfaces) +* [Logrotate](#logrotate) + * [Logrotate config commands](#logrotate-config-commands) + * [Logrotate show command](#logrotate-show-command) +* [Loopback Interfaces](#loopback-interfaces) * [Loopback show commands](#loopback-show-commands) * [Loopback config commands](#loopback-config-commands) * [VRF Configuration](#vrf-configuration) @@ -13701,3 +13704,67 @@ Sending 3 magic packet to 11:33:55:77:99:bb via interface Vlan1000 ``` For the 4th example, it specifise 2 target MAC addresses and `count` is 3. So it'll send 6 magic packets in total. + + +# Logrotate Commands +This sub-section explains the list of the configuration options available for logrotate feature. +## Logrotate config commands +- Rotate logs when they surpass a specified percentage of disk +``` +admin@sonic:~$ config logrotate disk-percentage <> <> +Usage: config logrotate disk-precentage [OPTIONS] + + Configuring logrotate disk-precentage file + +Options: + -?, -h, --help Show this message and exit. +``` +- Log files rotation frequency +``` +admin@sonic:~$ config logrotate frequency +Usage: config logrotate frequency [OPTIONS] + + Configuring logrotate frequency file + +Options: + -?, -h, --help Show this message and exit. +``` +- Max number of log files to keep +``` +admin@sonic:~$ config logrotate max-number +Usage: config logrotate max-number [OPTIONS] + + Configuring logrotate max-number file + +Options: + -?, -h, --help Show this message and exit. +``` +- Rotate logs if they grow bigger then size in Mebibytes +``` +admin@sonic:~$ config logrotate size +Usage: config logrotate size [OPTIONS] + + Configuring logrotate size file + +Options: + -h, -?, --help Show this message and exit. +``` +## Logrotate show command +- Show logrotate configuration +``` +admin@sonic:~$ show logrotate +Usage: show logrotate [OPTIONS] COMMAND [ARGS]... + + Show logrotate configuration + +Options: + -?, -h, --help Show this message and exit. +``` +``` +admin@sonic:~$ show logrotate +file disk-percentage frequency max-number size +------ ----------------- ----------- ------------ ------ +syslog 10.2 daily 10 20.0 +debug 50.5 weekly 20 10.0 + +``` \ No newline at end of file diff --git a/show/main.py b/show/main.py index 06114eb79f..3612ef4f25 100755 --- a/show/main.py +++ b/show/main.py @@ -2253,6 +2253,29 @@ def received(db, namespace): ctx.fail("ASIC/SDK health event is not supported on the platform") +# +# 'logrorare' command group ("show logrotate ...") +# +@cli.group('logrotate', invoke_without_command=True) +@clicommon.pass_db +def logrotate(db): + """Show logrotate configuration""" + + table = [] + + logging_table = db.cfgdb.get_table('LOGGING') + for key, value in logging_table.items(): + disk_percentage = value.get('disk_percentage', '') + frequency = value.get('frequency', '') + max_number = value.get('max_number', '') + size = value.get('size', '') + + table.append((key, disk_percentage, frequency, max_number, size)) + + hdrs = ['file', 'disk-percentage', 'frequency', 'max-number', 'size (MiB)'] + click.echo(tabulate(table, headers=hdrs, tablefmt='simple', missingval='')) + + # Load plugins and register them helper = util_base.UtilHelper() helper.load_and_register_plugins(plugins, cli) diff --git a/tests/config_test.py b/tests/config_test.py index 748d434fc2..60832447ca 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -3729,3 +3729,55 @@ def teardown_class(cls): from .mock_tables import mock_single_asic importlib.reload(mock_single_asic) dbconnector.load_database_config() + + +class TestConfigLogrotate(object): + @classmethod + def setup_class(cls): + print('SETUP') + import config.main + importlib.reload(config.main) + + @patch('utilities_common.cli.run_command', + mock.MagicMock(side_effect=mock_run_command_side_effect)) + def test_logrotate_disk_percentage(self): + runner = CliRunner() + obj = {'db': Db().cfgdb} + + result = runner.invoke(config.config.commands['logrotate'], + ['debug', 'disk-percentage', '24.25'], obj=obj) + assert result.exit_code == 0 + + @patch('utilities_common.cli.run_command', + mock.MagicMock(side_effect=mock_run_command_side_effect)) + def test_logrotate_frequency(self): + runner = CliRunner() + obj = {'db': Db().cfgdb} + + result = runner.invoke(config.config.commands['logrotate'], + ['debug', 'frequency', 'daily'], obj=obj) + assert result.exit_code == 0 + + @patch('utilities_common.cli.run_command', + mock.MagicMock(side_effect=mock_run_command_side_effect)) + def test_logrotate_max_number(self): + runner = CliRunner() + obj = {'db': Db().cfgdb} + + result = runner.invoke(config.config.commands['logrotate'], + ['syslog', 'max-number', '2024'], obj=obj) + assert result.exit_code == 0 + + @patch('utilities_common.cli.run_command', + mock.MagicMock(side_effect=mock_run_command_side_effect)) + def test_logrotate_size(self): + runner = CliRunner() + obj = {'db': Db().cfgdb} + + result = runner.invoke(config.config.commands['logrotate'], + ['syslog', 'size', '30.0'], obj=obj) + assert result.exit_code == 0 + + @classmethod + def teardown_class(cls): + print('TEARDOWN') diff --git a/tests/show_test.py b/tests/show_test.py index 4cd29ac45e..78499e3564 100644 --- a/tests/show_test.py +++ b/tests/show_test.py @@ -1040,6 +1040,12 @@ def test_show_ztp(self, mock_run_command): assert result.exit_code == 0 mock_run_command.assert_called_with(['ztp', 'status', '--verbose'], display_cmd=True) + @patch('show.main.run_command') + def test_show_logrotate(self, mock_run_command): + runner = CliRunner() + result = runner.invoke(show.cli.commands['logrotate']) + assert result.exit_code == 0 + def teardown(self): print('TEAR DOWN')