From fc4b3334c54d0f51883f6685279df5fac040e2bd Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Mon, 25 Dec 2023 14:45:17 +0800 Subject: [PATCH] Support disable/enable syslog rate limit feature (#3072) Depends on PR sonic-net/sonic-buildimage#17458 What I did Add CLIs to enable/disable containercfgd to optimize warm/fast boot path How I did it Add CLIs to enable/disable containercfgd How to verify it unit test manual test --- config/syslog.py | 83 ++++++++++++++++++++++++++++++++++++++ doc/Command-Reference.md | 27 +++++++++++++ tests/syslog_test.py | 86 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) diff --git a/config/syslog.py b/config/syslog.py index 90fc52ec9d..7533a7f71f 100644 --- a/config/syslog.py +++ b/config/syslog.py @@ -471,3 +471,86 @@ def rate_limit_container(db, service_name, interval, burst): feature_data = db.cfgdb.get_table(syslog_common.FEATURE_TABLE) syslog_common.service_validator(feature_data, service_name) syslog_common.save_rate_limit_to_db(db, service_name, interval, burst, log) + + +@syslog.group( + name="rate-limit-feature", + cls=clicommon.AliasedGroup +) +def rate_limit_feature(): + """ Configure syslog rate limit feature """ + pass + + +@rate_limit_feature.command("enable") +@clicommon.pass_db +def enable_rate_limit_feature(db): + """ Enable syslog rate limit feature """ + feature_data = db.cfgdb.get_table(syslog_common.FEATURE_TABLE) + for feature_name in feature_data.keys(): + click.echo(f'Enabling syslog rate limit feature for {feature_name}') + output, _ = clicommon.run_command(['docker', 'ps', '-q', '-f', 'status=running', '-f', f'name={feature_name}'], return_cmd=True) + if not output: + click.echo(f'{feature_name} is not running, ignoring...') + continue + + output, _ = clicommon.run_command(['docker', 'exec', '-i', feature_name, 'supervisorctl', 'status', 'containercfgd'], + ignore_error=True, return_cmd=True) + if 'no such process' not in output: + click.echo(f'Syslog rate limit feature is already enabled in {feature_name}, ignoring...') + continue + + commands = [ + ['docker', 'cp', '/usr/share/sonic/templates/containercfgd.conf', f'{feature_name}:/etc/supervisor/conf.d/'], + ['docker', 'exec', '-i', feature_name, 'supervisorctl', 'reread'], + ['docker', 'exec', '-i', feature_name, 'supervisorctl', 'update'], + ['docker', 'exec', '-i', feature_name, 'supervisorctl', 'start', 'containercfgd'] + ] + + failed = False + for command in commands: + output, ret = clicommon.run_command(command, return_cmd=True) + if ret != 0: + failed = True + click.echo(f'Enable syslog rate limit feature for {feature_name} failed - {output}') + break + + if not failed: + click.echo(f'Enabled syslog rate limit feature for {feature_name}') + + +@rate_limit_feature.command("disable") +@clicommon.pass_db +def disable_rate_limit_feature(db): + """ Disable syslog rate limit feature """ + feature_data = db.cfgdb.get_table(syslog_common.FEATURE_TABLE) + for feature_name in feature_data.keys(): + click.echo(f'Disabling syslog rate limit feature for {feature_name}') + output, _ = clicommon.run_command(['docker', 'ps', '-q', '-f', 'status=running', '-f', f'name={feature_name}'], return_cmd=True) + if not output: + click.echo(f'{feature_name} is not running, ignoring...') + continue + + output, _ = clicommon.run_command(['docker', 'exec', '-i', feature_name, 'supervisorctl', 'status', 'containercfgd'], + ignore_error=True, return_cmd=True) + if 'no such process' in output: + click.echo(f'Syslog rate limit feature is already disabled in {feature_name}, ignoring...') + continue + + commands = [ + ['docker', 'exec', '-i', feature_name, 'supervisorctl', 'stop', 'containercfgd'], + ['docker', 'exec', '-i', feature_name, 'rm', '-f', '/etc/supervisor/conf.d/containercfgd.conf'], + ['docker', 'exec', '-i', feature_name, 'supervisorctl', 'reread'], + ['docker', 'exec', '-i', feature_name, 'supervisorctl', 'update'] + ] + failed = False + for command in commands: + output, ret = clicommon.run_command(command, return_cmd=True) + if ret != 0: + failed = True + click.echo(f'Disable syslog rate limit feature for {feature_name} failed - {output}') + break + + if not failed: + click.echo(f'Disabled syslog rate limit feature for {feature_name}') + diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 380b5bfd9b..6571be3cfa 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -9893,6 +9893,33 @@ This command is used to configure syslog rate limit for containers. admin@sonic:~$ sudo config syslog rate-limit-container bgp --interval 300 --burst 20000 ``` +**config syslog rate-limit-feature enable** + +This command is used to enable syslog rate limit feature. + +- Usage: + ``` + config syslog rate-limit-feature enable + ``` + +- Example: + ``` + admin@sonic:~$ sudo config syslog rate-limit-feature enable + ``` + +**config syslog rate-limit-feature disable** + +This command is used to disable syslog rate limit feature. + +- Usage: + ``` + config syslog rate-limit-feature disable + ``` + +- Example: + ``` + admin@sonic:~$ sudo config syslog rate-limit-feature disable + ``` Go Back To [Beginning of the document](#) or [Beginning of this section](#syslog) diff --git a/tests/syslog_test.py b/tests/syslog_test.py index 354100a508..44915b6d36 100644 --- a/tests/syslog_test.py +++ b/tests/syslog_test.py @@ -399,3 +399,89 @@ def test_show_syslog_rate_limit_container_negative(self, subcommand): logger.debug("\n" + result.output) logger.debug(result.exit_code) assert result.exit_code != SUCCESS + + @mock.patch('config.syslog.clicommon.run_command') + def test_enable_syslog_rate_limit_feature(self, mock_run): + db = Db() + db.cfgdb.set_entry(FEATURE_TABLE, 'bgp', {SUPPORT_RATE_LIMIT: 'true', + 'state': 'enabled'}) + + runner = CliRunner() + + mock_run.return_value = ('no such process', 0) + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["enable"], obj=db + ) + assert result.exit_code == SUCCESS + + # container not run + mock_run.return_value = ('', 0) + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["enable"], obj=db + ) + assert result.exit_code == SUCCESS + + # process already running + mock_run.return_value = ('something', 0) + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["enable"], obj=db + ) + assert result.exit_code == SUCCESS + + # one command fail + def side_effect(*args, **kwargs): + side_effect.call_count += 1 + if side_effect.call_count <= 2: + return 'no such process', 0 + else: + return '', -1 + side_effect.call_count = 0 + mock_run.side_effect = side_effect + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["enable"], obj=db + ) + assert result.exit_code == SUCCESS + + + @mock.patch('config.syslog.clicommon.run_command') + def test_disable_syslog_rate_limit_feature(self, mock_run): + db = Db() + db.cfgdb.set_entry(FEATURE_TABLE, 'bgp', {SUPPORT_RATE_LIMIT: 'true', + 'state': 'enabled'}) + + runner = CliRunner() + + mock_run.return_value = ('something', 0) + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["disable"], obj=db + ) + assert result.exit_code == SUCCESS + + # container not run + mock_run.return_value = ('', 0) + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["disable"], obj=db + ) + assert result.exit_code == SUCCESS + + # process already stopped + mock_run.return_value = ('no such process', 0) + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["disable"], obj=db + ) + assert result.exit_code == SUCCESS + + # one command fail + def side_effect(*args, **kwargs): + side_effect.call_count += 1 + if side_effect.call_count <= 2: + return 'something', 0 + else: + return '', -1 + side_effect.call_count = 0 + mock_run.side_effect = side_effect + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-feature"].commands["disable"], obj=db + ) + assert result.exit_code == SUCCESS +