diff --git a/config/syslog.py b/config/syslog.py index be16a12d38..d924a6d161 100644 --- a/config/syslog.py +++ b/config/syslog.py @@ -6,6 +6,7 @@ import utilities_common.cli as clicommon from sonic_py_common import logger +from syslog_util import common as syslog_common SYSLOG_TABLE_CDB = "SYSLOG_SERVER" @@ -447,3 +448,26 @@ def delete(db, server_ip_address): except Exception as e: log.log_error("Failed to remove remote syslog logging: {}".format(str(e))) ctx.fail(str(e)) + + +@syslog.command("rate-limit-host") +@click.option("-i", "--interval", help="Configures syslog rate limit interval in seconds for host", type=click.IntRange(0, 2147483647)) +@click.option("-b", "--burst", help="Configures syslog rate limit burst in number of messages for host", type=click.IntRange(0, 2147483647)) +@clicommon.pass_db +def rate_limit_host(db, interval, burst): + """ Configure syslog rate limit for host """ + syslog_common.rate_limit_validator(interval, burst) + syslog_common.save_rate_limit_to_db(db, None, interval, burst, log) + + +@syslog.command("rate-limit-container") +@click.argument("service_name", required=True) +@click.option("-i", "--interval", help="Configures syslog rate limit interval in seconds for specified containers", type=click.IntRange(0, 2147483647)) +@click.option("-b", "--burst", help="Configures syslog rate limit burst in number of messages for specified containers", type=click.IntRange(0, 2147483647)) +@clicommon.pass_db +def rate_limit_container(db, service_name, interval, burst): + """ Configure syslog rate limit for containers """ + syslog_common.rate_limit_validator(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) diff --git a/setup.py b/setup.py index cc0c940afb..70d7473bd7 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ 'pddf_psuutil', 'pddf_thermalutil', 'pddf_ledutil', + 'syslog_util', 'show', 'show.interfaces', 'show.plugins', diff --git a/show/syslog.py b/show/syslog.py index ed112e4c2d..d258be3351 100644 --- a/show/syslog.py +++ b/show/syslog.py @@ -4,6 +4,7 @@ from natsort import natsorted import utilities_common.cli as clicommon +from syslog_util import common as syslog_common SYSLOG_TABLE = "SYSLOG_SERVER" @@ -28,10 +29,14 @@ def format(header, body): cls=clicommon.AliasedGroup, invoke_without_command=True ) +@click.pass_context @clicommon.pass_db -def syslog(db): +def syslog(db, ctx): """ Show syslog server configuration """ + if ctx.invoked_subcommand is not None: + return + header = [ "SERVER IP", "SOURCE IP", @@ -51,3 +56,59 @@ def syslog(db): body.append(row) click.echo(format(header, body)) + +@syslog.command( + name='rate-limit-host' +) +@clicommon.pass_db +def rate_limit_host(db): + """ Show syslog rate limit configuration for host """ + + header = [ + "INTERVAL", + "BURST", + ] + body = [] + entry = db.cfgdb.get_entry(syslog_common.SYSLOG_CONFIG_TABLE, syslog_common.SYSLOG_CONFIG_GLOBAL_KEY) + if entry: + body.append([entry.get(syslog_common.SYSLOG_RATE_LIMIT_INTERVAL, 'N/A'), + entry.get(syslog_common.SYSLOG_RATE_LIMIT_BURST, 'N/A')]) + else: + body.append('N/A', 'N/A') + + click.echo(format(header, body)) + + +@syslog.command( + name='rate-limit-container' +) +@click.argument('service_name', metavar='', required=False) +@clicommon.pass_db +def rate_limit_container(db, service_name): + """ Show syslog rate limit configuration for containers """ + + header = [ + "SERVICE", + "INTERVAL", + "BURST", + ] + body = [] + features = db.cfgdb.get_table(syslog_common.FEATURE_TABLE) + + if service_name: + syslog_common.service_validator(features, service_name) + service_list = [service_name] + else: + service_list = [name for name, service_config in features.items() if service_config.get(syslog_common.SUPPORT_RATE_LIMIT, '').lower() == 'true'] + + syslog_configs = db.cfgdb.get_table(syslog_common.SYSLOG_CONFIG_FEATURE_TABLE) + for service in natsorted(service_list): + if service in syslog_configs: + entry = syslog_configs[service] + body.append([service, + entry.get(syslog_common.SYSLOG_RATE_LIMIT_INTERVAL, 'N/A'), + entry.get(syslog_common.SYSLOG_RATE_LIMIT_BURST, 'N/A')]) + else: + body.append([service, 'N/A', 'N/A']) + + click.echo(format(header, body)) diff --git a/sonic_package_manager/manifest.py b/sonic_package_manager/manifest.py index 94e00dec32..dc5b16b1d8 100644 --- a/sonic_package_manager/manifest.py +++ b/sonic_package_manager/manifest.py @@ -186,6 +186,9 @@ def unmarshal(self, value): ManifestArray('after', DefaultMarshaller(str)), ManifestArray('before', DefaultMarshaller(str)), ]), + ManifestRoot('syslog', [ + ManifestField('support-rate-limit', DefaultMarshaller(bool), False), + ]), ]), ManifestRoot('container', [ ManifestField('privileged', DefaultMarshaller(bool), False), diff --git a/sonic_package_manager/service_creator/feature.py b/sonic_package_manager/service_creator/feature.py index 7e4d8da8ed..90378d378f 100644 --- a/sonic_package_manager/service_creator/feature.py +++ b/sonic_package_manager/service_creator/feature.py @@ -25,6 +25,12 @@ 'available_mem_threshold': '10.0' } +SYSLOG_CONFIG = 'SYSLOG_CONFIG_FEATURE' +DEFAULT_SYSLOG_FEATURE_CONFIG = { + 'rate_limit_interval': '300', + 'rate_limit_burst': '20000' +} + def is_enabled(cfg): return cfg.get('state', 'disabled').lower() == 'enabled' @@ -36,7 +42,7 @@ def is_multi_instance(cfg): class FeatureRegistry: """ 1) FeatureRegistry class provides an interface to register/de-register new feature tables persistently. - 2) Writes persistent configuration to FEATURE & + 2) Writes persistent configuration to FEATURE & AUTO_TECHSUPPORT_FEATURE tables """ @@ -72,10 +78,14 @@ def register(self, new_cfg = {**new_cfg, **non_cfg_entries} conn.set_entry(FEATURE, name, new_cfg) - + if self.register_auto_ts(name): log.info(f'{name} entry is added to {AUTO_TS_FEATURE} table') + if 'syslog' in manifest['service'] and 'support-rate-limit' in manifest['service']['syslog'] and manifest['service']['syslog']['support-rate-limit']: + self.register_syslog_config(name) + log.info(f'{name} entry is added to {SYSLOG_CONFIG} table') + def deregister(self, name: str): """ Deregister feature by name. @@ -89,6 +99,7 @@ def deregister(self, name: str): for conn in db_connetors: conn.set_entry(FEATURE, name, None) conn.set_entry(AUTO_TS_FEATURE, name, None) + conn.set_entry(SYSLOG_CONFIG, name, None) def update(self, old_manifest: Manifest, @@ -119,10 +130,14 @@ def update(self, new_cfg = {**new_cfg, **non_cfg_entries} conn.set_entry(FEATURE, new_name, new_cfg) - + if self.register_auto_ts(new_name, old_name): log.info(f'{new_name} entry is added to {AUTO_TS_FEATURE} table') + if 'syslog' in new_manifest['service'] and 'support-rate-limit' in new_manifest['service']['syslog'] and new_manifest['service']['syslog']['support-rate-limit']: + self.register_syslog_config(new_name, old_name) + log.info(f'{new_name} entry is added to {SYSLOG_CONFIG} table') + def is_feature_enabled(self, name: str) -> bool: """ Returns whether the feature is current enabled or not. Accesses running CONFIG DB. If no running CONFIG_DB @@ -178,10 +193,26 @@ def register_auto_ts(self, new_name, old_name=None): current_cfg = conn.get_entry(AUTO_TS_FEATURE, old_name) conn.set_entry(AUTO_TS_FEATURE, old_name, None) new_cfg.update(current_cfg) - + conn.set_entry(AUTO_TS_FEATURE, new_name, new_cfg) return True + def register_syslog_config(self, new_name, old_name=None): + """ Registers syslog configuration + + Args: + new_name (str): new table name + old_name (str, optional): old table name. Defaults to None. + """ + for conn in self._sonic_db.get_connectors(): + new_cfg = copy.deepcopy(DEFAULT_SYSLOG_FEATURE_CONFIG) + if old_name: + current_cfg = conn.get_entry(SYSLOG_CONFIG, old_name) + conn.set_entry(SYSLOG_CONFIG, old_name, None) + new_cfg.update(current_cfg) + + conn.set_entry(SYSLOG_CONFIG, new_name, new_cfg) + @staticmethod def get_default_feature_entries(state=None, owner=None) -> Dict[str, str]: """ Get configurable feature table entries: @@ -203,4 +234,5 @@ def get_non_configurable_feature_entries(manifest) -> Dict[str, str]: 'has_global_scope': str(manifest['service']['host-service']), 'has_timer': str(manifest['service']['delayed']), 'check_up_status': str(manifest['service']['check_up_status']), + 'support_syslog_rate_limit': str(manifest['service']['syslog']['support-rate-limit']), } diff --git a/syslog_util/__init__.py b/syslog_util/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/syslog_util/common.py b/syslog_util/common.py new file mode 100644 index 0000000000..5282c088e8 --- /dev/null +++ b/syslog_util/common.py @@ -0,0 +1,76 @@ +import click + + +FEATURE_TABLE = "FEATURE" +SYSLOG_CONFIG_TABLE = 'SYSLOG_CONFIG' +SYSLOG_CONFIG_GLOBAL_KEY = 'GLOBAL' +SYSLOG_CONFIG_FEATURE_TABLE = 'SYSLOG_CONFIG_FEATURE' + +SYSLOG_RATE_LIMIT_INTERVAL = 'rate_limit_interval' +SYSLOG_RATE_LIMIT_BURST = 'rate_limit_burst' +SUPPORT_RATE_LIMIT = 'support_syslog_rate_limit' + + +def rate_limit_validator(interval, burst): + """Validate input interval/burst + + Args: + interval (int): Rate limit interval + burst (int): Rate limit burst + """ + if interval is None and burst is None: + raise click.UsageError('Either interval or burst must be configured') + + +def service_validator(feature_data, service_name): + """Validate input service name + + Args: + feature_data (dict): feature entries of FEATURE table + service_name (str): service name + """ + if service_name not in feature_data: + valid_service_names = ','.join(feature_data.keys()) + raise click.ClickException(f'Invalid service name {service_name}, please choose from: {valid_service_names}') + + service_data = feature_data[service_name] + + support_rate_limit = service_data.get(SUPPORT_RATE_LIMIT, '').lower() == 'true' + if not support_rate_limit: + raise click.ClickException(f'Service {service_name} does not support syslog rate limit configuration') + + +def save_rate_limit_to_db(db, service_name, interval, burst, log): + """Save rate limit configuration to DB + + Args: + db (object): db object + service_name (str): service name. None means config for host. + interval (int): rate limit interval + burst (int): rate limit burst + log (obj): log object + """ + if service_name is None: + service_name = 'host' + table = SYSLOG_CONFIG_TABLE + key = SYSLOG_CONFIG_GLOBAL_KEY + else: + table = SYSLOG_CONFIG_FEATURE_TABLE + key = service_name + + if interval == 0 or burst == 0: + msg = f'Disable syslog rate limit for {service_name}' + click.echo(msg) + log.log_notice(msg) + interval = 0 + burst = 0 + + data = {} + if interval is not None: + data[SYSLOG_RATE_LIMIT_INTERVAL] = interval + if burst is not None: + data[SYSLOG_RATE_LIMIT_BURST] = burst + db.cfgdb.mod_entry(table, key, data) + log.log_notice(f"Configured syslog {service_name} rate-limits: interval={data.get(SYSLOG_RATE_LIMIT_INTERVAL, 'N/A')},\ + burst={data.get(SYSLOG_RATE_LIMIT_BURST, 'N/A')}") + diff --git a/tests/sonic_package_manager/test_service_creator.py b/tests/sonic_package_manager/test_service_creator.py index 645a820f94..c97d362626 100644 --- a/tests/sonic_package_manager/test_service_creator.py +++ b/tests/sonic_package_manager/test_service_creator.py @@ -36,6 +36,9 @@ def manifest(): 'fast-shutdown': { 'before': ['swss'], }, + 'syslog': { + 'support-rate-limit': False + } }, 'container': { 'privileged': True, @@ -217,6 +220,7 @@ def test_feature_registration(mock_sonic_db, manifest): 'has_global_scope': 'True', 'has_timer': 'False', 'check_up_status': 'False', + 'support_syslog_rate_limit': 'False', }) @@ -230,6 +234,7 @@ def test_feature_update(mock_sonic_db, manifest): 'has_global_scope': 'True', 'has_timer': 'False', 'check_up_status': 'False', + 'support_syslog_rate_limit': 'False', } mock_connector = Mock() mock_connector.get_entry = Mock(return_value=curr_feature_config) @@ -253,6 +258,7 @@ def test_feature_update(mock_sonic_db, manifest): 'has_global_scope': 'True', 'has_timer': 'True', 'check_up_status': 'False', + 'support_syslog_rate_limit': 'False', }), ], any_order=True) @@ -274,6 +280,7 @@ def test_feature_registration_with_timer(mock_sonic_db, manifest): 'has_global_scope': 'True', 'has_timer': 'True', 'check_up_status': 'False', + 'support_syslog_rate_limit': 'False', }) @@ -293,6 +300,7 @@ def test_feature_registration_with_non_default_owner(mock_sonic_db, manifest): 'has_global_scope': 'True', 'has_timer': 'False', 'check_up_status': 'False', + 'support_syslog_rate_limit': 'False', }) @@ -309,7 +317,7 @@ def get_entry(cls, table, key): return {"state" : "enabled", "rate_limit_interval" : "600"} else: return {} - + @classmethod def get_entry_running_cfg(cls, table, key): if table == "AUTO_TECHSUPPORT_FEATURE" and key == "test": @@ -362,7 +370,7 @@ def test_auto_ts_feature_update_flow(mock_sonic_db, manifest): new_manifest = copy.deepcopy(manifest) new_manifest['service']['name'] = 'test_new' new_manifest['service']['delayed'] = True - + AutoTSHelp.GLOBAL_STATE = {"state" : "enabled"} # Mock init_cfg connector mock_init_cfg = Mock() @@ -402,3 +410,80 @@ def test_auto_ts_feature_update_flow(mock_sonic_db, manifest): ], any_order = True ) + + +class TestSyslogRateLimit: + """ Helper class for Syslog rate limit Tests + """ + @classmethod + def get_entry_running_cfg(cls, table, key): + if table == "SYSLOG_CONFIG_FEATURE" and key == "test": + return {"rate_limit_burst" : "10000", "rate_limit_interval" : "20"} + else: + return {} + + def test_rate_limit_register(self, mock_sonic_db, manifest): + mock_init_cfg = Mock() + mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry) + mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg) + feature_registry = FeatureRegistry(mock_sonic_db) + new_manifest = copy.deepcopy(manifest) + new_manifest['service']['syslog']['support-rate-limit'] = True + feature_registry.register(new_manifest) + mock_init_cfg.set_entry.assert_any_call("SYSLOG_CONFIG_FEATURE", "test", { + "rate_limit_interval" : "300", + "rate_limit_burst" : "20000" + } + ) + + def test_rate_limit_deregister(self, mock_sonic_db): + mock_connector = Mock() + mock_sonic_db.get_connectors = Mock(return_value=[mock_connector]) + feature_registry = FeatureRegistry(mock_sonic_db) + feature_registry.deregister("test") + mock_connector.set_entry.assert_any_call("SYSLOG_CONFIG_FEATURE", "test", None) + + def test_rate_limit_update(self, mock_sonic_db, manifest): + rate_limit_manifest = copy.deepcopy(manifest) + rate_limit_manifest['service']['syslog']['support-rate-limit'] = True + new_rate_limit_manifest = copy.deepcopy(rate_limit_manifest) + new_rate_limit_manifest['service']['name'] = 'test_new' + new_rate_limit_manifest['service']['delayed'] = True + + # Mock init_cfg connector + mock_init_cfg = Mock() + mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry) + + # Mock running/peristent cfg connector + mock_other_cfg = Mock() + mock_other_cfg.get_entry = Mock(side_effect=TestSyslogRateLimit.get_entry_running_cfg) + + # Setup sonic_db class + mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg, mock_other_cfg]) + mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg) + + feature_registry = FeatureRegistry(mock_sonic_db) + feature_registry.update(rate_limit_manifest, new_rate_limit_manifest) + + mock_init_cfg.set_entry.assert_has_calls( + [ + call("SYSLOG_CONFIG_FEATURE", "test", None), + call("SYSLOG_CONFIG_FEATURE", "test_new", { + "rate_limit_interval" : "300", + "rate_limit_burst" : "20000" + }) + ], + any_order = True + ) + + mock_other_cfg.set_entry.assert_has_calls( + [ + call("SYSLOG_CONFIG_FEATURE", "test", None), + call("SYSLOG_CONFIG_FEATURE", "test_new", { + "rate_limit_interval" : "20", + "rate_limit_burst" : "10000" + }) + ], + any_order = True + ) diff --git a/tests/syslog_input/assert_show_output.py b/tests/syslog_input/assert_show_output.py index 64924d0e69..edcbe11a15 100644 --- a/tests/syslog_input/assert_show_output.py +++ b/tests/syslog_input/assert_show_output.py @@ -15,3 +15,36 @@ 3.3.3.3 1.1.1.1 514 mgmt 2222::2222 1111::1111 514 Vrf-Data """ + +show_syslog_rate_limit_host="""\ +INTERVAL BURST +---------- ------- +100 20000 +""" + +show_syslog_rate_limit_container="""\ +SERVICE INTERVAL BURST +--------- ---------- ------- +bgp 150 30000 +snmp N/A N/A +swss N/A 30000 +syncd N/A N/A +""" + +show_syslog_rate_limit_container_bgp="""\ +SERVICE INTERVAL BURST +--------- ---------- ------- +bgp 150 30000 +""" + +show_syslog_rate_limit_container_swss="""\ +SERVICE INTERVAL BURST +--------- ---------- ------- +swss N/A 30000 +""" + +show_syslog_rate_limit_container_syncd="""\ +SERVICE INTERVAL BURST +--------- ---------- ------- +syncd N/A N/A +""" diff --git a/tests/syslog_input/syslog_rate_limit_db.json b/tests/syslog_input/syslog_rate_limit_db.json new file mode 100644 index 0000000000..79517e1e4e --- /dev/null +++ b/tests/syslog_input/syslog_rate_limit_db.json @@ -0,0 +1,33 @@ +{ + "FEATURE|bgp": { + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "FEATURE|swss": { + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "FEATURE|syncd": { + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "FEATURE|snmp": { + "state": "disabled", + "support_syslog_rate_limit": "true" + }, + "FEATURE|pmon": { + "state": "enabled", + "support_syslog_rate_limit": "false" + }, + "SYSLOG_CONFIG|GLOBAL": { + "rate_limit_interval": "100", + "rate_limit_burst": "20000" + }, + "SYSLOG_CONFIG_FEATURE|bgp": { + "rate_limit_interval": "150", + "rate_limit_burst": "30000" + }, + "SYSLOG_CONFIG_FEATURE|swss": { + "rate_limit_burst": "30000" + } +} diff --git a/tests/syslog_test.py b/tests/syslog_test.py index 768f6219f0..f7d2cde13e 100644 --- a/tests/syslog_test.py +++ b/tests/syslog_test.py @@ -11,6 +11,13 @@ from click.testing import CliRunner from utilities_common.db import Db +from syslog_util.common import FEATURE_TABLE, \ + SYSLOG_CONFIG_TABLE, \ + SYSLOG_CONFIG_GLOBAL_KEY, \ + SYSLOG_CONFIG_FEATURE_TABLE, \ + SYSLOG_RATE_LIMIT_INTERVAL, \ + SYSLOG_RATE_LIMIT_BURST, \ + SUPPORT_RATE_LIMIT from .mock_tables import dbconnector from .syslog_input import config_mock @@ -30,7 +37,6 @@ SUCCESS = 0 ERROR2 = 2 - test_path = os.path.dirname(os.path.abspath(__file__)) mock_db_path = os.path.join(test_path, "syslog_input") logger = logging.getLogger(__name__) @@ -213,6 +219,95 @@ def test_config_syslog_nonexistent_vrf(self, vrf): assert ERROR_PATTERN_NONEXISTENT_VRF in result.output assert result.exit_code == ERROR2 + @pytest.mark.parametrize("test_data", [{'interval':1, 'burst':100, 'expected_interval': '1', 'expected_burst': '100'}, + {'interval':0, 'burst':100, 'expected_interval': '0', 'expected_burst': '0'}, + {'interval':1, 'burst':0, 'expected_interval': '0', 'expected_burst': '0'}]) + def test_config_syslog_rate_limit_host(self, test_data): + db = Db() + runner = CliRunner() + + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-host"], + ["--interval", test_data['interval'], "--burst", test_data['burst']], obj=db + ) + + assert result.exit_code == SUCCESS + + table = db.cfgdb.get_table(SYSLOG_CONFIG_TABLE) + assert SYSLOG_CONFIG_GLOBAL_KEY in table + entry = table[SYSLOG_CONFIG_GLOBAL_KEY] + assert SYSLOG_RATE_LIMIT_INTERVAL in entry + assert SYSLOG_RATE_LIMIT_BURST in entry + assert entry[SYSLOG_RATE_LIMIT_INTERVAL] == test_data['expected_interval'] + assert entry[SYSLOG_RATE_LIMIT_BURST] == test_data['expected_burst'] + + @pytest.mark.parametrize("test_data", [{'subcommand': 'rate-limit-host', 'arguments': []}, + {'subcommand': 'rate-limit-container', 'arguments': ['bgp']}]) + def test_config_syslog_rate_limit_no_option(self, test_data): + db = Db() + runner = CliRunner() + + result = runner.invoke( + config.config.commands["syslog"].commands[test_data['subcommand']], test_data['arguments'], obj=db + ) + assert result.exit_code == ERROR2 + + @pytest.mark.parametrize("test_data", [{'interval':1, 'burst':100, 'expected_interval': '1', 'expected_burst': '100'}, + {'interval':0, 'burst':100, 'expected_interval': '0', 'expected_burst': '0'}, + {'interval':1, 'burst':0, 'expected_interval': '0', 'expected_burst': '0'}]) + def test_config_syslog_rate_limit_container_basic(self, test_data): + db = Db() + db.cfgdb.set_entry(FEATURE_TABLE, 'bgp', {SUPPORT_RATE_LIMIT: 'true', + 'state': 'enabled'}) + + runner = CliRunner() + + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-container"], + ["bgp", "--interval", test_data['interval'], "--burst", test_data['burst']], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + print(result.output) + assert result.exit_code == SUCCESS + + table = db.cfgdb.get_table(SYSLOG_CONFIG_FEATURE_TABLE) + assert 'bgp' in table + entry = table['bgp'] + assert SYSLOG_RATE_LIMIT_INTERVAL in entry + assert SYSLOG_RATE_LIMIT_BURST in entry + assert entry[SYSLOG_RATE_LIMIT_INTERVAL] == test_data['expected_interval'] + assert entry[SYSLOG_RATE_LIMIT_BURST] == test_data['expected_burst'] + + def test_config_syslog_rate_limit_container_invalid_service(self): + db = Db() + runner = CliRunner() + + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-container"], + ["invalid", "--interval", 1, "--burst", 100], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code != SUCCESS + + def test_config_syslog_rate_limit_container_service_no_support(self): + db = Db() + db.cfgdb.set_entry(FEATURE_TABLE, 'bgp', {SUPPORT_RATE_LIMIT: 'false', + 'state': 'enabled'}) + runner = CliRunner() + + result = runner.invoke( + config.config.commands["syslog"].commands["rate-limit-container"], + ["bgp", "--interval", 1, "--burst", 100], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code != SUCCESS + ########## SHOW SYSLOG ########## def test_show_syslog_empty(self): @@ -242,3 +337,54 @@ def test_show_syslog(self): logger.debug(result.exit_code) assert result.exit_code == SUCCESS assert result.output == assert_show_output.show_syslog + + @pytest.mark.parametrize("test_data", [{'subcommand':'rate-limit-host', 'expected_output': assert_show_output.show_syslog_rate_limit_host}, + {'subcommand':'rate-limit-container', 'expected_output': assert_show_output.show_syslog_rate_limit_container}]) + def test_show_syslog_rate_limit(self, test_data): + dbconnector.dedicated_dbs["CONFIG_DB"] = os.path.join(mock_db_path, "syslog_rate_limit_db") + + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["syslog"].commands[test_data['subcommand']], [], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + assert result.output == test_data['expected_output'] + + @pytest.mark.parametrize("test_data", [{'argument':'bgp', 'expected_output': assert_show_output.show_syslog_rate_limit_container_bgp}, + {'argument':'swss', 'expected_output': assert_show_output.show_syslog_rate_limit_container_swss}, + {'argument':'syncd', 'expected_output': assert_show_output.show_syslog_rate_limit_container_syncd}]) + def test_show_syslog_rate_limit_container_per_service(self, test_data): + dbconnector.dedicated_dbs["CONFIG_DB"] = os.path.join(mock_db_path, "syslog_rate_limit_db") + + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["syslog"].commands["rate-limit-container"], [test_data['argument']], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code == SUCCESS + assert result.output == test_data['expected_output'] + + @pytest.mark.parametrize("subcommand", ['invalid', # invalid service + 'pmon']) # not supported service + def test_show_syslog_rate_limit_container_negative(self, subcommand): + dbconnector.dedicated_dbs["CONFIG_DB"] = os.path.join(mock_db_path, "syslog_rate_limit_db") + + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["syslog"].commands["rate-limit-container"], [subcommand], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + assert result.exit_code != SUCCESS