diff --git a/scripts/hostcfgd b/scripts/hostcfgd index 87b0ad7d..b9f22228 100644 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -1449,12 +1449,16 @@ class DeviceMetaCfg(object): def __init__(self): self.hostname = '' + self.timezone = None def load(self, dev_meta={}): # Get hostname initial self.hostname = dev_meta.get('localhost', {}).get('hostname', '') syslog.syslog(syslog.LOG_DEBUG, f'Initial hostname: {self.hostname}') + # Load appropriate config + self.timezone = dev_meta.get('localhost', {}).get('timezone') + def hostname_update(self, data): """ Apply hostname handler. @@ -1485,6 +1489,35 @@ class DeviceMetaCfg(object): return run_cmd(['sudo', 'monit', 'reload']) + def timezone_update(self, data): + """ + Apply timezone handler. + Run the following command in Linux: timedatectl set-timezone + Args: + data: Read table's key's data. + """ + new_timezone = data.get('timezone') + syslog.syslog(syslog.LOG_DEBUG, + f'DeviceMetaCfg: timezone update to {new_timezone}') + + if new_timezone is None: + syslog.syslog(syslog.LOG_DEBUG, + f'DeviceMetaCfg: Recieved empty timezone') + return + + if new_timezone == self.timezone: + syslog.syslog(syslog.LOG_DEBUG, + f'DeviceMetaCfg: No change in timezone') + return + + # run command will print out log error in case of error + run_cmd(['timedatectl', 'set-timezone', new_timezone]) + self.timezone = new_timezone + + run_cmd(['systemctl', 'restart', 'rsyslog'], True, False) + syslog.syslog(syslog.LOG_INFO, 'DeviceMetaCfg: Restart rsyslog after ' + 'changing timezone') + class MgmtIfaceCfg(object): """ MgmtIfaceCfg Config Daemon @@ -1824,6 +1857,7 @@ class HostConfigDaemon: def device_metadata_handler(self, key, op, data): syslog.syslog(syslog.LOG_INFO, 'DeviceMeta handler...') self.devmetacfg.hostname_update(data) + self.devmetacfg.timezone_update(data) def syslog_handler(self, key, op, data): self.syslogcfg.syslog_update(data) diff --git a/tests/hostcfgd/hostcfgd_test.py b/tests/hostcfgd/hostcfgd_test.py index 1139641b..58457a02 100644 --- a/tests/hostcfgd/hostcfgd_test.py +++ b/tests/hostcfgd/hostcfgd_test.py @@ -366,10 +366,15 @@ def test_loopback_update(self): class TestHostcfgdDaemon(TestCase): def setUp(self): + self.get_dev_meta = mock.patch( + 'sonic_py_common.device_info.get_device_runtime_metadata', + return_value={'DEVICE_RUNTIME_METADATA': {}}) + self.get_dev_meta.start() MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) def tearDown(self): MockConfigDb.CONFIG_DB = {} + self.get_dev_meta.stop() @patchfs def test_feature_events(self, fs): @@ -564,6 +569,7 @@ def test_devicemeta_event(self): """ Test handling DEVICE_METADATA events. 1) Hostname reload + 1) Timezone reload """ MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) MockConfigDb.event_queue = [(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, @@ -575,11 +581,6 @@ def test_devicemeta_event(self): daemon.load(HOSTCFG_DAEMON_INIT_CFG_DB) daemon.register_callbacks() with mock.patch('hostcfgd.subprocess') as mocked_subprocess: - popen_mock = mock.Mock() - attrs = {'communicate.return_value': ('output', 'error')} - popen_mock.configure_mock(**attrs) - mocked_subprocess.Popen.return_value = popen_mock - try: daemon.start() except TimeoutError: @@ -587,7 +588,9 @@ def test_devicemeta_event(self): expected = [ call(['sudo', 'service', 'hostname-config', 'restart']), - call(['sudo', 'monit', 'reload']) + call(['sudo', 'monit', 'reload']), + call(['timedatectl', 'set-timezone', 'Europe/Kyiv']), + call(['systemctl', 'restart', 'rsyslog']), ] mocked_subprocess.check_call.assert_has_calls(expected, any_order=True) @@ -597,31 +600,33 @@ def test_devicemeta_event(self): original_syslog = hostcfgd.syslog MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) with mock.patch('hostcfgd.syslog') as mocked_syslog: - mocked_syslog.LOG_ERR = original_syslog.LOG_ERR - try: - daemon.start() - except TimeoutError: - pass + with mock.patch('hostcfgd.subprocess') as mocked_subprocess: + mocked_syslog.LOG_ERR = original_syslog.LOG_ERR + try: + daemon.start() + except TimeoutError: + pass - expected = [ - call(original_syslog.LOG_ERR, 'Hostname was not updated: Empty not allowed') - ] - mocked_syslog.syslog.assert_has_calls(expected) + expected = [ + call(original_syslog.LOG_ERR, 'Hostname was not updated: Empty not allowed') + ] + mocked_syslog.syslog.assert_has_calls(expected) daemon.devmetacfg.hostname = "SameHostName" HOSTCFG_DAEMON_CFG_DB["DEVICE_METADATA"]["localhost"]["hostname"] = daemon.devmetacfg.hostname MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) with mock.patch('hostcfgd.syslog') as mocked_syslog: - mocked_syslog.LOG_INFO = original_syslog.LOG_INFO - try: - daemon.start() - except TimeoutError: - pass + with mock.patch('hostcfgd.subprocess') as mocked_subprocess: + mocked_syslog.LOG_INFO = original_syslog.LOG_INFO + try: + daemon.start() + except TimeoutError: + pass - expected = [ - call(original_syslog.LOG_INFO, 'Hostname was not updated: Already set up with the same name: SameHostName') - ] - mocked_syslog.syslog.assert_has_calls(expected) + expected = [ + call(original_syslog.LOG_INFO, 'Hostname was not updated: Already set up with the same name: SameHostName') + ] + mocked_syslog.syslog.assert_has_calls(expected) def test_mgmtiface_event(self): """ diff --git a/tests/hostcfgd/test_vectors.py b/tests/hostcfgd/test_vectors.py index 72ab8ca6..1c01621b 100644 --- a/tests/hostcfgd/test_vectors.py +++ b/tests/hostcfgd/test_vectors.py @@ -1178,7 +1178,8 @@ "LOOPBACK_INTERFACE": {}, "DEVICE_METADATA": { "localhost": { - "hostname": "old-hostname" + "hostname": "old-hostname", + "timezone": "Etc/UTC" } }, "MGMT_INTERFACE": {}, @@ -1247,7 +1248,8 @@ "localhost": { "subtype": "DualToR", "type": "ToRRouter", - "hostname": "SomeNewHostname" + "hostname": "SomeNewHostname", + "timezone": "Europe/Kyiv" } }, "MGMT_INTERFACE": {