Skip to content

Commit 438e54a

Browse files
authored
[Logrotate] Update log rotate configuration via ConfigDB (#61)
* Add log rotation configuration mechanism * Handle changes in LOGGING table Signed-off-by: Yevhen Fastiuk <[email protected]> * Added UT for logrotate service in hostcfgd * Align run_cmd function input arguments Signed-off-by: Yevhen Fastiuk <[email protected]> --------- Signed-off-by: Yevhen Fastiuk <[email protected]>
1 parent 89aead2 commit 438e54a

3 files changed

Lines changed: 190 additions & 0 deletions

File tree

scripts/hostcfgd

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,44 @@ class BannerCfg(object):
18271827
self.cache[k] = v
18281828

18291829

1830+
class LoggingCfg(object):
1831+
"""Logging Config Daemon
1832+
1833+
Handles changes in LOGGING table.
1834+
1) Handle change of debug/syslog log files config
1835+
"""
1836+
def __init__(self):
1837+
self.cache = {}
1838+
1839+
def load(self, logging_cfg={}):
1840+
# Get initial logging file configuration
1841+
self.cache = logging_cfg
1842+
syslog.syslog(syslog.LOG_DEBUG, f'Initial logging config: {self.cache}')
1843+
1844+
def update_logging_cfg(self, key, data):
1845+
"""Apply logging configuration
1846+
1847+
The daemon restarts logrotate-config which will regenerate logrotate
1848+
config files.
1849+
Args:
1850+
key: DB table's key that was triggered change (basically it is a
1851+
config file)
1852+
data: File's config data
1853+
"""
1854+
syslog.syslog(syslog.LOG_DEBUG, 'LoggingCfg: logging files cfg update')
1855+
if self.cache.get(key) != data:
1856+
syslog.syslog(syslog.LOG_INFO,
1857+
f'Set logging file {key} config: {data}')
1858+
try:
1859+
run_cmd(['systemctl', 'restart', 'logrotate-config'], True, True)
1860+
except Exception:
1861+
syslog.syslog(syslog.LOG_ERR, f'Failed to update {key} message')
1862+
return
1863+
1864+
# Update cache
1865+
self.cache[key] = data
1866+
1867+
18301868
class HostConfigDaemon:
18311869
def __init__(self):
18321870
self.state_db_conn = DBConnector(STATE_DB, 0)
@@ -1884,6 +1922,9 @@ class HostConfigDaemon:
18841922
# Initialize BannerCfg
18851923
self.bannermsgcfg = BannerCfg()
18861924

1925+
# Initialize LoggingCfg
1926+
self.loggingcfg = LoggingCfg()
1927+
18871928
def load(self, init_data):
18881929
aaa = init_data['AAA']
18891930
tacacs_global = init_data['TACPLUS']
@@ -1908,6 +1949,7 @@ class HostConfigDaemon:
19081949
ntp_keys = init_data.get(swsscommon.CFG_NTP_KEY_TABLE_NAME)
19091950
serial_console = init_data.get('SERIAL_CONSOLE', {})
19101951
banner_messages = init_data.get(swsscommon.CFG_BANNER_MESSAGE_TABLE_NAME)
1952+
logging = init_data.get(swsscommon.CFG_LOGGING_TABLE_NAME, {})
19111953

19121954
self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server, ldap_global, ldap_server)
19131955
self.iptables.load(lpbk_table)
@@ -1922,6 +1964,7 @@ class HostConfigDaemon:
19221964
self.ntpcfg.load(ntp_global, ntp_servers, ntp_keys)
19231965
self.serialconscfg.load(serial_console)
19241966
self.bannermsgcfg.load(banner_messages)
1967+
self.loggingcfg.load(logging)
19251968

19261969
self.pamLimitsCfg.update_config_file()
19271970

@@ -2080,6 +2123,10 @@ class HostConfigDaemon:
20802123
syslog.syslog(syslog.LOG_INFO, 'BANNER_MESSAGE table handler...')
20812124
self.bannermsgcfg.banner_message(key, data)
20822125

2126+
def logging_handler(self, key, op, data):
2127+
syslog.syslog(syslog.LOG_INFO, 'LOGGING table handler...')
2128+
self.loggingcfg.update_logging_cfg(key, data)
2129+
20832130
def wait_till_system_init_done(self):
20842131
# No need to print the output in the log file so using the "--quiet"
20852132
# flag
@@ -2151,6 +2198,10 @@ class HostConfigDaemon:
21512198
self.config_db.subscribe(swsscommon.CFG_BANNER_MESSAGE_TABLE_NAME,
21522199
make_callback(self.banner_handler))
21532200

2201+
# Handle LOGGING changes
2202+
self.config_db.subscribe(swsscommon.CFG_LOGGING_TABLE_NAME,
2203+
make_callback(self.logging_handler))
2204+
21542205
syslog.syslog(syslog.LOG_INFO,
21552206
"Waiting for systemctl to finish initialization")
21562207
self.wait_till_system_init_done()
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import importlib.machinery
2+
import importlib.util
3+
import os
4+
import sys
5+
6+
from copy import copy
7+
from swsscommon import swsscommon
8+
from syslog import syslog, LOG_ERR
9+
from tests.hostcfgd.test_logging_vectors \
10+
import HOSTCFGD_TEST_LOGGING_VECTOR as logging_test_data
11+
from tests.common.mock_configdb import MockConfigDb, MockDBConnector
12+
from unittest import TestCase, mock
13+
14+
test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15+
modules_path = os.path.dirname(test_path)
16+
scripts_path = os.path.join(modules_path, "scripts")
17+
src_path = os.path.dirname(modules_path)
18+
templates_path = os.path.join(src_path, "sonic-host-services-data/templates")
19+
output_path = os.path.join(test_path, "hostcfgd/output")
20+
sample_output_path = os.path.join(test_path, "hostcfgd/sample_output")
21+
sys.path.insert(0, modules_path)
22+
23+
# Load the file under test
24+
hostcfgd_path = os.path.join(scripts_path, 'hostcfgd')
25+
loader = importlib.machinery.SourceFileLoader('hostcfgd', hostcfgd_path)
26+
spec = importlib.util.spec_from_loader(loader.name, loader)
27+
hostcfgd = importlib.util.module_from_spec(spec)
28+
loader.exec_module(hostcfgd)
29+
sys.modules['hostcfgd'] = hostcfgd
30+
31+
# Mock swsscommon classes
32+
hostcfgd.ConfigDBConnector = MockConfigDb
33+
hostcfgd.DBConnector = MockDBConnector
34+
hostcfgd.Table = mock.Mock()
35+
hostcfgd.run_cmd = mock.Mock()
36+
37+
38+
class TestHostcfgLogging(TestCase):
39+
"""
40+
Test hostcfgd daemon - LogRotate
41+
"""
42+
43+
def __init__(self, *args, **kwargs):
44+
super(TestHostcfgLogging, self).__init__(*args, **kwargs)
45+
self.host_config_daemon = None
46+
47+
def setUp(self):
48+
MockConfigDb.set_config_db(logging_test_data['initial'])
49+
self.host_config_daemon = hostcfgd.HostConfigDaemon()
50+
51+
logging_config = self.host_config_daemon.config_db.get_table(
52+
swsscommon.CFG_LOGGING_TABLE_NAME)
53+
54+
assert self.host_config_daemon.loggingcfg.cache == {}
55+
self.host_config_daemon.loggingcfg.load(logging_config)
56+
assert self.host_config_daemon.loggingcfg.cache != {}
57+
58+
# Reset run_cmd mock
59+
hostcfgd.run_cmd.reset_mock()
60+
61+
def tearDown(self):
62+
self.host_config_daemon = None
63+
MockConfigDb.set_config_db({})
64+
65+
def update_config(self, config_name):
66+
MockConfigDb.mod_config_db(logging_test_data[config_name])
67+
68+
syslog_data = logging_test_data[config_name]['LOGGING']['syslog']
69+
debug_data = logging_test_data[config_name]['LOGGING']['debug']
70+
71+
self.host_config_daemon.logging_handler(key='syslog', op=None,
72+
data=syslog_data)
73+
self.host_config_daemon.logging_handler(key='debug', op=None,
74+
data=debug_data)
75+
76+
def assert_applied(self, config_name):
77+
"""Assert that updated config triggered appropriate services
78+
79+
Args:
80+
config_name: str: Test vectors config name
81+
82+
Assert:
83+
Assert when config wasn't used
84+
"""
85+
orig_cache = copy(self.host_config_daemon.loggingcfg.cache)
86+
self.update_config(config_name)
87+
assert self.host_config_daemon.loggingcfg.cache != orig_cache
88+
hostcfgd.run_cmd.assert_called()
89+
90+
def test_rsyslog_handle_modified(self):
91+
self.assert_applied('modified')
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'''
2+
hostcfgd test logging configuration vector
3+
'''
4+
5+
HOSTCFGD_TEST_LOGGING_VECTOR = {
6+
'initial': {
7+
'DEVICE_METADATA': {
8+
'localhost': {
9+
'hostname': 'logrotate',
10+
},
11+
},
12+
'LOGGING': {
13+
'syslog': {
14+
'disk_percentage': '',
15+
'frequency': 'daily',
16+
'max_number': '20',
17+
'size': '10.0'
18+
},
19+
'debug': {
20+
'disk_percentage': '',
21+
'frequency': 'daily',
22+
'max_number': '10',
23+
'size': '20.0'
24+
}
25+
},
26+
"SSH_SERVER": {
27+
"POLICIES" :{
28+
"max_sessions": "100"
29+
}
30+
}
31+
},
32+
'modified': {
33+
'LOGGING': {
34+
'syslog': {
35+
'disk_percentage': '',
36+
'frequency': 'weekly',
37+
'max_number': '100',
38+
'size': '20.0'
39+
},
40+
'debug': {
41+
'disk_percentage': '',
42+
'frequency': 'weekly',
43+
'max_number': '20',
44+
'size': '100.0'
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)