Skip to content

Commit 4ae970c

Browse files
Junchao-MellanoxStormLiangMS
authored andcommitted
Support syslog rate limit configuration for containers and host (#2454)
* Support syslog rate limit configuration for containers and host * Fix LGTM * Fix unit test issue * Add syslog_util as a package * Fix review comment * Update feature.py
1 parent 608ed14 commit 4ae970c

File tree

11 files changed

+502
-8
lines changed

11 files changed

+502
-8
lines changed

config/syslog.py

+24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import utilities_common.cli as clicommon
88
from sonic_py_common import logger
9+
from syslog_util import common as syslog_common
910

1011

1112
SYSLOG_TABLE_CDB = "SYSLOG_SERVER"
@@ -447,3 +448,26 @@ def delete(db, server_ip_address):
447448
except Exception as e:
448449
log.log_error("Failed to remove remote syslog logging: {}".format(str(e)))
449450
ctx.fail(str(e))
451+
452+
453+
@syslog.command("rate-limit-host")
454+
@click.option("-i", "--interval", help="Configures syslog rate limit interval in seconds for host", type=click.IntRange(0, 2147483647))
455+
@click.option("-b", "--burst", help="Configures syslog rate limit burst in number of messages for host", type=click.IntRange(0, 2147483647))
456+
@clicommon.pass_db
457+
def rate_limit_host(db, interval, burst):
458+
""" Configure syslog rate limit for host """
459+
syslog_common.rate_limit_validator(interval, burst)
460+
syslog_common.save_rate_limit_to_db(db, None, interval, burst, log)
461+
462+
463+
@syslog.command("rate-limit-container")
464+
@click.argument("service_name", required=True)
465+
@click.option("-i", "--interval", help="Configures syslog rate limit interval in seconds for specified containers", type=click.IntRange(0, 2147483647))
466+
@click.option("-b", "--burst", help="Configures syslog rate limit burst in number of messages for specified containers", type=click.IntRange(0, 2147483647))
467+
@clicommon.pass_db
468+
def rate_limit_container(db, service_name, interval, burst):
469+
""" Configure syslog rate limit for containers """
470+
syslog_common.rate_limit_validator(interval, burst)
471+
feature_data = db.cfgdb.get_table(syslog_common.FEATURE_TABLE)
472+
syslog_common.service_validator(feature_data, service_name)
473+
syslog_common.save_rate_limit_to_db(db, service_name, interval, burst, log)

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
'pddf_psuutil',
4949
'pddf_thermalutil',
5050
'pddf_ledutil',
51+
'syslog_util',
5152
'show',
5253
'show.interfaces',
5354
'show.plugins',

show/syslog.py

+62-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from natsort import natsorted
55

66
import utilities_common.cli as clicommon
7+
from syslog_util import common as syslog_common
78

89

910
SYSLOG_TABLE = "SYSLOG_SERVER"
@@ -28,10 +29,14 @@ def format(header, body):
2829
cls=clicommon.AliasedGroup,
2930
invoke_without_command=True
3031
)
32+
@click.pass_context
3133
@clicommon.pass_db
32-
def syslog(db):
34+
def syslog(db, ctx):
3335
""" Show syslog server configuration """
3436

37+
if ctx.invoked_subcommand is not None:
38+
return
39+
3540
header = [
3641
"SERVER IP",
3742
"SOURCE IP",
@@ -51,3 +56,59 @@ def syslog(db):
5156
body.append(row)
5257

5358
click.echo(format(header, body))
59+
60+
@syslog.command(
61+
name='rate-limit-host'
62+
)
63+
@clicommon.pass_db
64+
def rate_limit_host(db):
65+
""" Show syslog rate limit configuration for host """
66+
67+
header = [
68+
"INTERVAL",
69+
"BURST",
70+
]
71+
body = []
72+
entry = db.cfgdb.get_entry(syslog_common.SYSLOG_CONFIG_TABLE, syslog_common.SYSLOG_CONFIG_GLOBAL_KEY)
73+
if entry:
74+
body.append([entry.get(syslog_common.SYSLOG_RATE_LIMIT_INTERVAL, 'N/A'),
75+
entry.get(syslog_common.SYSLOG_RATE_LIMIT_BURST, 'N/A')])
76+
else:
77+
body.append('N/A', 'N/A')
78+
79+
click.echo(format(header, body))
80+
81+
82+
@syslog.command(
83+
name='rate-limit-container'
84+
)
85+
@click.argument('service_name', metavar='<service_name>', required=False)
86+
@clicommon.pass_db
87+
def rate_limit_container(db, service_name):
88+
""" Show syslog rate limit configuration for containers """
89+
90+
header = [
91+
"SERVICE",
92+
"INTERVAL",
93+
"BURST",
94+
]
95+
body = []
96+
features = db.cfgdb.get_table(syslog_common.FEATURE_TABLE)
97+
98+
if service_name:
99+
syslog_common.service_validator(features, service_name)
100+
service_list = [service_name]
101+
else:
102+
service_list = [name for name, service_config in features.items() if service_config.get(syslog_common.SUPPORT_RATE_LIMIT, '').lower() == 'true']
103+
104+
syslog_configs = db.cfgdb.get_table(syslog_common.SYSLOG_CONFIG_FEATURE_TABLE)
105+
for service in natsorted(service_list):
106+
if service in syslog_configs:
107+
entry = syslog_configs[service]
108+
body.append([service,
109+
entry.get(syslog_common.SYSLOG_RATE_LIMIT_INTERVAL, 'N/A'),
110+
entry.get(syslog_common.SYSLOG_RATE_LIMIT_BURST, 'N/A')])
111+
else:
112+
body.append([service, 'N/A', 'N/A'])
113+
114+
click.echo(format(header, body))

sonic_package_manager/manifest.py

+3
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ def unmarshal(self, value):
186186
ManifestArray('after', DefaultMarshaller(str)),
187187
ManifestArray('before', DefaultMarshaller(str)),
188188
]),
189+
ManifestRoot('syslog', [
190+
ManifestField('support-rate-limit', DefaultMarshaller(bool), False),
191+
]),
189192
]),
190193
ManifestRoot('container', [
191194
ManifestField('privileged', DefaultMarshaller(bool), False),

sonic_package_manager/service_creator/feature.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
'available_mem_threshold': '10.0'
2626
}
2727

28+
SYSLOG_CONFIG = 'SYSLOG_CONFIG_FEATURE'
29+
DEFAULT_SYSLOG_FEATURE_CONFIG = {
30+
'rate_limit_interval': '300',
31+
'rate_limit_burst': '20000'
32+
}
33+
2834
def is_enabled(cfg):
2935
return cfg.get('state', 'disabled').lower() == 'enabled'
3036

@@ -36,7 +42,7 @@ def is_multi_instance(cfg):
3642
class FeatureRegistry:
3743
""" 1) FeatureRegistry class provides an interface to
3844
register/de-register new feature tables persistently.
39-
2) Writes persistent configuration to FEATURE &
45+
2) Writes persistent configuration to FEATURE &
4046
AUTO_TECHSUPPORT_FEATURE tables
4147
"""
4248

@@ -72,10 +78,14 @@ def register(self,
7278
new_cfg = {**new_cfg, **non_cfg_entries}
7379

7480
conn.set_entry(FEATURE, name, new_cfg)
75-
81+
7682
if self.register_auto_ts(name):
7783
log.info(f'{name} entry is added to {AUTO_TS_FEATURE} table')
7884

85+
if 'syslog' in manifest['service'] and 'support-rate-limit' in manifest['service']['syslog'] and manifest['service']['syslog']['support-rate-limit']:
86+
self.register_syslog_config(name)
87+
log.info(f'{name} entry is added to {SYSLOG_CONFIG} table')
88+
7989
def deregister(self, name: str):
8090
""" Deregister feature by name.
8191
@@ -89,6 +99,7 @@ def deregister(self, name: str):
8999
for conn in db_connetors:
90100
conn.set_entry(FEATURE, name, None)
91101
conn.set_entry(AUTO_TS_FEATURE, name, None)
102+
conn.set_entry(SYSLOG_CONFIG, name, None)
92103

93104
def update(self,
94105
old_manifest: Manifest,
@@ -119,10 +130,14 @@ def update(self,
119130
new_cfg = {**new_cfg, **non_cfg_entries}
120131

121132
conn.set_entry(FEATURE, new_name, new_cfg)
122-
133+
123134
if self.register_auto_ts(new_name, old_name):
124135
log.info(f'{new_name} entry is added to {AUTO_TS_FEATURE} table')
125136

137+
if 'syslog' in new_manifest['service'] and 'support-rate-limit' in new_manifest['service']['syslog'] and new_manifest['service']['syslog']['support-rate-limit']:
138+
self.register_syslog_config(new_name, old_name)
139+
log.info(f'{new_name} entry is added to {SYSLOG_CONFIG} table')
140+
126141
def is_feature_enabled(self, name: str) -> bool:
127142
""" Returns whether the feature is current enabled
128143
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):
178193
current_cfg = conn.get_entry(AUTO_TS_FEATURE, old_name)
179194
conn.set_entry(AUTO_TS_FEATURE, old_name, None)
180195
new_cfg.update(current_cfg)
181-
196+
182197
conn.set_entry(AUTO_TS_FEATURE, new_name, new_cfg)
183198
return True
184199

200+
def register_syslog_config(self, new_name, old_name=None):
201+
""" Registers syslog configuration
202+
203+
Args:
204+
new_name (str): new table name
205+
old_name (str, optional): old table name. Defaults to None.
206+
"""
207+
for conn in self._sonic_db.get_connectors():
208+
new_cfg = copy.deepcopy(DEFAULT_SYSLOG_FEATURE_CONFIG)
209+
if old_name:
210+
current_cfg = conn.get_entry(SYSLOG_CONFIG, old_name)
211+
conn.set_entry(SYSLOG_CONFIG, old_name, None)
212+
new_cfg.update(current_cfg)
213+
214+
conn.set_entry(SYSLOG_CONFIG, new_name, new_cfg)
215+
185216
@staticmethod
186217
def get_default_feature_entries(state=None, owner=None) -> Dict[str, str]:
187218
""" Get configurable feature table entries:
@@ -203,4 +234,5 @@ def get_non_configurable_feature_entries(manifest) -> Dict[str, str]:
203234
'has_global_scope': str(manifest['service']['host-service']),
204235
'has_timer': str(manifest['service']['delayed']),
205236
'check_up_status': str(manifest['service']['check_up_status']),
237+
'support_syslog_rate_limit': str(manifest['service']['syslog']['support-rate-limit']),
206238
}

syslog_util/__init__.py

Whitespace-only changes.

syslog_util/common.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import click
2+
3+
4+
FEATURE_TABLE = "FEATURE"
5+
SYSLOG_CONFIG_TABLE = 'SYSLOG_CONFIG'
6+
SYSLOG_CONFIG_GLOBAL_KEY = 'GLOBAL'
7+
SYSLOG_CONFIG_FEATURE_TABLE = 'SYSLOG_CONFIG_FEATURE'
8+
9+
SYSLOG_RATE_LIMIT_INTERVAL = 'rate_limit_interval'
10+
SYSLOG_RATE_LIMIT_BURST = 'rate_limit_burst'
11+
SUPPORT_RATE_LIMIT = 'support_syslog_rate_limit'
12+
13+
14+
def rate_limit_validator(interval, burst):
15+
"""Validate input interval/burst
16+
17+
Args:
18+
interval (int): Rate limit interval
19+
burst (int): Rate limit burst
20+
"""
21+
if interval is None and burst is None:
22+
raise click.UsageError('Either interval or burst must be configured')
23+
24+
25+
def service_validator(feature_data, service_name):
26+
"""Validate input service name
27+
28+
Args:
29+
feature_data (dict): feature entries of FEATURE table
30+
service_name (str): service name
31+
"""
32+
if service_name not in feature_data:
33+
valid_service_names = ','.join(feature_data.keys())
34+
raise click.ClickException(f'Invalid service name {service_name}, please choose from: {valid_service_names}')
35+
36+
service_data = feature_data[service_name]
37+
38+
support_rate_limit = service_data.get(SUPPORT_RATE_LIMIT, '').lower() == 'true'
39+
if not support_rate_limit:
40+
raise click.ClickException(f'Service {service_name} does not support syslog rate limit configuration')
41+
42+
43+
def save_rate_limit_to_db(db, service_name, interval, burst, log):
44+
"""Save rate limit configuration to DB
45+
46+
Args:
47+
db (object): db object
48+
service_name (str): service name. None means config for host.
49+
interval (int): rate limit interval
50+
burst (int): rate limit burst
51+
log (obj): log object
52+
"""
53+
if service_name is None:
54+
service_name = 'host'
55+
table = SYSLOG_CONFIG_TABLE
56+
key = SYSLOG_CONFIG_GLOBAL_KEY
57+
else:
58+
table = SYSLOG_CONFIG_FEATURE_TABLE
59+
key = service_name
60+
61+
if interval == 0 or burst == 0:
62+
msg = f'Disable syslog rate limit for {service_name}'
63+
click.echo(msg)
64+
log.log_notice(msg)
65+
interval = 0
66+
burst = 0
67+
68+
data = {}
69+
if interval is not None:
70+
data[SYSLOG_RATE_LIMIT_INTERVAL] = interval
71+
if burst is not None:
72+
data[SYSLOG_RATE_LIMIT_BURST] = burst
73+
db.cfgdb.mod_entry(table, key, data)
74+
log.log_notice(f"Configured syslog {service_name} rate-limits: interval={data.get(SYSLOG_RATE_LIMIT_INTERVAL, 'N/A')},\
75+
burst={data.get(SYSLOG_RATE_LIMIT_BURST, 'N/A')}")
76+

0 commit comments

Comments
 (0)