From 5f4431e12e2faa61d0e82476863d0c0ae5e08ae0 Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Sun, 28 Apr 2024 01:21:23 +0300 Subject: [PATCH 1/7] Added logrotate config and show commands Signed-off-by: Yevhen Fastiuk --- config/main.py | 71 ++++++++++++++++++++++++++++++++++++++++ doc/Command-Reference.md | 69 +++++++++++++++++++++++++++++++++++++- show/main.py | 24 ++++++++++++++ tests/config_test.py | 62 ++++++++++++++++++++++++++++++++++- tests/show_test.py | 6 ++++ 5 files changed, 230 insertions(+), 2 deletions(-) diff --git a/config/main.py b/config/main.py index 8f3b7245bd..1457018f59 100644 --- a/config/main.py +++ b/config/main.py @@ -7524,5 +7524,76 @@ def date(date, time): clicommon.run_command(['timedatectl', 'set-time', date_time]) +# +# 'logrotate' group ('config logrotate ...') +# +@config.group() +def logrotate(): + """Configuring logrotate""" + pass + + +@logrotate.command() +@click.argument('file', metavar='', required=True, + type=click.Choice(['syslog', 'debug'])) +@click.argument('disk_percentage', metavar='', + required=True, type=float) +def disk_percentage(file, disk_percentage): + """Configuring logrotate disk-precentage 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() +@click.argument('file', metavar='', required=True, + type=click.Choice(['syslog', 'debug'])) +@click.argument('frequency', metavar='', + required=True, + type=click.Choice(['daily', 'weekly', 'monthly', 'yearly'])) +def frequency(file, frequency): + """Configuring logrotate frequency file """ + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, + {'frequency': frequency}) + + +@logrotate.command() +@click.argument('file', metavar='', required=True, + type=click.Choice(['syslog', 'debug'])) +@click.argument('max_number', metavar='', + type=click.IntRange(0, 999999), required=True) +def max_number(file, max_number): + """Configuring logrotate max-number file """ + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, + {'max_number': max_number}) + + +@logrotate.command() +@click.argument('file', metavar='', required=True, + type=click.Choice(['syslog', 'debug'])) +@click.argument('size', metavar='', type=float, required=True) +def size(file, size): + """Configuring logrotate size file """ + + if 0.001 > size > 3500.0: + click.echo(f'Size {disk_percentage} 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 e97922af65..30c105587e 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -100,7 +100,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) @@ -13309,3 +13312,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 2d331e7da1..fc89a7f883 100755 --- a/show/main.py +++ b/show/main.py @@ -2157,6 +2157,30 @@ def suppress_pending_fib(db): click.echo(state) + +# +# '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'] + 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 1054a52a33..fe4784c851 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -2741,4 +2741,64 @@ def teardown_class(cls): from .mock_tables import dbconnector from .mock_tables import mock_single_asic importlib.reload(mock_single_asic) - dbconnector.load_database_config() \ No newline at end of file + 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'].commands['disk-percentage'], + ['debug', '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'].commands['frequency'], + ['debug', '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'].commands['max-number'], + ['debug', '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'].commands['size'], + ['debug', '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') From fb95e498c362c427ac2c20e249c41b73499b8a2d Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Tue, 21 May 2024 03:55:51 +0300 Subject: [PATCH 2/7] Add an ability to return to default config Signed-off-by: Yevhen Fastiuk --- config/main.py | 40 +++++++++++++++++++++++----------------- tests/config_test.py | 16 ++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/config/main.py b/config/main.py index a8a983971d..f6cdd98b56 100644 --- a/config/main.py +++ b/config/main.py @@ -7753,20 +7753,27 @@ def notice(db, category_list, max_events, namespace): # # 'logrotate' group ('config logrotate ...') # -@config.group() -def 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""" - pass + # 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.argument('file', metavar='', required=True, - type=click.Choice(['syslog', 'debug'])) +@click.pass_context @click.argument('disk_percentage', metavar='', required=True, type=float) -def disk_percentage(file, disk_percentage): +def disk_percentage(ctx, disk_percentage): """Configuring logrotate disk-precentage file """ - + 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 @@ -7778,13 +7785,13 @@ def disk_percentage(file, disk_percentage): @logrotate.command() -@click.argument('file', metavar='', required=True, - type=click.Choice(['syslog', 'debug'])) +@click.pass_context @click.argument('frequency', metavar='', required=True, type=click.Choice(['daily', 'weekly', 'monthly', 'yearly'])) -def frequency(file, frequency): +def frequency(ctx, frequency): """Configuring logrotate frequency file """ + file = ctx.parent.params.get('file') config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, @@ -7792,12 +7799,12 @@ def frequency(file, frequency): @logrotate.command() -@click.argument('file', metavar='', required=True, - type=click.Choice(['syslog', 'debug'])) +@click.pass_context @click.argument('max_number', metavar='', type=click.IntRange(0, 999999), required=True) -def max_number(file, max_number): +def max_number(ctx, max_number): """Configuring logrotate max-number file """ + file = ctx.parent.params.get('file') config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry(swsscommon.CFG_LOGGING_TABLE_NAME, file, @@ -7805,12 +7812,11 @@ def max_number(file, max_number): @logrotate.command() -@click.argument('file', metavar='', required=True, - type=click.Choice(['syslog', 'debug'])) +@click.pass_context @click.argument('size', metavar='', type=float, required=True) -def size(file, size): +def size(ctx, size): """Configuring logrotate size file """ - + file = ctx.parent.params.get('file') if 0.001 > size > 3500.0: click.echo(f'Size {disk_percentage} is not in range [0.001 - 3500.0]') pass diff --git a/tests/config_test.py b/tests/config_test.py index fc431a4c6c..adcb398711 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -3109,8 +3109,8 @@ def test_logrotate_disk_percentage(self): obj = {'db': Db().cfgdb} result = runner.invoke( - config.config.commands['logrotate'].commands['disk-percentage'], - ['debug', '24.25'], obj=obj) + config.config.commands['logrotate'].commands['debug'], + ['disk-percentage', '24.25'], obj=obj) assert result.exit_code == 0 @@ -3121,8 +3121,8 @@ def test_logrotate_frequency(self): obj = {'db': Db().cfgdb} result = runner.invoke( - config.config.commands['logrotate'].commands['frequency'], - ['debug', 'daily'], obj=obj) + config.config.commands['logrotate'].commands['debug'], + ['frequency', 'daily'], obj=obj) assert result.exit_code == 0 @@ -3133,8 +3133,8 @@ def test_logrotate_max_number(self): obj = {'db': Db().cfgdb} result = runner.invoke( - config.config.commands['logrotate'].commands['max-number'], - ['debug', '2024'], obj=obj) + config.config.commands['logrotate'].commands['syslog'], + ['max-number', '2024'], obj=obj) assert result.exit_code == 0 @@ -3145,8 +3145,8 @@ def test_logrotate_size(self): obj = {'db': Db().cfgdb} result = runner.invoke( - config.config.commands['logrotate'].commands['size'], - ['debug', '30.0'], obj=obj) + config.config.commands['logrotate'].commands['syslog'], + ['size', '30.0'], obj=obj) assert result.exit_code == 0 From 110cb4304ae437b5699e801ba19aab7638b3e573 Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Tue, 21 May 2024 23:04:19 +0300 Subject: [PATCH 3/7] Logrotate: Fix flake checks Signed-off-by: Yevhen Fastiuk --- config/main.py | 16 ++++++++-------- show/main.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/main.py b/config/main.py index f6cdd98b56..25ce7143b2 100644 --- a/config/main.py +++ b/config/main.py @@ -7772,7 +7772,7 @@ def logrotate(ctx, file): @click.argument('disk_percentage', metavar='', required=True, type=float) def disk_percentage(ctx, disk_percentage): - """Configuring logrotate disk-precentage file """ + """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]') @@ -7784,13 +7784,13 @@ def disk_percentage(ctx, disk_percentage): {'disk_percentage': disk_percentage}) -@logrotate.command() +@logrotate.command(name='frequency') @click.pass_context @click.argument('frequency', metavar='', required=True, type=click.Choice(['daily', 'weekly', 'monthly', 'yearly'])) -def frequency(ctx, frequency): - """Configuring logrotate frequency file """ +def logrotate_frequency(ctx, frequency): + """Configuring logrotate rotation frequency""" file = ctx.parent.params.get('file') config_db = ConfigDBConnector() config_db.connect() @@ -7803,7 +7803,7 @@ def frequency(ctx, frequency): @click.argument('max_number', metavar='', type=click.IntRange(0, 999999), required=True) def max_number(ctx, max_number): - """Configuring logrotate max-number file """ + """Configuring logrotate max-number of files""" file = ctx.parent.params.get('file') config_db = ConfigDBConnector() config_db.connect() @@ -7811,11 +7811,11 @@ def max_number(ctx, max_number): {'max_number': max_number}) -@logrotate.command() +@logrotate.command(name='size') @click.pass_context @click.argument('size', metavar='', type=float, required=True) -def size(ctx, size): - """Configuring logrotate size file """ +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 {disk_percentage} is not in range [0.001 - 3500.0]') diff --git a/show/main.py b/show/main.py index 5d7d55a5c3..364499bffd 100755 --- a/show/main.py +++ b/show/main.py @@ -2273,7 +2273,7 @@ def logrotate(db): table.append((key, disk_percentage, frequency, max_number, size)) - hdrs = ['file', 'disk-percentage', 'frequency', 'max-number', 'size'] + hdrs = ['file', 'disk-percentage', 'frequency', 'max-number', 'size'] click.echo(tabulate(table, headers=hdrs, tablefmt='simple', missingval='')) From e2e45395fcf45fd23844cce81c3bc5cc924d8810 Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Mon, 27 May 2024 17:31:39 +0300 Subject: [PATCH 4/7] Logrotate: Align tests Signed-off-by: Yevhen Fastiuk --- tests/config_test.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/tests/config_test.py b/tests/config_test.py index adcb398711..c5181e2b7f 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -3108,10 +3108,8 @@ def test_logrotate_disk_percentage(self): runner = CliRunner() obj = {'db': Db().cfgdb} - result = runner.invoke( - config.config.commands['logrotate'].commands['debug'], - ['disk-percentage', '24.25'], obj=obj) - + 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', @@ -3120,10 +3118,8 @@ def test_logrotate_frequency(self): runner = CliRunner() obj = {'db': Db().cfgdb} - result = runner.invoke( - config.config.commands['logrotate'].commands['debug'], - ['frequency', 'daily'], obj=obj) - + result = runner.invoke(config.config.commands['logrotate'], + ['debug', 'frequency', 'daily'], obj=obj) assert result.exit_code == 0 @patch('utilities_common.cli.run_command', @@ -3132,10 +3128,8 @@ def test_logrotate_max_number(self): runner = CliRunner() obj = {'db': Db().cfgdb} - result = runner.invoke( - config.config.commands['logrotate'].commands['syslog'], - ['max-number', '2024'], obj=obj) - + result = runner.invoke(config.config.commands['logrotate'], + ['syslog', 'max-number', '2024'], obj=obj) assert result.exit_code == 0 @patch('utilities_common.cli.run_command', @@ -3144,10 +3138,8 @@ def test_logrotate_size(self): runner = CliRunner() obj = {'db': Db().cfgdb} - result = runner.invoke( - config.config.commands['logrotate'].commands['syslog'], - ['size', '30.0'], obj=obj) - + result = runner.invoke(config.config.commands['logrotate'], + ['syslog', 'size', '30.0'], obj=obj) assert result.exit_code == 0 @classmethod From f4ae104e1fd606f779b8dbe275bbd51b957d421c Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Mon, 12 Aug 2024 03:11:11 +0300 Subject: [PATCH 5/7] Fix copy-paste error Signed-off-by: Yevhen Fastiuk --- config/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/main.py b/config/main.py index 25ce7143b2..e792ecc780 100644 --- a/config/main.py +++ b/config/main.py @@ -7818,7 +7818,7 @@ 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 {disk_percentage} is not in range [0.001 - 3500.0]') + click.echo(f'Size {size} is not in range [0.001 - 3500.0]') pass config_db = ConfigDBConnector() From da2df2758173535323b124b39dadaa85e4d14888 Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Wed, 14 Aug 2024 04:58:43 +0300 Subject: [PATCH 6/7] Logrotate: Fix static anlysis test Signed-off-by: Yevhen Fastiuk --- tests/config_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config_test.py b/tests/config_test.py index 3d4d45a358..60832447ca 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -3775,7 +3775,7 @@ def test_logrotate_size(self): obj = {'db': Db().cfgdb} result = runner.invoke(config.config.commands['logrotate'], - ['syslog', 'size', '30.0'], obj=obj) + ['syslog', 'size', '30.0'], obj=obj) assert result.exit_code == 0 @classmethod From c0ccc47ef17eb9e094c49ab3adcc515e4678b88f Mon Sep 17 00:00:00 2001 From: Yevhen Fastiuk Date: Wed, 21 Aug 2024 16:20:33 +0300 Subject: [PATCH 7/7] Logrotate: Update header according to HLD Signed-off-by: Yevhen Fastiuk --- show/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/show/main.py b/show/main.py index 53a360290c..3612ef4f25 100755 --- a/show/main.py +++ b/show/main.py @@ -2272,7 +2272,7 @@ def logrotate(db): table.append((key, disk_percentage, frequency, max_number, size)) - hdrs = ['file', 'disk-percentage', 'frequency', 'max-number', 'size'] + hdrs = ['file', 'disk-percentage', 'frequency', 'max-number', 'size (MiB)'] click.echo(tabulate(table, headers=hdrs, tablefmt='simple', missingval=''))